From d661f9534e8f24b550d655c9911c322846597e39 Mon Sep 17 00:00:00 2001 From: Thomas Kranitsas Date: Thu, 11 Mar 2021 22:21:20 +0200 Subject: [PATCH 1/2] Metadata + user ID fix --- config/default.js | 1 + src/common/helper.js | 31 ++++- src/constants.js | 82 ++++++++++++- src/services/ProcessorService.js | 66 +++++----- src/utils/metadataExtractor.js | 200 +++++++++++++++++++++++++++++++ 5 files changed, 336 insertions(+), 44 deletions(-) create mode 100644 src/utils/metadataExtractor.js diff --git a/config/default.js b/config/default.js index ddca4ad..4a1844b 100644 --- a/config/default.js +++ b/config/default.js @@ -51,6 +51,7 @@ module.exports = { V5_RESOURCES_API_URL: process.env.V5_RESOURCES_API_URL || 'http://localhost:4000/v5/resources', V5_TERMS_API_URL: process.env.V5_TERMS_API_URL || 'http://localhost:4000/v5/terms', V5_RESOURCE_ROLES_API_URL: process.env.V5_RESOURCE_ROLES_API_URL || 'http://localhost:4000/v5/resource-roles', + MEMBER_API_URL: process.env.MEMBER_API_URL || 'https://api.topcoder-dev.com/v5/members', V5_CHALLENGE_TYPE_API_URL: process.env.V5_CHALLENGE_TYPE_API_URL || 'http://localhost:4000/v5/challenge-types', V4_CHALLENGE_TYPE_API_URL: process.env.V4_CHALLENGE_TYPE_API_URL || 'http://localhost:4000/v4/challenge-types', diff --git a/src/common/helper.js b/src/common/helper.js index 3b8d1f5..281a377 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -162,6 +162,34 @@ async function forceV4ESFeeder (legacyId) { await request.put(`${config.V4_ES_FEEDER_API_URL}`).send(body).set({ Authorization: `Bearer ${token}` }) } +/** + * Get the member ID by handle + * @param {String} handle the handle + */ +async function getMemberIdByHandle (handle) { + const m2mToken = await getM2MToken() + let memberId + try { + const res = await getRequest(`${config.MEMBER_API_URL}/${handle}`, m2mToken) + if (_.get(res, 'body.userId')) { + memberId = res.body.userId + } + // handle return from v3 API, handle and memberHandle are the same under case-insensitive condition + handle = _.get(res, 'body.handle') + } catch (error) { + // re-throw all error except 404 Not-Founded, BadRequestError should be thrown if 404 occurs + if (error.status !== 404) { + throw error + } + } + + if (_.isUndefined(memberId)) { + throw new Error(`User with handle: ${handle} doesn't exist`) + } + + return memberId +} + module.exports = { getInformixConnection, getKafkaOptions, @@ -171,5 +199,6 @@ module.exports = { putRequest, postRequest, postBusEvent, - forceV4ESFeeder + forceV4ESFeeder, + getMemberIdByHandle } diff --git a/src/constants.js b/src/constants.js index 7fe0b58..fb1d937 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,6 +1,7 @@ /** * constants */ +const metadataExtractor = require('./utils/metadataExtractor') const prizeSetTypes = { ChallengePrizes: 'placement', @@ -57,12 +58,81 @@ const prizeTypesIds = { } const supportedMetadata = { - allowStockArt: 52, - drPoints: 30, - submissionViewable: 53, - submissionLimit: 51, - codeRepo: 85, - environment: 84 + 32: { + method: metadataExtractor.extractBillingProject, + defaultValue: null, + description: 'Billing Project' + }, + 30: { + method: metadataExtractor.extractDrPoints, + defaultValue: 0, + description: 'DR points' + }, + 35: { + method: metadataExtractor.extractSpecReviewCost, + defaultValue: null, + description: 'Spec review cost' + }, + 41: { + method: metadataExtractor.extractApprovalRequired, + defaultValue: true, + description: 'Approval Required' + }, + 44: { + method: metadataExtractor.extractPostMortemRequired, + defaultValue: true, + description: 'Post-Mortem Required' + }, + 48: { + method: metadataExtractor.extractTrackLateDeliverablesRequired, + defaultValue: true, + description: 'Track Late Deliverables' + }, + 51: { + method: metadataExtractor.extractSubmissionLimit, + defaultValue: null, + description: 'Maximum submissions' + }, + 52: { + method: metadataExtractor.extractAllowStockArtRequired, + defaultValue: false, + description: 'Allow Stock Art' + }, + 53: { + method: metadataExtractor.extractSubmissionViewable, + defaultValue: false, + description: 'Viewable Submissions Flag' + }, + 59: { + method: metadataExtractor.extractReviewFeedback, + defaultValue: true, + description: 'Review Feedback Flag' + }, + 84: { + method: metadataExtractor.extractEnvironment, + defaultValue: null, + description: 'Environment' + }, + 85: { + method: metadataExtractor.extractCodeRepo, + defaultValue: null, + description: 'Code repo' + }, + 88: { + method: metadataExtractor.extractEstimateEffortHours, + defaultValue: 0, + description: 'Effort Hours Estimate' + }, + 89: { + method: metadataExtractor.extractEstimateEffortOffshore, + defaultValue: 0, + description: 'Estimate Effort Days offshore' + }, + 90: { + method: metadataExtractor.extractEstimateEffortOnsite, + defaultValue: 0, + description: 'Estimate Effort Days Onsite' + } } module.exports = { diff --git a/src/services/ProcessorService.js b/src/services/ProcessorService.js index 72d2330..ae6a77b 100644 --- a/src/services/ProcessorService.js +++ b/src/services/ProcessorService.js @@ -496,6 +496,8 @@ async function processCreate (message) { } const m2mToken = await helper.getM2MToken() + const createdByUserId = await helper.getMemberIdByHandle(_.get(message, 'payload.createdBy')) + const updatedByUserId = await helper.getMemberIdByHandle(_.get(message, 'payload.updatedBy') || _.get(message, 'payload.createdBy')) const saveDraftContestDTO = await parsePayload(message.payload, m2mToken) logger.debug('Parsed Payload', saveDraftContestDTO) @@ -514,7 +516,7 @@ async function processCreate (message) { await helper.forceV4ESFeeder(newChallenge.body.result.content.id) await associateChallengeGroups(saveDraftContestDTO.groupsToBeAdded, saveDraftContestDTO.groupsToBeDeleted, newChallenge.body.result.content.id) // await associateChallengeTerms(saveDraftContestDTO.termsToBeAdded, saveDraftContestDTO.termsToBeRemoved, newChallenge.body.result.content.id) - await setCopilotPayment(challengeUuid, newChallenge.body.result.content.id, _.get(message, 'payload.prizeSets'), _.get(message, 'payload.createdBy'), _.get(message, 'payload.updatedBy'), m2mToken) + await setCopilotPayment(challengeUuid, newChallenge.body.result.content.id, _.get(message, 'payload.prizeSets'), createdByUserId, updatedByUserId, m2mToken) await helper.patchRequest(`${config.V5_CHALLENGE_API_URL}/${challengeUuid}`, { legacy: { ...message.payload.legacy, @@ -528,7 +530,7 @@ async function processCreate (message) { }, m2mToken) // 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(newChallenge.body.result.content.id, _.get(message, 'payload.createdBy')) + await timelineService.enableTimelineNotifications(newChallenge.body.result.content.id, createdByUserId) logger.debug('End of processCreate') return newChallenge.body.result.content.id } catch (e) { @@ -603,6 +605,9 @@ async function processUpdate (message) { } const m2mToken = await helper.getM2MToken() + const createdByUserId = await helper.getMemberIdByHandle(_.get(message, 'payload.createdBy')) + const updatedByUserId = await helper.getMemberIdByHandle(_.get(message, 'payload.updatedBy') || _.get(message, 'payload.createdBy')) + let challenge try { // ensure challenge existed @@ -640,41 +645,28 @@ async function processUpdate (message) { logger.debug(JSON.stringify(saveDraftContestDTO, null, 2)) // logger.debug('Parsed Payload', saveDraftContestDTO) try { - try { - if (challenge) { - await helper.putRequest(`${config.V4_CHALLENGE_API_URL}/${legacyId}`, { param: _.omit(saveDraftContestDTO, ['groupsToBeAdded', 'groupsToBeDeleted']) }, m2mToken) - } - } catch (e) { - logger.warn('Failed to update the challenge via the V4 API') - logger.error(e) - } - - // Update metadata in IFX - if (message.payload.metadata && message.payload.metadata.length > 0) { - for (const metadataKey of _.keys(constants.supportedMetadata)) { - const entry = _.find(message.payload.metadata, meta => meta.name === metadataKey) - if (entry) { - if (metadataKey === 'submissionLimit') { - // data here is JSON stringified - try { - const parsedEntryValue = JSON.parse(entry.value) - if (parsedEntryValue.limit) { - entry.value = parsedEntryValue.count - } else { - entry.value = null - } - } catch (e) { - entry.value = null - } - } - try { - await metadataService.createOrUpdateMetadata(legacyId, constants.supportedMetadata[metadataKey], entry.value, _.get(message, 'payload.updatedBy') || _.get(message, 'payload.createdBy')) - } catch (e) { - logger.warn(`Failed to set ${metadataKey} (${constants.supportedMetadata[metadataKey]})`) - } + // extract metadata from challenge and insert into IFX + let metaValue + for (const metadataKey of _.keys(constants.supportedMetadata)) { + try { + metaValue = constants.supportedMetadata[metadataKey].method(message.payload, constants.supportedMetadata[metadataKey].defaultValue) + if (metaValue !== null && metaValue !== '') { + logger.info(`Setting ${constants.supportedMetadata[metadataKey].description} to ${metaValue}`) + await metadataService.createOrUpdateMetadata(legacyId, metadataKey, metaValue, updatedByUserId) } + } catch (e) { + logger.warn(`Failed to set ${constants.supportedMetadata[metadataKey].description} to ${metaValue}`) } } + // try { + // if (challenge) { + // await helper.putRequest(`${config.V4_CHALLENGE_API_URL}/${legacyId}`, { param: _.omit(saveDraftContestDTO, ['groupsToBeAdded', 'groupsToBeDeleted']) }, m2mToken) + // } + // } catch (e) { + // logger.warn('Failed to update the challenge via the V4 API') + // logger.error(e) + // } + if (message.payload.status && challenge) { // logger.info(`The status has changed from ${challenge.currentStatus} to ${message.payload.status}`) if (message.payload.status === constants.challengeStatuses.Active && challenge.currentStatus !== constants.challengeStatuses.Active) { @@ -703,10 +695,10 @@ async function processUpdate (message) { } else { logger.info('Will skip syncing phases as the challenge is a task...') } - await updateMemberPayments(message.payload.legacyId, message.payload.prizeSets, _.get(message, 'payload.updatedBy') || _.get(message, 'payload.createdBy')) + await updateMemberPayments(message.payload.legacyId, message.payload.prizeSets, updatedByUserId) await associateChallengeGroups(saveDraftContestDTO.groupsToBeAdded, saveDraftContestDTO.groupsToBeDeleted, legacyId) - await associateChallengeTerms(message.payload.terms, legacyId, _.get(message, 'payload.createdBy'), _.get(message, 'payload.updatedBy') || _.get(message, 'payload.createdBy')) - await setCopilotPayment(message.payload.id, legacyId, _.get(message, 'payload.prizeSets'), _.get(message, 'payload.createdBy'), _.get(message, 'payload.updatedBy') || _.get(message, 'payload.createdBy'), m2mToken) + await associateChallengeTerms(message.payload.terms, legacyId, createdByUserId, updatedByUserId) + await setCopilotPayment(message.payload.id, legacyId, _.get(message, 'payload.prizeSets'), createdByUserId, updatedByUserId, m2mToken) try { await helper.forceV4ESFeeder(legacyId) diff --git a/src/utils/metadataExtractor.js b/src/utils/metadataExtractor.js new file mode 100644 index 0000000..0375cb5 --- /dev/null +++ b/src/utils/metadataExtractor.js @@ -0,0 +1,200 @@ +/** + * Metadata extractor + */ +const _ = require('lodash') + +/** +* Get metadata entry by key +* @param {Array} metadata the metadata array +* @param {String} key the metadata key +*/ +const getMeta = (metadata = [], key) => _.find(metadata, meta => meta.name === key) + +/** +* Extract billing project +* @param {Object} challenge the challenge object +* @param {Any} defaultValue the default value +*/ +function extractBillingProject (challenge, defaultValue) { + return _.get(challenge, 'billingAccountId', _.get(challenge, 'billing.billingAccountId', _.toString(defaultValue))) +} + +/** +* Extract submission limit +* @param {Object} challenge the challenge object +* @param {Any} defaultValue the default value +*/ +function extractSubmissionLimit (challenge, defaultValue) { + const entry = getMeta(challenge.metadata, 'submissionLimit') + if (!entry) return _.toString(defaultValue) + try { + const parsedEntryValue = JSON.parse(entry.value) + if (parsedEntryValue.limit) { + entry.value = parsedEntryValue.count + } else { + entry.value = null + } + } catch (e) { + entry.value = null + } + return _.toString(entry.value || defaultValue) +} + +/** +* Extract spec review cost +* @param {Object} challenge the challenge object +* @param {Any} defaultValue the default value +*/ +function extractSpecReviewCost (challenge, defaultValue) { + return _.get(_.find(_.get(challenge, 'prizeSets', []), p => p.type === 'specReviewer') || {}, 'prizes[0].value', _.toString(defaultValue)) +} + +/** +* Extract DR points +* @param {Object} challenge the challenge object +* @param {Any} defaultValue the default value +*/ +function extractDrPoints (challenge, defaultValue) { + const entry = getMeta(challenge.metadata, 'drPoints') + if (!entry) return _.toString(defaultValue) + return _.toString(entry.value || defaultValue) +} + +/** +* Extract Approval required +* @param {Object} challenge the challenge object +* @param {Any} defaultValue the default value +*/ +function extractApprovalRequired (challenge, defaultValue) { + const entry = getMeta(challenge.metadata, 'approvalRequired') + if (!entry) return _.toString(defaultValue) + return _.toString(entry.value) +} + +/** +* Extract Post-mortem required +* @param {Object} challenge the challenge object +* @param {Any} defaultValue the default value +*/ +function extractPostMortemRequired (challenge, defaultValue) { + const entry = getMeta(challenge.metadata, 'postMortemRequired') + if (!entry) return _.toString(defaultValue) + return _.toString(entry.value) +} + +/** +* Extract track late deliverables required +* @param {Object} challenge the challenge object +* @param {Any} defaultValue the default value +*/ +function extractTrackLateDeliverablesRequired (challenge, defaultValue) { + const entry = getMeta(challenge.metadata, 'trackLateDeliverables') + if (!entry) return _.toString(defaultValue) + return _.toString(entry.value) +} + +/** +* Extract allow stock art required +* @param {Object} challenge the challenge object +* @param {Any} defaultValue the default value +*/ +function extractAllowStockArtRequired (challenge, defaultValue) { + const entry = getMeta(challenge.metadata, 'allowStockArt') + if (!entry) return _.toString(defaultValue) + return _.toString(entry.value) +} + +/** +* Extract submission viewable +* @param {Object} challenge the challenge object +* @param {Any} defaultValue the default value +*/ +function extractSubmissionViewable (challenge, defaultValue) { + const entry = getMeta(challenge.metadata, 'submissionViewable') + if (!entry) return _.toString(defaultValue) + return _.toString(entry.value) +} + +/** +* Extract review feedback +* @param {Object} challenge the challenge object +* @param {Any} defaultValue the default value +*/ +function extractReviewFeedback (challenge, defaultValue) { + const entry = getMeta(challenge.metadata, 'reviewFeedback') + if (!entry) return _.toString(defaultValue) + return _.toString(entry.value) +} + +/** +* Extract environment +* @param {Object} challenge the challenge object +* @param {Any} defaultValue the default value +*/ +function extractEnvironment (challenge, defaultValue) { + const entry = getMeta(challenge.metadata, 'environment') + if (!entry) return _.toString(defaultValue) + return _.toString(entry.value) +} + +/** +* Extract code repo +* @param {Object} challenge the challenge object +* @param {Any} defaultValue the default value +*/ +function extractCodeRepo (challenge, defaultValue) { + const entry = getMeta(challenge.metadata, 'codeRepo') + if (!entry) return _.toString(defaultValue) + return _.toString(entry.value) +} + +/** +* Extract estimate effort hours +* @param {Object} challenge the challenge object +* @param {Any} defaultValue the default value +*/ +function extractEstimateEffortHours (challenge, defaultValue) { + const entry = getMeta(challenge.metadata, 'effortHoursEstimate') + if (!entry) return _.toString(defaultValue) + return _.toNumber(entry.value) +} + +/** +* Extract estimate effort days offshore +* @param {Object} challenge the challenge object +* @param {Any} defaultValue the default value +*/ +function extractEstimateEffortOffshore (challenge, defaultValue) { + const entry = getMeta(challenge.metadata, 'effortHoursOffshore') + if (!entry) return _.toString(defaultValue) + return _.toNumber(entry.value) +} + +/** +* Extract estimate effort days Onsite +* @param {Object} challenge the challenge object +* @param {Any} defaultValue the default value +*/ +function extractEstimateEffortOnsite (challenge, defaultValue) { + const entry = getMeta(challenge.metadata, 'effortHoursOnshore') + if (!entry) return _.toString(defaultValue) + return _.toNumber(entry.value) +} + +module.exports = { + extractBillingProject, + extractSubmissionLimit, + extractSpecReviewCost, + extractDrPoints, + extractApprovalRequired, + extractPostMortemRequired, + extractTrackLateDeliverablesRequired, + extractAllowStockArtRequired, + extractSubmissionViewable, + extractReviewFeedback, + extractEnvironment, + extractCodeRepo, + extractEstimateEffortHours, + extractEstimateEffortOffshore, + extractEstimateEffortOnsite +} From 05c4185ef1a0646ca5b9969252791be4aa44131d Mon Sep 17 00:00:00 2001 From: Thomas Kranitsas Date: Mon, 29 Mar 2021 22:04:37 +0300 Subject: [PATCH 2/2] set the Review Feedback Flag to false --- src/constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constants.js b/src/constants.js index fb1d937..c59b2ae 100644 --- a/src/constants.js +++ b/src/constants.js @@ -105,7 +105,7 @@ const supportedMetadata = { }, 59: { method: metadataExtractor.extractReviewFeedback, - defaultValue: true, + defaultValue: false, description: 'Review Feedback Flag' }, 84: {