Skip to content
This repository was archived by the owner on Jan 23, 2025. It is now read-only.

Commit 3bea5dd

Browse files
Merge pull request #68 from topcoder-platform/develop
Self Service prod release
2 parents 9b4ff0a + 1ed67c6 commit 3bea5dd

7 files changed

+211
-26
lines changed

ReadMe.md

+1
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ npm run e2e
207207

208208
- TBD
209209

210+
210211
## Verification
211212
Refer to the verification document `Verification.md`
212213

config/default.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,7 @@ module.exports = {
8585
SYNC_V5_TERM_UUID: process.env.SYNC_V5_TERM_UUID || '317cd8f9-d66c-4f2a-8774-63c612d99cd4',
8686
SYNC_V5_WRITE_ENABLED: process.env.SYNC_V5_WRITE_ENABLED === 'true' || false,
8787

88-
TIMEZONE: process.env.TIMEZONE || 'America/New_York'
88+
TIMEZONE: process.env.TIMEZONE || 'America/New_York',
89+
90+
IGNORED_ORIGINATORS: process.env.IGNORED_ORIGINATORS ? process.env.IGNORED_ORIGINATORS.split(',') : ['legacy-migration-script']
8991
}

src/app.js

+8
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ const dataHandler = (messageSet, topic, partition) => Promise.each(messageSet, a
4646
return
4747
}
4848

49+
if (_.includes(config.IGNORED_ORIGINATORS, messageJSON.originator)) {
50+
logger.error(`The message originator is in the ignored list. Originator: ${messageJSON.originator}`)
51+
52+
// commit the message and ignore it
53+
await consumer.commitOffset({ topic, partition, offset: m.offset })
54+
return
55+
}
56+
4957
// do not trust the message payload
5058
// the message.payload will be replaced with the data from the API
5159
try {

src/constants.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const createChallengeStatusesMap = {
3333
const challengeStatuses = {
3434
New: 'New',
3535
Draft: 'Draft',
36+
Approved: 'Approved',
3637
Canceled: 'Canceled',
3738
Active: 'Active',
3839
Completed: 'Completed',
@@ -43,7 +44,8 @@ const challengeStatuses = {
4344
CancelledWinnerUnresponsive: 'Cancelled - Winner Unresponsive',
4445
CancelledClientRequest: 'Cancelled - Client Request',
4546
CancelledRequirementsInfeasible: 'Cancelled - Requirements Infeasible',
46-
CancelledZeroRegistrations: 'Cancelled - Zero Registrations'
47+
CancelledZeroRegistrations: 'Cancelled - Zero Registrations',
48+
CancelledPaymentFailed: 'Cancelled - Payment Failed'
4749
}
4850

4951
const PhaseStatusTypes = {

src/services/ProcessorService.js

+40-24
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ const copilotPaymentService = require('./copilotPaymentService')
1616
const timelineService = require('./timelineService')
1717
const metadataService = require('./metadataService')
1818
const paymentService = require('./paymentService')
19+
const { createOrSetNumberOfReviewers } = require('./selfServiceReviewerService')
20+
const { disableTimelineNotifications } = require('./selfServiceNotificationService')
1921

2022
/**
2123
* Drop and recreate phases in ifx
@@ -68,8 +70,10 @@ async function recreatePhases (legacyId, v5Phases, createdBy) {
6870
* Sync the information from the v5 phases into legacy
6971
* @param {Number} legacyId the legacy challenge ID
7072
* @param {Array} v5Phases the v5 phases
73+
* @param {Boolean} isSelfService is the challenge self-service
74+
* @param {String} createdBy the created by
7175
*/
72-
async function syncChallengePhases (legacyId, v5Phases) {
76+
async function syncChallengePhases (legacyId, v5Phases, createdBy, isSelfService) {
7377
const phaseTypes = await timelineService.getPhaseTypes()
7478
const phasesFromIFx = await timelineService.getChallengePhases(legacyId)
7579
logger.debug(`Phases from v5: ${JSON.stringify(v5Phases)}`)
@@ -104,6 +108,10 @@ async function syncChallengePhases (legacyId, v5Phases) {
104108
} else {
105109
logger.info(`No v5 Equivalent Found for ${phaseName}`)
106110
}
111+
if (isSelfService && phaseName === 'Review') {
112+
// make sure to set the required reviewers to 2
113+
await createOrSetNumberOfReviewers(phase.project_phase_id, 2, createdBy)
114+
}
107115
}
108116
// TODO: What about iterative reviews? There can be many for the same challenge.
109117
// TODO: handle timeline template updates
@@ -216,10 +224,10 @@ async function associateChallengeTerms (v5Terms, legacyChallengeId, createdBy, u
216224
const standardTerms = _.find(v5Terms, e => e.id === config.V5_TERMS_STANDARD_ID)
217225
const legacyStandardTerms = _.find(legacyTermsArray, e => _.toNumber(e.id) === _.toNumber(config.LEGACY_TERMS_STANDARD_ID))
218226

219-
// logger.debug(`NDA: ${config.V5_TERMS_NDA_ID} - ${JSON.stringify(nda)}`)
220-
// logger.debug(`Standard Terms: ${config.V5_TERMS_STANDARD_ID} - ${JSON.stringify(standardTerms)}`)
221-
// logger.debug(`Legacy NDA: ${JSON.stringify(legacyNDA)}`)
222-
// logger.debug(`Legacy Standard Terms: ${JSON.stringify(legacyStandardTerms)}`)
227+
logger.debug(`NDA: ${config.V5_TERMS_NDA_ID} - ${JSON.stringify(nda)}`)
228+
logger.debug(`Standard Terms: ${config.V5_TERMS_STANDARD_ID} - ${JSON.stringify(standardTerms)}`)
229+
logger.debug(`Legacy NDA: ${JSON.stringify(legacyNDA)}`)
230+
logger.debug(`Legacy Standard Terms: ${JSON.stringify(legacyStandardTerms)}`)
223231

224232
const m2mToken = await helper.getM2MToken()
225233
if (standardTerms && standardTerms.id && !legacyStandardTerms) {
@@ -254,21 +262,17 @@ async function associateChallengeTerms (v5Terms, legacyChallengeId, createdBy, u
254262
*/
255263
async function setCopilotPayment (challengeId, legacyChallengeId, prizeSets = [], createdBy, updatedBy, m2mToken) {
256264
try {
257-
const copilotPayment = _.get(_.find(prizeSets, p => p.type === config.COPILOT_PAYMENT_TYPE), 'prizes[0].value', null)
258-
if (copilotPayment) {
259-
logger.debug('Fetching challenge copilot...')
260-
const res = await helper.getRequest(`${config.V5_RESOURCES_API_URL}?challengeId=${challengeId}&roleId=${config.COPILOT_ROLE_ID}`, m2mToken)
261-
const [copilotResource] = res.body
262-
if (!copilotResource) {
263-
logger.warn(`Copilot does not exist for challenge ${challengeId} (legacy: ${legacyChallengeId})`)
264-
return
265-
}
266-
logger.debug(`Setting Copilot Payment: ${copilotPayment} for legacyId ${legacyChallengeId} for copilot ${copilotResource.memberId}`)
267-
if (copilotPayment !== null && copilotPayment >= 0) {
268-
await copilotPaymentService.setManualCopilotPayment(legacyChallengeId, createdBy, updatedBy)
269-
}
270-
await copilotPaymentService.setCopilotPayment(legacyChallengeId, copilotPayment, createdBy, updatedBy)
265+
const copilotPayment = _.get(_.find(prizeSets, p => p.type === config.COPILOT_PAYMENT_TYPE), 'prizes[0].value', 0)
266+
logger.debug('Fetching challenge copilot...')
267+
const res = await helper.getRequest(`${config.V5_RESOURCES_API_URL}?challengeId=${challengeId}&roleId=${config.COPILOT_ROLE_ID}`, m2mToken)
268+
const [copilotResource] = res.body
269+
if (!copilotResource) {
270+
logger.warn(`Copilot does not exist for challenge ${challengeId} (legacy: ${legacyChallengeId})`)
271+
return
271272
}
273+
logger.debug(`Setting Copilot Payment: ${copilotPayment} for legacyId ${legacyChallengeId} for copilot ${copilotResource.memberId}`)
274+
await copilotPaymentService.setManualCopilotPayment(legacyChallengeId, createdBy, updatedBy)
275+
await copilotPaymentService.setCopilotPayment(legacyChallengeId, copilotPayment, createdBy, updatedBy)
272276
} catch (e) {
273277
logger.error('Failed to set the copilot payment!')
274278
logger.debug(e)
@@ -376,7 +380,7 @@ async function parsePayload (payload, m2mToken) {
376380
name: payload.name,
377381
reviewType: _.get(payload, 'legacy.reviewType', 'INTERNAL'),
378382
projectId,
379-
status: payload.status
383+
status: payload.status === constants.challengeStatuses.CancelledPaymentFailed ? constants.challengeStatuses.CancelledFailedScreening : payload.status
380384
}
381385
if (payload.billingAccountId) {
382386
data.billingAccountId = payload.billingAccountId
@@ -601,6 +605,7 @@ async function createChallenge (saveDraftContestDTO, challengeUuid, createdByUse
601605
// Repost all challenge resource on Kafka so they will get created on legacy by the legacy-challenge-resource-processor
602606
await rePostResourcesOnKafka(challengeUuid, m2mToken)
603607
await timelineService.enableTimelineNotifications(legacyId, createdByUserId)
608+
await metadataService.createOrUpdateMetadata(legacyId, 9, 'On', createdByUserId) // autopilot
604609
return legacyId
605610
}
606611

@@ -609,8 +614,8 @@ async function createChallenge (saveDraftContestDTO, challengeUuid, createdByUse
609614
* @param {Object} message the kafka message
610615
*/
611616
async function processMessage (message) {
612-
if (_.get(message, 'payload.legacy.pureV5Task')) {
613-
logger.debug(`Challenge ${message.payload.id} is a pure v5 task. Will skip...`)
617+
if (_.get(message, 'payload.legacy.pureV5Task') || _.get(message, 'payload.legacy.pureV5')) {
618+
logger.debug(`Challenge ${message.payload.id} is a pure v5 task or challenge. Will skip...`)
614619
return
615620
}
616621

@@ -619,6 +624,11 @@ async function processMessage (message) {
619624
return
620625
}
621626

627+
if (message.payload.status === constants.challengeStatuses.Approved) {
628+
logger.debug(`Will skip updating on legacy as status is ${constants.challengeStatuses.Approved}`)
629+
return
630+
}
631+
622632
logger.info(`Processing Kafka Message: ${JSON.stringify(message)}`)
623633

624634
const createdByUserHandle = _.get(message, 'payload.createdBy')
@@ -640,6 +650,9 @@ async function processMessage (message) {
640650
logger.debug('Legacy ID does not exist. Will create...')
641651
legacyId = await createChallenge(saveDraftContestDTO, challengeUuid, createdByUserId, message.payload.legacy, m2mToken)
642652
await recreatePhases(legacyId, message.payload.phases, updatedByUserId)
653+
if (_.get(message, 'payload.legacy.selfService')) {
654+
await disableTimelineNotifications(legacyId, createdByUserId) // disable
655+
}
643656
}
644657

645658
let challenge
@@ -676,6 +689,8 @@ async function processMessage (message) {
676689
logger.info('Activating challenge...')
677690
const activated = await activateChallenge(legacyId)
678691
logger.info(`Activated! ${JSON.stringify(activated)}`)
692+
// make sure autopilot is on
693+
await metadataService.createOrUpdateMetadata(legacyId, 9, 'On', createdByUserId) // autopilot
679694
// Repost all challenge resource on Kafka so they will get created on legacy by the legacy-challenge-resource-processor
680695
await rePostResourcesOnKafka(challengeUuid, m2mToken)
681696
}
@@ -694,7 +709,7 @@ async function processMessage (message) {
694709
}
695710

696711
if (!_.get(message.payload, 'task.isTask')) {
697-
await syncChallengePhases(legacyId, message.payload.phases)
712+
await syncChallengePhases(legacyId, message.payload.phases, _.get(message, 'payload.legacy.selfService'), createdByUserId)
698713
} else {
699714
logger.info('Will skip syncing phases as the challenge is a task...')
700715
}
@@ -720,7 +735,8 @@ processMessage.schema = {
720735
reviewType: Joi.string().required(),
721736
confidentialityType: Joi.string(),
722737
directProjectId: Joi.number(),
723-
forumId: Joi.number().integer().positive()
738+
forumId: Joi.number().integer().positive(),
739+
selfService: Joi.boolean()
724740
}).unknown(true),
725741
task: Joi.object().keys({
726742
isTask: Joi.boolean().default(false),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/**
2+
* timeline notification Service
3+
* Interacts with InformixDB
4+
*/
5+
const util = require('util')
6+
const logger = require('../common/logger')
7+
const helper = require('../common/helper')
8+
9+
const QUERY_GET_ENTRY = 'SELECT notification_type_id FROM notification WHERE external_ref_id = %d AND project_id = %d'
10+
const QUERY_DELETE = 'DELETE FROM notification WHERE external_ref_id = ? AND project_id = ?'
11+
12+
/**
13+
* Prepare Informix statement
14+
* @param {Object} connection the Informix connection
15+
* @param {String} sql the sql
16+
* @return {Object} Informix statement
17+
*/
18+
async function prepare (connection, sql) {
19+
// logger.debug(`Preparing SQL ${sql}`)
20+
const stmt = await connection.prepareAsync(sql)
21+
return Promise.promisifyAll(stmt)
22+
}
23+
24+
/**
25+
* Get entry
26+
* @param {Number} legacyId the legacy challenge ID
27+
* @param {String} userId the userId
28+
*/
29+
async function getEntry (legacyId, userId) {
30+
const connection = await helper.getInformixConnection()
31+
let result = null
32+
try {
33+
result = await connection.queryAsync(util.format(QUERY_GET_ENTRY, userId, legacyId))
34+
} catch (e) {
35+
logger.error(`Error in 'getEntry' ${e}`)
36+
throw e
37+
} finally {
38+
await connection.closeAsync()
39+
}
40+
return result
41+
}
42+
43+
/**
44+
* Disable timeline notifications
45+
* @param {Number} legacyId the legacy challenge ID
46+
* @param {String} userId the userId
47+
*/
48+
async function disableTimelineNotifications (legacyId, userId) {
49+
const connection = await helper.getInformixConnection()
50+
let result = null
51+
try {
52+
await connection.beginTransactionAsync()
53+
const [existing] = await getEntry(legacyId, userId)
54+
if (existing) {
55+
const query = await prepare(connection, QUERY_DELETE)
56+
result = await query.executeAsync([userId, legacyId])
57+
}
58+
await connection.commitTransactionAsync()
59+
} catch (e) {
60+
logger.error(`Error in 'disableTimelineNotifications' ${e}, rolling back transaction`)
61+
await connection.rollbackTransactionAsync()
62+
throw e
63+
} finally {
64+
await connection.closeAsync()
65+
}
66+
return result
67+
}
68+
69+
module.exports = {
70+
getEntry,
71+
disableTimelineNotifications
72+
}
+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/**
2+
* Number of reviewers Service
3+
* Interacts with InformixDB
4+
*/
5+
const util = require('util')
6+
const logger = require('../common/logger')
7+
const helper = require('../common/helper')
8+
9+
const QUERY_GET_ENTRY = 'SELECT parameter FROM phase_criteria WHERE project_phase_id = %d'
10+
const QUERY_CREATE = 'INSERT INTO phase_criteria (project_phase_id, phase_criteria_type_id, parameter, create_user, create_date, modify_user, modify_date) VALUES (?, 6, ?, ?, CURRENT, ?, CURRENT)'
11+
const QUERY_UPDATE = 'UPDATE phase_criteria SET parameter = ?, modify_user = ?, modify_date = CURRENT WHERE project_phase_id = ?'
12+
const QUERY_DELETE = 'DELETE FROM phase_criteria WHERE project_phase_id = ?'
13+
14+
/**
15+
* Prepare Informix statement
16+
* @param {Object} connection the Informix connection
17+
* @param {String} sql the sql
18+
* @return {Object} Informix statement
19+
*/
20+
async function prepare (connection, sql) {
21+
// logger.debug(`Preparing SQL ${sql}`)
22+
const stmt = await connection.prepareAsync(sql)
23+
return Promise.promisifyAll(stmt)
24+
}
25+
26+
/**
27+
* Get entry
28+
* @param {Number} phaseId the phase ID
29+
*/
30+
async function getEntry (phaseId) {
31+
// logger.debug(`Getting Groups for Challenge ${challengeLegacyId}`)
32+
const connection = await helper.getInformixConnection()
33+
let result = null
34+
try {
35+
result = await connection.queryAsync(util.format(QUERY_GET_ENTRY, phaseId))
36+
} catch (e) {
37+
logger.error(`Error in 'getEntry' ${e}`)
38+
throw e
39+
} finally {
40+
await connection.closeAsync()
41+
}
42+
return result
43+
}
44+
45+
/**
46+
* Enable timeline notifications
47+
* @param {Number} phaseId the legacy challenge ID
48+
* @param {Number} typeId the type ID
49+
* @param {Any} value the value
50+
* @param {String} createdBy the created by
51+
*/
52+
async function createOrSetNumberOfReviewers (phaseId, value, createdBy) {
53+
const connection = await helper.getInformixConnection()
54+
let result = null
55+
try {
56+
await connection.beginTransactionAsync()
57+
const [existing] = await getEntry(phaseId)
58+
if (existing) {
59+
if (value) {
60+
const query = await prepare(connection, QUERY_UPDATE)
61+
result = await query.executeAsync([value, createdBy, phaseId])
62+
} else {
63+
const query = await prepare(connection, QUERY_DELETE)
64+
result = await query.executeAsync([phaseId, value])
65+
}
66+
} else {
67+
const query = await prepare(connection, QUERY_CREATE)
68+
result = await query.executeAsync([phaseId, value, createdBy, createdBy])
69+
}
70+
await connection.commitTransactionAsync()
71+
} catch (e) {
72+
logger.error(`Error in 'createOrSetNumberOfReviewers' ${e}, rolling back transaction`)
73+
await connection.rollbackTransactionAsync()
74+
throw e
75+
} finally {
76+
await connection.closeAsync()
77+
}
78+
return result
79+
}
80+
81+
module.exports = {
82+
getEntry,
83+
createOrSetNumberOfReviewers
84+
}

0 commit comments

Comments
 (0)