Skip to content
This repository was archived by the owner on Jan 23, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1fe2532
use onsiteEfforts and offshoreEfforts
ThomasKranitsas Apr 1, 2021
7a2a8af
Set the copilot payment to 0 if it's not defined
ThomasKranitsas Apr 5, 2021
dda1988
Merge pull request #50 from topcoder-platform/fix-effort-hours
rootelement Apr 6, 2021
c8feca9
Merge pull request #52 from topcoder-platform/fix-zero-copilot-payments
rootelement Apr 6, 2021
4a5274b
always set the copilot payment
ThomasKranitsas Apr 6, 2021
650a3e7
Merge pull request #54 from topcoder-platform/fix-zero-copilot-payments
ThomasKranitsas Apr 6, 2021
fdf0ee5
sync phases on tasks
ThomasKranitsas Apr 29, 2021
e8bfe03
Revert "sync phases on tasks"
ThomasKranitsas Apr 29, 2021
1cf9e08
Ignore messages from configured originators
ThomasKranitsas Apr 29, 2021
7fc9a0c
Extract markup metadata
ThomasKranitsas May 3, 2021
b034c59
Merge pull request #57 from topcoder-platform/fix-markup-metadata
rootelement May 3, 2021
deb2534
skip pureV5 challenges
ThomasKranitsas Jun 1, 2021
ba3a6a7
Debugging Terms
Jul 6, 2021
de6d90c
redeployed
ThomasKranitsas Sep 30, 2021
cabb59c
map payment failed status to failed screening
ThomasKranitsas Jan 21, 2022
31c3b40
Ignore approved state
ThomasKranitsas Jan 21, 2022
07f103d
Merge pull request #66 from topcoder-platform/hotfix/efforthours
ThomasKranitsas Feb 3, 2022
2d94829
Merge pull request #67 from topcoder-platform/hot-fix-markup
ThomasKranitsas Feb 3, 2022
78cdaf1
set number of reviewers on self service challenges
ThomasKranitsas Feb 3, 2022
b9b4a06
set autopilot to on
ThomasKranitsas Feb 3, 2022
1ed67c6
disable notifications for creator
ThomasKranitsas Feb 3, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ npm run e2e

- TBD


## Verification
Refer to the verification document `Verification.md`

Expand Down
4 changes: 3 additions & 1 deletion config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,7 @@ module.exports = {
SYNC_V5_TERM_UUID: process.env.SYNC_V5_TERM_UUID || '317cd8f9-d66c-4f2a-8774-63c612d99cd4',
SYNC_V5_WRITE_ENABLED: process.env.SYNC_V5_WRITE_ENABLED === 'true' || false,

TIMEZONE: process.env.TIMEZONE || 'America/New_York'
TIMEZONE: process.env.TIMEZONE || 'America/New_York',

IGNORED_ORIGINATORS: process.env.IGNORED_ORIGINATORS ? process.env.IGNORED_ORIGINATORS.split(',') : ['legacy-migration-script']
}
8 changes: 8 additions & 0 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ const dataHandler = (messageSet, topic, partition) => Promise.each(messageSet, a
return
}

if (_.includes(config.IGNORED_ORIGINATORS, messageJSON.originator)) {
logger.error(`The message originator is in the ignored list. Originator: ${messageJSON.originator}`)

// commit the message and ignore it
await consumer.commitOffset({ topic, partition, offset: m.offset })
return
}

// do not trust the message payload
// the message.payload will be replaced with the data from the API
try {
Expand Down
4 changes: 3 additions & 1 deletion src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const createChallengeStatusesMap = {
const challengeStatuses = {
New: 'New',
Draft: 'Draft',
Approved: 'Approved',
Canceled: 'Canceled',
Active: 'Active',
Completed: 'Completed',
Expand All @@ -43,7 +44,8 @@ const challengeStatuses = {
CancelledWinnerUnresponsive: 'Cancelled - Winner Unresponsive',
CancelledClientRequest: 'Cancelled - Client Request',
CancelledRequirementsInfeasible: 'Cancelled - Requirements Infeasible',
CancelledZeroRegistrations: 'Cancelled - Zero Registrations'
CancelledZeroRegistrations: 'Cancelled - Zero Registrations',
CancelledPaymentFailed: 'Cancelled - Payment Failed'
}

const PhaseStatusTypes = {
Expand Down
64 changes: 40 additions & 24 deletions src/services/ProcessorService.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const copilotPaymentService = require('./copilotPaymentService')
const timelineService = require('./timelineService')
const metadataService = require('./metadataService')
const paymentService = require('./paymentService')
const { createOrSetNumberOfReviewers } = require('./selfServiceReviewerService')
const { disableTimelineNotifications } = require('./selfServiceNotificationService')

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

// logger.debug(`NDA: ${config.V5_TERMS_NDA_ID} - ${JSON.stringify(nda)}`)
// logger.debug(`Standard Terms: ${config.V5_TERMS_STANDARD_ID} - ${JSON.stringify(standardTerms)}`)
// logger.debug(`Legacy NDA: ${JSON.stringify(legacyNDA)}`)
// logger.debug(`Legacy Standard Terms: ${JSON.stringify(legacyStandardTerms)}`)
logger.debug(`NDA: ${config.V5_TERMS_NDA_ID} - ${JSON.stringify(nda)}`)
logger.debug(`Standard Terms: ${config.V5_TERMS_STANDARD_ID} - ${JSON.stringify(standardTerms)}`)
logger.debug(`Legacy NDA: ${JSON.stringify(legacyNDA)}`)
logger.debug(`Legacy Standard Terms: ${JSON.stringify(legacyStandardTerms)}`)

const m2mToken = await helper.getM2MToken()
if (standardTerms && standardTerms.id && !legacyStandardTerms) {
Expand Down Expand Up @@ -254,21 +262,17 @@ async function associateChallengeTerms (v5Terms, legacyChallengeId, createdBy, u
*/
async function setCopilotPayment (challengeId, legacyChallengeId, prizeSets = [], createdBy, updatedBy, m2mToken) {
try {
const copilotPayment = _.get(_.find(prizeSets, p => p.type === config.COPILOT_PAYMENT_TYPE), 'prizes[0].value', null)
if (copilotPayment) {
logger.debug('Fetching challenge copilot...')
const res = await helper.getRequest(`${config.V5_RESOURCES_API_URL}?challengeId=${challengeId}&roleId=${config.COPILOT_ROLE_ID}`, m2mToken)
const [copilotResource] = res.body
if (!copilotResource) {
logger.warn(`Copilot does not exist for challenge ${challengeId} (legacy: ${legacyChallengeId})`)
return
}
logger.debug(`Setting Copilot Payment: ${copilotPayment} for legacyId ${legacyChallengeId} for copilot ${copilotResource.memberId}`)
if (copilotPayment !== null && copilotPayment >= 0) {
await copilotPaymentService.setManualCopilotPayment(legacyChallengeId, createdBy, updatedBy)
}
await copilotPaymentService.setCopilotPayment(legacyChallengeId, copilotPayment, createdBy, updatedBy)
const copilotPayment = _.get(_.find(prizeSets, p => p.type === config.COPILOT_PAYMENT_TYPE), 'prizes[0].value', 0)
logger.debug('Fetching challenge copilot...')
const res = await helper.getRequest(`${config.V5_RESOURCES_API_URL}?challengeId=${challengeId}&roleId=${config.COPILOT_ROLE_ID}`, m2mToken)
const [copilotResource] = res.body
if (!copilotResource) {
logger.warn(`Copilot does not exist for challenge ${challengeId} (legacy: ${legacyChallengeId})`)
return
}
logger.debug(`Setting Copilot Payment: ${copilotPayment} for legacyId ${legacyChallengeId} for copilot ${copilotResource.memberId}`)
await copilotPaymentService.setManualCopilotPayment(legacyChallengeId, createdBy, updatedBy)
await copilotPaymentService.setCopilotPayment(legacyChallengeId, copilotPayment, createdBy, updatedBy)
} catch (e) {
logger.error('Failed to set the copilot payment!')
logger.debug(e)
Expand Down Expand Up @@ -376,7 +380,7 @@ async function parsePayload (payload, m2mToken) {
name: payload.name,
reviewType: _.get(payload, 'legacy.reviewType', 'INTERNAL'),
projectId,
status: payload.status
status: payload.status === constants.challengeStatuses.CancelledPaymentFailed ? constants.challengeStatuses.CancelledFailedScreening : payload.status
}
if (payload.billingAccountId) {
data.billingAccountId = payload.billingAccountId
Expand Down Expand Up @@ -601,6 +605,7 @@ async function createChallenge (saveDraftContestDTO, challengeUuid, createdByUse
// Repost all challenge resource on Kafka so they will get created on legacy by the legacy-challenge-resource-processor
await rePostResourcesOnKafka(challengeUuid, m2mToken)
await timelineService.enableTimelineNotifications(legacyId, createdByUserId)
await metadataService.createOrUpdateMetadata(legacyId, 9, 'On', createdByUserId) // autopilot
return legacyId
}

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

Expand All @@ -619,6 +624,11 @@ async function processMessage (message) {
return
}

if (message.payload.status === constants.challengeStatuses.Approved) {
logger.debug(`Will skip updating on legacy as status is ${constants.challengeStatuses.Approved}`)
return
}

logger.info(`Processing Kafka Message: ${JSON.stringify(message)}`)

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

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

if (!_.get(message.payload, 'task.isTask')) {
await syncChallengePhases(legacyId, message.payload.phases)
await syncChallengePhases(legacyId, message.payload.phases, _.get(message, 'payload.legacy.selfService'), createdByUserId)
} else {
logger.info('Will skip syncing phases as the challenge is a task...')
}
Expand All @@ -720,7 +735,8 @@ processMessage.schema = {
reviewType: Joi.string().required(),
confidentialityType: Joi.string(),
directProjectId: Joi.number(),
forumId: Joi.number().integer().positive()
forumId: Joi.number().integer().positive(),
selfService: Joi.boolean()
}).unknown(true),
task: Joi.object().keys({
isTask: Joi.boolean().default(false),
Expand Down
72 changes: 72 additions & 0 deletions src/services/selfServiceNotificationService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* timeline notification Service
* Interacts with InformixDB
*/
const util = require('util')
const logger = require('../common/logger')
const helper = require('../common/helper')

const QUERY_GET_ENTRY = 'SELECT notification_type_id FROM notification WHERE external_ref_id = %d AND project_id = %d'
const QUERY_DELETE = 'DELETE FROM notification WHERE external_ref_id = ? AND project_id = ?'

/**
* Prepare Informix statement
* @param {Object} connection the Informix connection
* @param {String} sql the sql
* @return {Object} Informix statement
*/
async function prepare (connection, sql) {
// logger.debug(`Preparing SQL ${sql}`)
const stmt = await connection.prepareAsync(sql)
return Promise.promisifyAll(stmt)
}

/**
* Get entry
* @param {Number} legacyId the legacy challenge ID
* @param {String} userId the userId
*/
async function getEntry (legacyId, userId) {
const connection = await helper.getInformixConnection()
let result = null
try {
result = await connection.queryAsync(util.format(QUERY_GET_ENTRY, userId, legacyId))
} catch (e) {
logger.error(`Error in 'getEntry' ${e}`)
throw e
} finally {
await connection.closeAsync()
}
return result
}

/**
* Disable timeline notifications
* @param {Number} legacyId the legacy challenge ID
* @param {String} userId the userId
*/
async function disableTimelineNotifications (legacyId, userId) {
const connection = await helper.getInformixConnection()
let result = null
try {
await connection.beginTransactionAsync()
const [existing] = await getEntry(legacyId, userId)
if (existing) {
const query = await prepare(connection, QUERY_DELETE)
result = await query.executeAsync([userId, legacyId])
}
await connection.commitTransactionAsync()
} catch (e) {
logger.error(`Error in 'disableTimelineNotifications' ${e}, rolling back transaction`)
await connection.rollbackTransactionAsync()
throw e
} finally {
await connection.closeAsync()
}
return result
}

module.exports = {
getEntry,
disableTimelineNotifications
}
84 changes: 84 additions & 0 deletions src/services/selfServiceReviewerService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* Number of reviewers Service
* Interacts with InformixDB
*/
const util = require('util')
const logger = require('../common/logger')
const helper = require('../common/helper')

const QUERY_GET_ENTRY = 'SELECT parameter FROM phase_criteria WHERE project_phase_id = %d'
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)'
const QUERY_UPDATE = 'UPDATE phase_criteria SET parameter = ?, modify_user = ?, modify_date = CURRENT WHERE project_phase_id = ?'
const QUERY_DELETE = 'DELETE FROM phase_criteria WHERE project_phase_id = ?'

/**
* Prepare Informix statement
* @param {Object} connection the Informix connection
* @param {String} sql the sql
* @return {Object} Informix statement
*/
async function prepare (connection, sql) {
// logger.debug(`Preparing SQL ${sql}`)
const stmt = await connection.prepareAsync(sql)
return Promise.promisifyAll(stmt)
}

/**
* Get entry
* @param {Number} phaseId the phase ID
*/
async function getEntry (phaseId) {
// logger.debug(`Getting Groups for Challenge ${challengeLegacyId}`)
const connection = await helper.getInformixConnection()
let result = null
try {
result = await connection.queryAsync(util.format(QUERY_GET_ENTRY, phaseId))
} catch (e) {
logger.error(`Error in 'getEntry' ${e}`)
throw e
} finally {
await connection.closeAsync()
}
return result
}

/**
* Enable timeline notifications
* @param {Number} phaseId the legacy challenge ID
* @param {Number} typeId the type ID
* @param {Any} value the value
* @param {String} createdBy the created by
*/
async function createOrSetNumberOfReviewers (phaseId, value, createdBy) {
const connection = await helper.getInformixConnection()
let result = null
try {
await connection.beginTransactionAsync()
const [existing] = await getEntry(phaseId)
if (existing) {
if (value) {
const query = await prepare(connection, QUERY_UPDATE)
result = await query.executeAsync([value, createdBy, phaseId])
} else {
const query = await prepare(connection, QUERY_DELETE)
result = await query.executeAsync([phaseId, value])
}
} else {
const query = await prepare(connection, QUERY_CREATE)
result = await query.executeAsync([phaseId, value, createdBy, createdBy])
}
await connection.commitTransactionAsync()
} catch (e) {
logger.error(`Error in 'createOrSetNumberOfReviewers' ${e}, rolling back transaction`)
await connection.rollbackTransactionAsync()
throw e
} finally {
await connection.closeAsync()
}
return result
}

module.exports = {
getEntry,
createOrSetNumberOfReviewers
}