From 40c16f9d12348149188922eb95963c5adaa280f6 Mon Sep 17 00:00:00 2001 From: Emre Date: Mon, 27 Mar 2023 13:49:38 +0300 Subject: [PATCH 1/4] fix typo --- src/common/phase-helper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/phase-helper.js b/src/common/phase-helper.js index af23b4bb..e5b5cb7f 100644 --- a/src/common/phase-helper.js +++ b/src/common/phase-helper.js @@ -249,7 +249,7 @@ class ChallengePhaseHelper { if (updatedPhase.name === "Post-Mortem") { updatedPhase.predecessor = "a93544bc-c165-4af4-b55e-18f3593b457a"; } - if (_.undefined(updatedPhase.actualEndDate) && updatedPhase.name !== "Iterative Review") { + if (_.isUndefined(updatedPhase.actualEndDate) && updatedPhase.name !== "Iterative Review") { updatedPhase.duration = _.defaultTo(_.get(newPhase, "duration"), updatedPhase.duration); } if (_.isUndefined(updatedPhase.predecessor)) { From c065c71aa574c88b560c74c0efd7ae2688d6c3bc Mon Sep 17 00:00:00 2001 From: Emre Date: Mon, 27 Mar 2023 18:01:36 +0300 Subject: [PATCH 2/4] fix: phase --- src/common/phase-helper.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/common/phase-helper.js b/src/common/phase-helper.js index e5b5cb7f..6a753098 100644 --- a/src/common/phase-helper.js +++ b/src/common/phase-helper.js @@ -298,13 +298,15 @@ class ChallengePhaseHelper { } iterativeReviewSet = true; } - } else { + } else if (_.isUndefined(phase.actualStartDate)) { phase.scheduledStartDate = predecessorPhase.scheduledEndDate; } - phase.scheduledEndDate = moment(phase.scheduledStartDate) + if (_.isUndefined(phase.actualEndDate)) { + phase.scheduledEndDate = moment(phase.scheduledStartDate) .add(phase.duration, "seconds") .toDate() .toISOString(); + } } return updatedPhases; } From 155538beb4d7a637dcdf8ad4cc3e36a7175e1665 Mon Sep 17 00:00:00 2001 From: eisbilir Date: Tue, 28 Mar 2023 00:32:56 +0300 Subject: [PATCH 3/4] update startdate/endate calculation --- src/services/ChallengeService.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/services/ChallengeService.js b/src/services/ChallengeService.js index c5e5a023..7983b5e5 100644 --- a/src/services/ChallengeService.js +++ b/src/services/ChallengeService.js @@ -1724,7 +1724,12 @@ async function updateChallenge(currentUser, challengeId, data) { } } - if (data.phases || data.startDate || timelineTemplateChanged) { + let phasesUpdated = false; + if ( + (data.phases && data.phases.length > 0) || + isChallengeBeingActivated || + timelineTemplateChanged + ) { if ( challenge.status === constants.challengeStatuses.Completed || challenge.status.indexOf(constants.challengeStatuses.Cancelled) > -1 @@ -1741,7 +1746,7 @@ async function updateChallenge(currentUser, challengeId, data) { newStartDate, finalTimelineTemplateId ); - } else if (data.startDate || (data.phases && data.phases.length > 0)) { + } else { newPhases = await phaseHelper.populatePhasesForChallengeUpdate( challenge.phases, data.phases, @@ -1749,10 +1754,14 @@ async function updateChallenge(currentUser, challengeId, data) { isChallengeBeingActivated ); } - + phasesUpdated = true; data.phases = newPhases; - data.startDate = convertToISOString(newStartDate); - data.endDate = helper.calculateChallengeEndDate(challenge, data); + } + if (phasesUpdated || data.startDate) { + data.startDate = convertToISOString(data.phases[0].scheduledStartDate); + } + if (phasesUpdated || data.endDate) { + data.endDate = convertToISOString(data.phases[data.phases.length - 1].scheduledEndDate); } if (data.winners && data.winners.length && data.winners.length > 0) { From e0abb8452187e9efe4c41401dbbce96962300776 Mon Sep 17 00:00:00 2001 From: eisbilir Date: Tue, 28 Mar 2023 01:55:04 +0300 Subject: [PATCH 4/4] use phase cache and clean code --- src/common/phase-helper.js | 205 +++++-------------------------- src/services/ChallengeService.js | 13 +- 2 files changed, 39 insertions(+), 179 deletions(-) diff --git a/src/common/phase-helper.js b/src/common/phase-helper.js index 6a753098..8e00312a 100644 --- a/src/common/phase-helper.js +++ b/src/common/phase-helper.js @@ -1,10 +1,10 @@ const { GRPC_CHALLENGE_SERVER_HOST, GRPC_CHALLENGE_SERVER_PORT } = process.env; const { - DomainHelper: { getLookupCriteria, getScanCriteria }, + DomainHelper: { getScanCriteria }, } = require("@topcoder-framework/lib-common"); -const { PhaseDomain, TimelineTemplateDomain } = require("@topcoder-framework/domain-challenge"); +const { PhaseDomain } = require("@topcoder-framework/domain-challenge"); const _ = require("lodash"); @@ -13,153 +13,13 @@ const moment = require("moment"); const errors = require("./errors"); -const phaseService = require("../services/PhaseService"); const timelineTemplateService = require("../services/TimelineTemplateService"); const phaseDomain = new PhaseDomain(GRPC_CHALLENGE_SERVER_HOST, GRPC_CHALLENGE_SERVER_PORT); class ChallengePhaseHelper { - /** - * Populate challenge phases. - * @param {Array} phases the phases to populate - * @param {Date} startDate the challenge start date - * @param {String} timelineTemplateId the timeline template id - */ - async populatePhases(phases, startDate, timelineTemplateId) { - if (_.isUndefined(timelineTemplateId)) { - throw new errors.BadRequestError(`Invalid timeline template ID: ${timelineTemplateId}`); - } - - const { timelineTempate, timelineTemplateMap } = await this.getTemplateAndTemplateMap( - timelineTemplateId - ); - const { phaseDefinitionMap } = await this.getPhaseDefinitionsAndMap(); - - if (!phases || phases.length === 0) { - // auto populate phases - for (const p of timelineTempate) { - phases.push({ ...p }); - } - } - - for (const p of phases) { - const phaseDefinition = phaseDefinitionMap.get(p.phaseId); - - // TODO: move to domain-challenge - p.id = uuid(); - p.name = phaseDefinition.name; - p.description = phaseDefinition.description; - - // set p.open based on current phase - const phaseTemplate = timelineTemplateMap.get(p.phaseId); - if (phaseTemplate) { - if (!p.duration) { - p.duration = phaseTemplate.defaultDuration; - } - - if (phaseTemplate.predecessor) { - const predecessor = _.find(phases, { - phaseId: phaseTemplate.predecessor, - }); - if (!predecessor) { - throw new errors.BadRequestError( - `Predecessor ${phaseTemplate.predecessor} not found in given phases.` - ); - } - p.predecessor = phaseTemplate.predecessor; - console.log("Setting predecessor", p.predecessor, "for phase", p.phaseId); - } - } - } - - // calculate dates - if (!startDate) { - return; - } - - // sort phases by predecessor - phases.sort((a, b) => { - if (a.predecessor === b.phaseId) { - return 1; - } - if (b.predecessor === a.phaseId) { - return -1; - } - return 0; - }); - - let isSubmissionPhaseOpen = false; - - for (let p of phases) { - const predecessor = timelineTemplateMap.get(p.predecessor); - - if (predecessor == null) { - if (p.name === "Registration") { - p.scheduledStartDate = moment(startDate).toDate(); - } - if (p.name === "Submission") { - p.scheduledStartDate = moment(startDate).add(5, "minutes").toDate(); - } - - if (moment(p.scheduledStartDate).isSameOrBefore(moment())) { - p.actualStartDate = p.scheduledStartDate; - } else { - delete p.actualStartDate; - } - - p.scheduledEndDate = moment(p.scheduledStartDate).add(p.duration, "seconds").toDate(); - if (moment(p.scheduledEndDate).isBefore(moment())) { - delete p.actualEndDate; - } else { - p.actualEndDate = p.scheduledEndDate; - } - } else { - const precedecessorPhase = _.find(phases, { - phaseId: predecessor.phaseId, - }); - if (precedecessorPhase == null) { - throw new errors.BadRequestError( - `Predecessor ${predecessor.phaseId} not found in given phases.` - ); - } - let phaseEndDate = moment(precedecessorPhase.scheduledEndDate); - if ( - precedecessorPhase.actualEndDate != null && - moment(precedecessorPhase.actualEndDate).isAfter(phaseEndDate) - ) { - phaseEndDate = moment(precedecessorPhase.actualEndDate); - } else { - phaseEndDate = moment(precedecessorPhase.scheduledEndDate); - } - - p.scheduledStartDate = phaseEndDate.toDate(); - p.scheduledEndDate = moment(p.scheduledStartDate).add(p.duration, "seconds").toDate(); - } - p.isOpen = moment().isBetween(p.scheduledStartDate, p.scheduledEndDate); - if (p.isOpen) { - if (p.name === "Submission") { - isSubmissionPhaseOpen = true; - } - delete p.actualEndDate; - } - - if (moment(p.scheduledStartDate).isAfter(moment())) { - delete p.actualStartDate; - delete p.actualEndDate; - } - - if (p.name === "Post-Mortem" && isSubmissionPhaseOpen) { - delete p.actualStartDate; - delete p.actualEndDate; - p.isOpen = false; - } - - if (p.constraints == null) { - p.constraints = []; - } - } - } - + phaseDefinitionMap = {}; + timelineTemplateMap = {}; async populatePhasesForChallengeCreation(phases, startDate, timelineTemplateId) { if (_.isUndefined(timelineTemplateId)) { throw new errors.BadRequestError(`Invalid timeline template ID: ${timelineTemplateId}`); @@ -232,9 +92,7 @@ class ChallengePhaseHelper { timelineTemplateId, isBeingActivated ) { - const { timelineTempate, timelineTemplateMap } = await this.getTemplateAndTemplateMap( - timelineTemplateId - ); + const { timelineTemplateMap } = await this.getTemplateAndTemplateMap(timelineTemplateId); const { phaseDefinitionMap } = await this.getPhaseDefinitionsAndMap(); let fixedStartDate = undefined; const updatedPhases = _.map(challengePhases, (phase) => { @@ -303,9 +161,9 @@ class ChallengePhaseHelper { } if (_.isUndefined(phase.actualEndDate)) { phase.scheduledEndDate = moment(phase.scheduledStartDate) - .add(phase.duration, "seconds") - .toDate() - .toISOString(); + .add(phase.duration, "seconds") + .toDate() + .toISOString(); } } return updatedPhases; @@ -315,12 +173,8 @@ class ChallengePhaseHelper { if (!phases || phases.length === 0) { return; } - const { items: records } = await phaseDomain.scan({ criteria: getScanCriteria({}) }); - const map = new Map(); - _.each(records, (r) => { - map.set(r.id, r); - }); - const invalidPhases = _.filter(phases, (p) => !map.has(p.phaseId)); + const { phaseDefinitionMap } = await this.getPhaseDefinitionsAndMap(); + const invalidPhases = _.filter(phases, (p) => !phaseDefinitionMap.has(p.phaseId)); if (invalidPhases.length > 0) { throw new errors.BadRequestError( `The following phases are invalid: ${toString(invalidPhases)}` @@ -328,28 +182,37 @@ class ChallengePhaseHelper { } } + async getPhase(phaseId) { + const { phaseDefinitionMap } = await this.getPhaseDefinitionsAndMap(); + return phaseDefinitionMap.get(phaseId); + } + async getPhaseDefinitionsAndMap() { - const { result: records } = await phaseService.searchPhases(); + if (_.isEmpty(this.phaseDefinitionMap)) { + const { items: records } = await phaseDomain.scan({ criteria: getScanCriteria({}) }); - const map = new Map(); - _.each(records, (r) => { - map.set(r.id, r); - }); - return { phaseDefinitions: records, phaseDefinitionMap: map }; + const map = new Map(); + _.each(records, (r) => { + map.set(r.id, r); + }); + + this.phaseDefinitionMap = { phaseDefinitions: records, phaseDefinitionMap: map }; + } + return this.phaseDefinitionMap; } async getTemplateAndTemplateMap(timelineTemplateId) { - const records = await timelineTemplateService.getTimelineTemplate(timelineTemplateId); + if (_.isEmpty(this.timelineTemplateMap)) { + const records = await timelineTemplateService.getTimelineTemplate(timelineTemplateId); - const map = new Map(); - _.each(records.phases, (r) => { - map.set(r.phaseId, r); - }); + const map = new Map(); + _.each(records.phases, (r) => { + map.set(r.phaseId, r); + }); - return { - timelineTempate: records.phases, - timelineTemplateMap: map, - }; + this.timelineTemplateMap = { timelineTempate: records.phases, timelineTemplateMap: map }; + } + return this.timelineTemplateMap; } } diff --git a/src/services/ChallengeService.js b/src/services/ChallengeService.js index 7983b5e5..c5f5f0b3 100644 --- a/src/services/ChallengeService.js +++ b/src/services/ChallengeService.js @@ -18,12 +18,9 @@ const logger = require("../common/logger"); const errors = require("../common/errors"); const constants = require("../../app-constants"); const HttpStatus = require("http-status-codes"); -const moment = require("moment"); -const PhaseService = require("./PhaseService"); const ChallengeTypeService = require("./ChallengeTypeService"); const ChallengeTrackService = require("./ChallengeTrackService"); const ChallengeTimelineTemplateService = require("./ChallengeTimelineTemplateService"); -const TimelineTemplateService = require("./TimelineTemplateService"); const { BadRequestError } = require("../common/errors"); const phaseHelper = require("../common/phase-helper"); @@ -34,7 +31,7 @@ const { Metadata: GrpcMetadata } = require("@grpc/grpc-js"); const esClient = helper.getESClient(); -const { ChallengeDomain, UpdateChallengeInput } = require("@topcoder-framework/domain-challenge"); +const { ChallengeDomain } = require("@topcoder-framework/domain-challenge"); const { hasAdminRole } = require("../common/role-helper"); const { validateChallengeUpdateRequest, @@ -1054,7 +1051,7 @@ async function createChallenge(currentUser, challenge, userToken) { } if (challenge.phases && challenge.phases.length > 0) { - await PhaseService.validatePhases(challenge.phases); + await phaseHelper.validatePhases(challenge.phases); } // populate phases @@ -1274,7 +1271,7 @@ createChallenge.schema = { */ async function getPhasesAndPopulate(data) { _.each(data.phases, async (p) => { - const phase = await PhaseService.getPhase(p.phaseId); + const phase = await phaseHelper.getPhase(p.phaseId); p.name = phase.name; if (phase.description) { p.description = phase.description; @@ -1758,10 +1755,10 @@ async function updateChallenge(currentUser, challengeId, data) { data.phases = newPhases; } if (phasesUpdated || data.startDate) { - data.startDate = convertToISOString(data.phases[0].scheduledStartDate); + data.startDate = convertToISOString(_.min(_.map(data.phases, "scheduledStartDate"))); } if (phasesUpdated || data.endDate) { - data.endDate = convertToISOString(data.phases[data.phases.length - 1].scheduledEndDate); + data.endDate = convertToISOString(_.max(_.map(data.phases, "scheduledEndDate"))); } if (data.winners && data.winners.length && data.winners.length > 0) {