diff --git a/.circleci/config.yml b/.circleci/config.yml index b49f0029..5ae70314 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -70,9 +70,7 @@ workflows: branches: only: - develop - - fix/challenge-timelines-edit-routes - - test/performance-profile - - July2022Updates + - fix/task-memberId-reset # Production builds are exectuted only on tagged commits to the # master branch. diff --git a/config/default.js b/config/default.js index bd24f029..babe7a5e 100644 --- a/config/default.js +++ b/config/default.js @@ -69,6 +69,9 @@ module.exports = { OBSERVER_ROLE_ID: process.env.OBSERVER_ROLE_ID || '2a4dc376-a31c-4d00-b173-13934d89e286', CLIENT_MANAGER_ROLE_ID: process.env.OBSERVER_ROLE_ID || '9b2f1905-8128-42da-85df-ed64410f4781', + // topgear billing accounts + TOPGEAR_BILLING_ACCOUNTS_ID: process.env.TOPGEAR_BILLING_ACCOUNTS_ID ? process.env.TOPGEAR_BILLING_ACCOUNTS_ID.split(',') : [], + // health check timeout in milliseconds HEALTH_CHECK_TIMEOUT: process.env.HEALTH_CHECK_TIMEOUT || 3000, diff --git a/src/common/helper.js b/src/common/helper.js index c349ecfb..0db4a781 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -889,7 +889,7 @@ async function listChallengesByMember (memberId) { // get search is paginated, we need to get all pages' data let page = 1 while (true) { - let result = {}; + let result = {} try { result = await axios.get(`${config.RESOURCES_API_URL}/${memberId}/challenges`, { headers: { Authorization: `Bearer ${token}` }, diff --git a/src/routes.js b/src/routes.js index 3c778369..b02f79fe 100644 --- a/src/routes.js +++ b/src/routes.js @@ -70,7 +70,7 @@ module.exports = { '/challenges/:challengeId/statistics': { get: { controller: 'ChallengeController', - method: 'getChallengeStatistics', + method: 'getChallengeStatistics' } }, '/challenges/:challengeId/notifications': { diff --git a/src/services/ChallengeService.js b/src/services/ChallengeService.js index ed15a9a7..60a5489f 100644 --- a/src/services/ChallengeService.js +++ b/src/services/ChallengeService.js @@ -274,7 +274,7 @@ async function searchChallenges (currentUser, criteria) { { wildcard: { name: `*${criteria.search}*` } }, { wildcard: { name: `${criteria.search}*` } }, { wildcard: { name: `*${criteria.search}` } }, - { match_phrase: { tags: criteria.search } }, + { match_phrase: { tags: criteria.search } } ] } }) } else { @@ -1450,6 +1450,17 @@ async function update (currentUser, challengeId, data, isFull) { _.set(data, 'billing.billingAccountId', billingAccountId) _.set(data, 'billing.markup', markup || 0) } + if (billingAccountId && _.includes(config.TOPGEAR_BILLING_ACCOUNTS_ID, _.toString(billingAccountId))) { + if (_.isEmpty(data.metadata)) { + data.metadata = [] + } + if (!_.find(data.metadata, e => e.name === 'postMortemRequired')) { + data.metadata.push({ + name: 'postMortemRequired', + value: 'false' + }) + } + } if (data.status) { if (data.status === constants.challengeStatuses.Active) { if (!_.get(challenge, 'legacy.pureV5Task') && !_.get(challenge, 'legacy.pureV5') && _.isUndefined(_.get(challenge, 'legacyId'))) { @@ -1649,8 +1660,51 @@ async function update (currentUser, challengeId, data, isFull) { await validateWinners(data.winners, challengeId) } + // Only m2m tokens are allowed to modify the `task.*` information on a challenge + if (!_.isUndefined(_.get(data, 'task')) && !currentUser.isMachine) { + if (!_.isUndefined(_.get(challenge, 'task'))) { + logger.info(`User ${currentUser.handle || currentUser.sub} is not allowed to modify the task information on challenge ${challengeId}`) + data.task = challenge.task + logger.info(`Task information on challenge ${challengeId} is reset to ${JSON.stringify(challenge.task)}. Original data: ${JSON.stringify(data.task)}`) + } else { + delete data.task + } + } + + // task.memberId goes out of sync due to another processor setting "task.memberId" but subsequent immediate update to the task + // will not have the memberId set. So we need to set it using winners to ensure it is always in sync. The proper fix is to correct + // the sync issue in the processor. However this is quick fix that works since winner.userId is task.memberId. + if (_.get(challenge, 'legacy.pureV5Task') && !_.isUndefined(data.winners)) { + const winnerMemberId = _.get(data.winners, '[0].userId') + logger.info(`Setting task.memberId to ${winnerMemberId} for challenge ${challengeId}. Task ${_.get(data, 'task')} - ${_.get(challenge, 'task')}`) + + if (winnerMemberId != null && _.get(data, 'task.memberId') !== winnerMemberId) { + logger.info(`Task ${challengeId} has a winner ${winnerMemberId}`) + data.task = { + isTask: true, + isAssigned: true, + memberId: winnerMemberId + } + logger.warn(`task.memberId mismatched with winner memberId. task.memberId is updated to ${winnerMemberId}`) + } else { + logger.info(`task ${challengeId} has no winner set yet.`) + } + } else { + logger.info(`${challengeId} is not a pureV5 challenge or has no winners set yet.`) + } + data.updated = moment().utc() data.updatedBy = currentUser.handle || currentUser.sub + const finalMetadata = [...challenge.metadata || []] + _.each(data.metadata || [], (rec) => { + const existingMeta = _.findIndex(finalMetadata, m => m.name === rec.name) + if (existingMeta > -1) { + finalMetadata[existingMeta].value = rec.value + } else { + finalMetadata.push(rec) + } + }) + data.metadata = finalMetadata const updateDetails = {} const auditLogs = [] let phasesHaveBeenModified = false @@ -1709,6 +1763,9 @@ async function update (currentUser, challengeId, data, isFull) { op = '$PUT' } else if (_.isUndefined(challenge[key]) || challenge[key] !== value) { op = '$PUT' + } else if (_.get(challenge, 'legacy.pureV5Task') && key === 'task') { + // always update task for pureV5 challenges + op = '$PUT' } if (op) { @@ -1848,15 +1905,6 @@ async function update (currentUser, challengeId, data, isFull) { const { track, type } = await validateChallengeData(_.pick(challenge, ['trackId', 'typeId'])) - // Only m2m tokens are allowed to modify the `task.*` information on a challenge - if (!_.isUndefined(_.get(data, 'task')) && !currentUser.isMachine) { - if (!_.isUndefined(_.get(challenge, 'task'))) { - data.task = challenge.task - } else { - delete data.task - } - } - if (_.get(type, 'isTask')) { if (!_.isEmpty(_.get(data, 'task.memberId'))) { const challengeResources = await helper.getChallengeResources(challengeId)