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

Commit 45ffc8a

Browse files
committed
Merge branch 'develop'
2 parents 67b5ff0 + 3a860c5 commit 45ffc8a

File tree

4 files changed

+92
-114
lines changed

4 files changed

+92
-114
lines changed

ReadMe.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,8 @@ The following parameters can be set in config files or in env variables:
4949
- V4_CHALLENGE_API_URL: v4 challenge api url, default value is 'http://localhost:4000/v4/challenges'
5050
- V4_TECHNOLOGIES_API_URL: v4 technologies api url, default value is 'http://localhost:4000/v4/technologies'
5151
- V4_PLATFORMS_API_URL: v4 platforms api url, default value is 'http://localhost:4000/v4/platforms'
52-
- TASK_TYPE_IDS: The v5 Type ID for tasks per track
53-
- CHALLENGE_TYPE_ID: The v5 Type ID for regular challenges
54-
- FIRST_2_FINISH_TYPE_ID: The v5 Type ID for F2F challenges
55-
52+
- V5_CHALLENGE_MIGRATION_API_URL: v5 challenge migration API URL, default value is https://api.topcoder-dev.com/v5/challenge-migration
53+
- V5_GROUPS_API_URL: v5 groups API URL, default value is https://api.topcoder-dev.com/v5/groups
5654
There is a `/health` endpoint that checks for the health of the app. This sets up an expressjs server and listens on the environment variable `PORT`. It's not part of the configuration file and needs to be passed as an environment variable
5755

5856
Configuration for the tests is at `config/test.js`, only add such new configurations different from `config/default.js`

config/default.js

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,12 @@ module.exports = {
3737
V4_TECHNOLOGIES_API_URL: process.env.V4_TECHNOLOGIES_API_URL || 'http://localhost:4000/v4/technologies',
3838
V4_PLATFORMS_API_URL: process.env.V4_PLATFORMS_API_URL || 'http://localhost:4000/v4/platforms',
3939
V5_PROJECTS_API_URL: process.env.V5_PROJECTS_API_URL || 'https://api.topcoder-dev.com/v5/projects',
40+
V5_CHALLENGE_MIGRATION_API_URL: process.env.V5_CHALLENGE_MIGRATION_API_URL || 'https://api.topcoder-dev.com/v5/challenge-migration',
41+
42+
V5_GROUPS_API_URL: process.env.V5_GROUPS_API_URL || 'https://api.topcoder-dev.com/v5/groups',
4043

4144
// PHASE IDs
4245
REGISTRATION_PHASE_ID: process.env.REGISTRATION_PHASE_ID || 'a93544bc-c165-4af4-b55e-18f3593b457a',
4346
SUBMISSION_PHASE_ID: process.env.SUBMISSION_PHASE_ID || '6950164f-3c5e-4bdc-abc8-22aaf5a1bd49',
44-
CHECKPOINT_SUBMISSION_PHASE_ID: process.env.CHECKPOINT_SUBMISSION_PHASE_ID || 'd8a2cdbe-84d1-4687-ab75-78a6a7efdcc8',
45-
46-
// challenge types
47-
TASK_TYPE_ID: process.env.TASK_TYPE_ID || 'e885273d-aeda-42c0-917d-bfbf979afbba',
48-
TASK_TYPE_IDS: {
49-
DEVELOP: process.env.DEVELOP_TASK_TYPE_ID || 'e885273d-aeda-42c0-917d-bfbf979afbba',
50-
DESIGN: process.env.DESIGN_TASK_TYPE_ID || '149a2013-92b9-4ca9-b35d-c337d47a2490',
51-
QA: process.env.QA_TASK_TYPE_ID || 'a91e69fd-6240-4227-8484-66b8defc4ca9',
52-
DATA_SCENCE: process.env.DATA_SCENCE_TASK_TYPE_ID || 'b3b60e22-e302-4db8-bef8-4eaff965565f'
53-
},
54-
CHALLENGE_TYPE_ID: process.env.CHALLENGE_TYPE_ID || '94eee466-9255-4b60-88d8-4f59c1810dd0',
55-
FIRST_2_FINISH_TYPE_ID: process.env.FIRST_2_FINISH_TYPE_ID || '6950164f-3c5e-4bdc-abc8-22aaf5a1bd49'
47+
CHECKPOINT_SUBMISSION_PHASE_ID: process.env.CHECKPOINT_SUBMISSION_PHASE_ID || 'd8a2cdbe-84d1-4687-ab75-78a6a7efdcc8'
5648
}

src/constants.js

Lines changed: 1 addition & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
const _ = require('lodash')
2-
const config = require('config')
3-
41
/**
52
* constants
63
*/
@@ -48,47 +45,10 @@ const challengeStatuses = {
4845
CancelledZeroRegistrations: 'Cancelled - Zero Registrations'
4946
}
5047

51-
const challengeTracks = {
52-
DEVELOP: 'DEVELOP',
53-
DESIGN: 'DESIGN',
54-
DATA_SCIENCE: 'DATA_SCIENCE',
55-
QA: 'QA'
56-
}
57-
58-
const challengeAbbreviations = {
59-
TASK: 'TASK',
60-
FIRST_2_FINISH: 'FIRST_2_FINISH',
61-
DESIGN_FIRST_2_FINISH: 'DESIGN_FIRST_2_FINISH',
62-
CODE: 'CODE',
63-
APPLICATION_FRONT_END_DESIGN: 'APPLICATION_FRONT_END_DESIGN'
64-
}
65-
66-
const legacySubTrackMapping = {
67-
[_.toLower(challengeTracks.DEVELOP)]: {
68-
[config.TASK_TYPE_IDS.DEVELOP]: challengeAbbreviations.FIRST_2_FINISH,
69-
[config.CHALLENGE_TYPE_ID]: challengeAbbreviations.CODE,
70-
[config.FIRST_2_FINISH_TYPE_ID]: challengeAbbreviations.FIRST_2_FINISH
71-
},
72-
[_.toLower(challengeTracks.DESIGN)]: {
73-
[config.TASK_TYPE_IDS.DESIGN]: challengeAbbreviations.DESIGN_FIRST_2_FINISH,
74-
[config.CHALLENGE_TYPE_ID]: challengeAbbreviations.APPLICATION_FRONT_END_DESIGN,
75-
[config.FIRST_2_FINISH_TYPE_ID]: challengeAbbreviations.DESIGN_FIRST_2_FINISH
76-
},
77-
[_.toLower(challengeTracks.QA)]: {
78-
[config.TASK_TYPE_IDS.QA]: challengeAbbreviations.FIRST_2_FINISH
79-
},
80-
[_.toLower(challengeTracks.DATA_SCIENCE)]: {
81-
[config.TASK_TYPE_IDS.DATA_SCIENCE]: challengeAbbreviations.FIRST_2_FINISH
82-
}
83-
}
84-
8548
module.exports = {
8649
prizeSetTypes,
8750
EVENT_ORIGINATOR,
8851
EVENT_MIME_TYPE,
8952
createChallengeStatusesMap,
90-
challengeStatuses,
91-
challengeAbbreviations,
92-
challengeTracks,
93-
legacySubTrackMapping
53+
challengeStatuses
9454
}

src/services/ProcessorService.js

Lines changed: 85 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,19 @@ const config = require('config')
1010
const logger = require('../common/logger')
1111
const helper = require('../common/helper')
1212
const constants = require('../constants')
13-
const showdown = require('showdown')
14-
const converter = new showdown.Converter()
13+
// TODO: Remove this
14+
// const showdown = require('showdown')
15+
// const converter = new showdown.Converter()
16+
17+
/**
18+
* Get group information by V5 UUID
19+
* @param {String} v5GroupId the v5 group UUID
20+
* @param {String} m2mToken token for accessing the API
21+
*/
22+
async function getGroup (v5GroupId, m2mToken) {
23+
const response = await helper.getRequest(`${config.V5_GROUPS_API_URL}/${v5GroupId}`, m2mToken)
24+
return response.body
25+
}
1526

1627
/**
1728
* Get technologies from V4 API
@@ -56,46 +67,36 @@ async function getDirectProjectId (m2mToken, projectId) {
5667
}
5768

5869
/**
59-
* Get legacy challenge track and subTrack values based on the v5 legacy.track and typeId
60-
* @param {String} legacyTrack the legacy.track value from the v5 challenge object
70+
* Get legacy challenge track and subTrack values based on the v5 trackId, typeId and tags
71+
* @param {String} trackId the V5 track ID
6172
* @param {String} typeId the v5 type ID
73+
* @param {Array<String>} tags the v5 tags
6274
* @param {String} m2mToken the M2M token
6375
*/
64-
async function getLegacyTrackInformation (legacyTrack, typeId, m2mToken) {
65-
const data = {
66-
track: _.toUpper(legacyTrack)
67-
}
68-
if (_.isUndefined(data.track)) {
69-
throw new Error(`Cannot create a challenge without a track. Please use one of [${_.values(constants.challengeTracks).join(', ')}]`)
76+
async function getLegacyTrackInformation (trackId, typeId, tags, m2mToken) {
77+
if (_.isUndefined(trackId)) {
78+
throw new Error('Cannot create a challenge without a trackId.')
7079
}
7180
if (_.isUndefined(typeId)) {
7281
throw new Error('Cannot create a challenge without a typeId.')
7382
}
74-
75-
// Use the configured subTrack if set for the given track/typeId
76-
if (constants.legacySubTrackMapping[_.toLower(legacyTrack)] && constants.legacySubTrackMapping[_.toLower(legacyTrack)][typeId]) {
77-
data.subTrack = constants.legacySubTrackMapping[_.toLower(legacyTrack)][typeId]
78-
} else {
79-
// otherwise fetch v4 challenge type based on the v5 type.legacyId
80-
const v5Type = await helper.getRequest(`${config.V5_CHALLENGE_TYPE_API_URL}/${typeId}`, m2mToken)
81-
const v4TypeList = await helper.getRequest(`${config.V4_CHALLENGE_TYPE_API_URL}`, m2mToken)
82-
const v4Type = _.find(_.get(v4TypeList, 'body.result.content', []), type => type.id === v5Type.body.legacyId)
83-
if (!v4Type) {
84-
throw new Error(`There is no mapping between v5 Type ${v5Type.body.name} and V4`)
85-
}
86-
data.subTrack = v4Type.subTrack
87-
data.legacyTypeId = v5Type.body.legacyId
88-
}
89-
90-
// If it's a private task, set the `task` property to `true`
91-
if (_.values(config.TASK_TYPE_IDS).includes(typeId)) {
92-
// Tasks can only be created for the develop and design tracks so we're setting the track for QA/DS to DEVELOP
93-
if (data.track === constants.challengeTracks.QA || data.track !== constants.challengeTracks.DATA_SCIENCE) {
94-
data.track = constants.challengeTracks.DEVELOP
83+
const query = [
84+
`trackId=${trackId}`,
85+
`typeId=${typeId}`
86+
]
87+
_.each((tags || []), (tag) => {
88+
query.push(`tags[]=${tag}`)
89+
})
90+
try {
91+
const res = await helper.getRequest(`${config.V5_CHALLENGE_MIGRATION_API_URL}/convert-to-v4?${query.join('&')}`, m2mToken)
92+
return {
93+
track: res.body.track,
94+
subTrack: res.body.subTrack,
95+
...(res.body.isTask ? { task: true } : {})
9596
}
96-
data.task = true
97+
} catch (e) {
98+
throw new Error(_.get(e, 'message', 'Failed to get V4 track/subTrack information'))
9799
}
98-
return data
99100
}
100101

101102
/**
@@ -115,7 +116,7 @@ async function parsePayload (payload, m2mToken, isCreated = true) {
115116
if (!projectId) throw new Error(`Could not find Direct Project ID for Project ${payload.projectId}`)
116117
}
117118

118-
const legacyTrackInfo = await getLegacyTrackInformation(_.get(payload, 'legacy.track'), payload.typeId, m2mToken)
119+
const legacyTrackInfo = await getLegacyTrackInformation(payload.trackId, payload.typeId, payload.tags, m2mToken)
119120

120121
const data = {
121122
...legacyTrackInfo,
@@ -140,20 +141,15 @@ async function parsePayload (payload, m2mToken, isCreated = true) {
140141
data.submissionVisibility = true
141142
data.milestoneId = 1
142143
}
143-
if (payload.description) {
144-
try {
145-
data.detailedRequirements = converter.makeHtml(payload.description)
146-
} catch (e) {
147-
data.detailedRequirements = payload.description
148-
}
149-
}
144+
145+
data.detailedRequirements = payload.description
150146
if (payload.privateDescription) {
151-
try {
152-
data.privateDescription = converter.makeHtml(payload.privateDescription)
153-
} catch (e) {
154-
data.privateDescription = payload.privateDescription
155-
}
147+
// don't include the private description as there could be
148+
// info that shouldn't be public. Just identify the v5 challenge id
149+
data.detailedRequirements += '\n\r'
150+
data.detailedRequirements += 'V5 Challenge - Additional Details: ' + payload.id
156151
}
152+
157153
if (payload.phases) {
158154
const registrationPhase = _.find(payload.phases, p => p.phaseId === config.REGISTRATION_PHASE_ID)
159155
const submissionPhase = _.find(payload.phases, p => p.phaseId === config.SUBMISSION_PHASE_ID)
@@ -202,6 +198,22 @@ async function parsePayload (payload, m2mToken, isCreated = true) {
202198
const platResult = await getPlatforms(m2mToken)
203199
data.platforms = _.filter(platResult.result.content, e => payload.tags.includes(e.name))
204200
}
201+
if (payload.groups && _.get(payload, 'groups.length', 0) > 0) {
202+
const legacyGroups = []
203+
for (const group of payload.groups) {
204+
try {
205+
const groupInfo = await getGroup(group, m2mToken)
206+
if (!_.isEmpty(_.get(groupInfo, 'oldId'))) {
207+
legacyGroups.push(_.get(groupInfo, 'oldId'))
208+
}
209+
} catch (e) {
210+
logger.warn(`Failed to load details for group ${group}`)
211+
}
212+
}
213+
if (legacyGroups.length > 0) {
214+
data.groupIds = legacyGroups
215+
}
216+
}
205217
return data
206218
} catch (err) {
207219
// Debugging
@@ -256,9 +268,11 @@ async function processCreate (message) {
256268
await helper.patchRequest(`${config.V5_CHALLENGE_API_URL}/${challengeUuid}`, {
257269
legacy: {
258270
...message.payload.legacy,
271+
track: saveDraftContestDTO.track,
272+
subTrack: saveDraftContestDTO.subTrack,
273+
isTask: saveDraftContestDTO.task || false,
259274
directProjectId: newChallenge.body.result.content.projectId,
260-
forumId: _.get(newChallenge, 'body.result.content.forumId', message.payload.legacy.forumId),
261-
informixModified: _.get(newChallenge, 'body.result.content.updatedAt', new Date())
275+
forumId: _.get(newChallenge, 'body.result.content.forumId', message.payload.legacy.forumId)
262276
},
263277
legacyId: newChallenge.body.result.content.id
264278
}, m2mToken)
@@ -277,14 +291,20 @@ processCreate.schema = {
277291
'mime-type': Joi.string().required(),
278292
payload: Joi.object().keys({
279293
id: Joi.string().required(),
280-
typeId: Joi.string(),
294+
typeId: Joi.string().required(),
295+
trackId: Joi.string().required(),
281296
legacy: Joi.object().keys({
282297
track: Joi.string().required(),
283298
reviewType: Joi.string().required(),
284299
confidentialityType: Joi.string(),
285300
directProjectId: Joi.number(),
286301
forumId: Joi.number().integer().positive()
287302
}).unknown(true),
303+
task: Joi.object().keys({
304+
isTask: Joi.boolean().default(false),
305+
isAssigned: Joi.boolean().default(false),
306+
memberId: Joi.string().allow(null)
307+
}),
288308
billingAccountId: Joi.number(),
289309
name: Joi.string().required(),
290310
description: Joi.string(),
@@ -303,7 +323,8 @@ processCreate.schema = {
303323
projectId: Joi.number().integer().positive().required(),
304324
copilotId: Joi.number().integer().positive().optional(),
305325
status: Joi.string().valid(_.values(Object.keys(constants.createChallengeStatusesMap))).required(),
306-
startDate: Joi.date(),
326+
groups: Joi.array().items(Joi.string()),
327+
startDate: Joi.date()
307328
}).unknown(true).required()
308329
}).required()
309330
}
@@ -343,6 +364,7 @@ async function processUpdate (message) {
343364
if (!challenge) {
344365
throw new Error(`Could not find challenge ${message.payload.legacyId}`)
345366
}
367+
346368
await helper.putRequest(`${config.V4_CHALLENGE_API_URL}/${message.payload.legacyId}`, { param: saveDraftContestDTO }, m2mToken)
347369

348370
if (message.payload.status) {
@@ -355,16 +377,16 @@ async function processUpdate (message) {
355377
if (message.payload.status === constants.challengeStatuses.Completed && challenge.currentStatus !== constants.challengeStatuses.Completed) {
356378
const challengeUuid = message.payload.id
357379
const v5Challenge = await helper.getRequest(`${config.V5_CHALLENGE_API_URL}/${challengeUuid}`, m2mToken)
358-
if (_.values(config.TASK_TYPE_IDS).includes(v5Challenge.body.typeId)) {
359-
logger.info('Challenge type is TASK')
380+
if (v5Challenge.body.task.isTask) {
381+
logger.info('Challenge is a TASK')
360382
if (!message.payload.winners || message.payload.winners.length === 0) {
361383
throw new Error('Cannot close challenge without winners')
362384
}
363385
const winnerId = _.find(message.payload.winners, winner => winner.placement === 1).userId
364386
logger.info(`Will close the challenge with ID ${message.payload.legacyId}. Winner ${winnerId}!`)
365387
await closeChallenge(message.payload.legacyId, winnerId)
366388
} else {
367-
logger.info(`Challenge type is ${v5Challenge.body.typeId}.. Skip closing challenge...`)
389+
logger.info('Challenge type is not a task.. Skip closing challenge...')
368390
}
369391
}
370392
}
@@ -397,11 +419,16 @@ processUpdate.schema = {
397419
reviewType: Joi.string().required(),
398420
confidentialityType: Joi.string(),
399421
directProjectId: Joi.number(),
400-
forumId: Joi.number().integer().positive(),
401-
informixModified: Joi.string()
422+
forumId: Joi.number().integer().positive()
402423
}).unknown(true),
424+
task: Joi.object().keys({
425+
isTask: Joi.boolean().default(false),
426+
isAssigned: Joi.boolean().default(false),
427+
memberId: Joi.string().allow(null)
428+
}),
403429
billingAccountId: Joi.number(),
404-
typeId: Joi.string(),
430+
typeId: Joi.string().required(),
431+
trackId: Joi.string().required(),
405432
name: Joi.string(),
406433
description: Joi.string(),
407434
privateDescription: Joi.string(),
@@ -417,6 +444,7 @@ processUpdate.schema = {
417444
}).unknown(true)).min(1),
418445
tags: Joi.array().items(Joi.string().required()).min(1), // tag names
419446
projectId: Joi.number().integer().positive().allow(null),
447+
groups: Joi.array().items(Joi.string()),
420448
startDate: Joi.date()
421449
}).unknown(true).required()
422450
}).required()
@@ -427,4 +455,4 @@ module.exports = {
427455
processUpdate
428456
}
429457

430-
logger.buildService(module.exports)
458+
// logger.buildService(module.exports)

0 commit comments

Comments
 (0)