diff --git a/config/default.js b/config/default.js index d62967a..e7290c8 100644 --- a/config/default.js +++ b/config/default.js @@ -68,7 +68,7 @@ module.exports = { GRANT_TYPE: 'client_credentials', // used as base to construct various URLs - WEBSITE_SECURE: process.env.WEBSITE_SECURE || 'https://topcoderx.topcoder-dev.com', + WEBSITE_SECURE: process.env.WEBSITE_SECURE || 'http://topcoderx.topcoder-dev.com', ROLE_ID_COPILOT: process.env.ROLE_ID_COPILOT || 'cfe12b3f-2a24-4639-9d8b-ec86726f76bd', ROLE_ID_ITERATIVE_REVIEWER: process.env.ROLE_ID_ITERATIVE_REVIEWER || 'f6df7212-b9d6-4193-bfb1-b383586fce63', @@ -76,6 +76,7 @@ module.exports = { TYPE_ID_TASK: process.env.TYPE_ID_TASK || 'ecd58c69-238f-43a4-a4bb-d172719b9f31', DEFAULT_TIMELINE_TEMPLATE_ID: process.env.DEFAULT_TIMELINE_TEMPLATE_ID || '53a307ce-b4b3-4d6f-b9a1-3741a58f77e6', DEFAULT_TRACK_ID: process.env.DEFAULT_TRACK_ID || '9b6fc876-f4d9-4ccb-9dfd-419247628825', + WORK_TYPE_ID: process.env.WORK_TYPE_ID || 'b658b280-6c4d-11e5-9d70-22000b2c9aef', GITLAB_ACCESS_TOKEN_DEFAULT_EXPIRATION: 3600 * 2, GITLAB_REFRESH_TOKEN_BEFORE_EXPIRATION: 300, GITLAB_CLIENT_ID: process.env.GITLAB_CLIENT_ID, diff --git a/models/Project.js b/models/Project.js index d2d1499..caf4d67 100755 --- a/models/Project.js +++ b/models/Project.js @@ -17,7 +17,7 @@ const Schema = dynamoose.Schema; * @property {String} id The id. * @property {String} title The title. * @property {Number} tcDirectId The tc direct id. - * @property {String} tags The tags. + * @property {Array<{id: string, name: string}>} tags The tags. * @property {String} rocketChatWebhook The rocket chat webhook. * @property {String} rocketChatChannelName The rocket chat channel name. * @property {String} archived The archived. @@ -41,9 +41,33 @@ const schema = new Schema({ required: true }, tags: { - type: String, + type: 'list', + list: [{ + type: 'map', + map: { + id: {type: String, required: true}, + name: {type: String, required: true} + } + }], required: true, - default: '' + default: [], + fromDynamo(value) { + if (value.S) { + return value.S; + } + if (value.L) { + return value.L.map((item) => { + if (item.M && item.M.name && item.M.id) { + return { + id: item.M.id.S, + name: item.M.name.S + }; + } + return null; + }); + } + return []; + } }, rocketChatWebhook: {type: String, required: false}, rocketChatChannelName: {type: String, required: false}, diff --git a/services/ChallengeService.js b/services/ChallengeService.js index ae4dd22..93fca2e 100644 --- a/services/ChallengeService.js +++ b/services/ChallengeService.js @@ -20,7 +20,7 @@ const dbHelper = require('../utils/db-helper'); * @param {Object} event the event */ async function handleChallengeTagsUpdate(event) { - const tags = event.data.tags.split(','); + const tags = event.data.tags.map((tag) => tag.name); await Promise.all( event.data.challengeUUIDsList.map(async (challengeUUIDs) => { if (_.isString(challengeUUIDs)) { // repoUrl @@ -54,7 +54,12 @@ process.schema = Joi.object().keys({ challengeUUIDsList: Joi.array().items( Joi.alternatives().try(Joi.string(), Joi.array().items(Joi.string())) ).required(), - tags: Joi.string().required() + tags: Joi.array().items( + Joi.object().keys({ + id: Joi.string().required(), + name: Joi.string().required() + }) + ).required() }).required(), retryCount: Joi.number().integer().default(0).optional() }); diff --git a/services/CopilotPaymentService.js b/services/CopilotPaymentService.js index f828244..944d50e 100644 --- a/services/CopilotPaymentService.js +++ b/services/CopilotPaymentService.js @@ -194,7 +194,7 @@ async function handlePaymentAdd(event, payment) { const newChallenge = { name: challengeTitle, projectId: project.tcDirectId, - tags: !!project.tags ? project.tags.split(',') : [], + tags: project.tags ? project.tags.map((tag) => tag.name) : [], detailedRequirements: challengeRequirements, prizes: [payment.amount], reviewType: 'INTERNAL' @@ -203,6 +203,9 @@ async function handlePaymentAdd(event, payment) { // Create a new challenge const challengeUUID = await topcoderApiHelper.createChallenge(newChallenge); + // Apply skills to the challenge + await topcoderApiHelper.applySkillsSet(challengeUUID, project.tags); + logger.debug(`updating database payment with new challenge id:${challengeUUID}`); // update db payment diff --git a/services/IssueService.js b/services/IssueService.js index a845f6c..4986cc6 100755 --- a/services/IssueService.js +++ b/services/IssueService.js @@ -635,11 +635,14 @@ async function handleIssueCreate(event, issue, forceAssign = false) { issue.challengeUUID = await topcoderApiHelper.createChallenge({ name: issue.title, projectId, - tags: project.tags ? project.tags.split(',') : [], + tags: project.tags ? project.tags.map((tag) => tag.name) : [], detailedRequirements: issue.body, prizes: issue.prizes }); + // Apply skills to the challenge + await topcoderApiHelper.applySkillsSetToChallenge(issue.challengeUUID, project.tags); + // Save // update db payment await dbHelper.update(models.Issue, dbIssue.id, { diff --git a/utils/db-helper.js b/utils/db-helper.js index 2c707b0..a01bde2 100644 --- a/utils/db-helper.js +++ b/utils/db-helper.js @@ -490,6 +490,7 @@ async function acquireLockOnUser(userId, lockId, ttl) { } }); } + /** * Release lock on user * @param {String} id ID of the user diff --git a/utils/logger.js b/utils/logger.js index 473b392..d0f889a 100644 --- a/utils/logger.js +++ b/utils/logger.js @@ -32,7 +32,7 @@ logger.logFullError = function logFullError(err, signature) { if (!err || err.logged) { return; } - logger.error(`Error happened in ${signature}\n${err.stack}`); + logger.error(`Error happened in ${signature}\n${err.stack || err.message}`); err.logged = true; }; diff --git a/utils/topcoder-api-helper.js b/utils/topcoder-api-helper.js index 58b05ca..a57730b 100644 --- a/utils/topcoder-api-helper.js +++ b/utils/topcoder-api-helper.js @@ -76,7 +76,7 @@ async function createChallenge(challenge) { }], timelineTemplateId: config.DEFAULT_TIMELINE_TEMPLATE_ID, projectId: challenge.projectId, - tags: challenge.tags, + tags: challenge.tags.map((tag) => tag.name), trackId: config.DEFAULT_TRACK_ID, legacy: { pureV5Task: true @@ -163,6 +163,41 @@ async function activateChallenge(id) { } } +/** + * Apply skills set to the challenge + * @param {String} challengeId the challenge id + * @param {Array<{id: string, name: string}>} tags the list of tags applied to the challenge + */ +async function applySkillsSetToChallenge(challengeId, tags) { + const apiKey = await getM2Mtoken(); + logger.debug(`Applying skills set to the challenge ${challengeId}`); + const url = `${config.TC_API_URL}/standardized-skills/work-skills`; + const payload = { + workId: challengeId, + workTypeId: config.WORK_TYPE_ID, + skillIds: tags.map((tag) => tag.id) + }; + const params = { + headers: { + authorization: `Bearer ${apiKey}`, + 'Content-Type': 'application/json' + } + }; + try { + const response = await axios.post(url, payload, params); + const statusCode = response.status ? response.status : null; + loggerFile.info(`EndPoint: POST /standardized-skills/work-skills, + POST parameters: ${circularJSON.stringify(payload)}, Status Code:${statusCode}, Response: ${circularJSON.stringify(response.data)}`); + logger.debug(`Skills set applied successfully to the challenge ${challengeId}`); + return response.data; + } catch (err) { + loggerFile.info(`EndPoint: POST /standardized-skills/work-skills, POST parameters: ${circularJSON.stringify(payload)}, Status Code:null, + Error: 'Failed to apply skills set to the challenge.', Details: ${circularJSON.stringify(err)}`); + logger.error(`Response Data: ${JSON.stringify(err.response.data)}`); + throw errors.convertTopcoderApiError(err, 'Failed to apply skills set to the challenge.'); + } +} + /** * Get challenge details by id * @param {String} id challenge ID @@ -511,6 +546,7 @@ module.exports = { createChallenge, updateChallenge, activateChallenge, + applySkillsSetToChallenge, closeChallenge, getProjectBillingAccountId, getTopcoderMemberId,