Skip to content

Commit 017cfa5

Browse files
authored
Merge pull request #571 from topcoder-platform/feat/phase-logic-update
feat: new phase logic
2 parents 54f18e1 + d0862f9 commit 017cfa5

File tree

2 files changed

+151
-36
lines changed

2 files changed

+151
-36
lines changed

src/common/phase-helper.js

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,130 @@ class ChallengePhaseHelper {
160160
}
161161
}
162162

163+
async populatePhasesForChallengeCreation(phases, startDate, timelineTemplateId) {
164+
if (_.isUndefined(timelineTemplateId)) {
165+
throw new errors.BadRequestError(`Invalid timeline template ID: ${timelineTemplateId}`);
166+
}
167+
const { timelineTempate } = await this.getTemplateAndTemplateMap(timelineTemplateId);
168+
const { phaseDefinitionMap } = await this.getPhaseDefinitionsAndMap();
169+
const finalPhases = _.map(timelineTempate, (phaseFromTemplate) => {
170+
const phaseDefinition = phaseDefinitionMap.get(phaseFromTemplate.phaseId);
171+
const phaseFromInput = _.find(phases, (p) => p.phaseId === phaseFromTemplate.phaseId);
172+
const phase = {
173+
id: uuid(),
174+
phaseId: phaseFromTemplate.phaseId,
175+
name: phaseDefinition.name,
176+
description: phaseDefinition.description,
177+
duration: _.defaultTo(_.get(phaseFromInput, "duration"), phaseFromTemplate.defaultDuration),
178+
isOpen: false,
179+
predecessor: phaseFromTemplate.predecessor,
180+
constraints: _.defaultTo(_.get(phaseFromInput, "constraints"), []),
181+
scheduledStartDate: undefined,
182+
scheduledEndDate: undefined,
183+
actualStartDate: undefined,
184+
actualEndDate: undefined,
185+
};
186+
if (_.isUndefined(phase.predecessor)) {
187+
if (_.isUndefined(_.get(phaseFromInput, "scheduledStartDate"))) {
188+
phase.scheduledStartDate = moment(startDate).toDate();
189+
} else {
190+
phase.scheduledStartDate = moment(_.get(phaseFromInput, "scheduledStartDate")).toDate();
191+
}
192+
phase.scheduledEndDate = moment(phase.scheduledStartDate)
193+
.add(phase.duration, "seconds")
194+
.toDate();
195+
}
196+
return phase;
197+
});
198+
for (let phase of finalPhases) {
199+
if (_.isUndefined(phase.predecessor)) {
200+
continue;
201+
}
202+
const precedecessorPhase = _.find(finalPhases, {
203+
phaseId: phase.predecessor,
204+
});
205+
if (phase.name === "Iterative Review Phase") {
206+
phase.scheduledStartDate = precedecessorPhase.scheduledStartDate;
207+
} else {
208+
phase.scheduledStartDate = precedecessorPhase.scheduledEndDate;
209+
}
210+
phase.scheduledEndDate = moment(phase.scheduledStartDate)
211+
.add(phase.duration, "seconds")
212+
.toDate();
213+
}
214+
return finalPhases;
215+
}
216+
217+
async populatePhasesForChallengeUpdate(
218+
challengePhases,
219+
newPhases,
220+
timelineTemplateId,
221+
isBeingActivated
222+
) {
223+
const { timelineTempate, timelineTemplateMap } = await this.getTemplateAndTemplateMap(
224+
timelineTemplateId
225+
);
226+
const { phaseDefinitionMap } = await this.getPhaseDefinitionsAndMap();
227+
228+
const updatedPhases = _.map(challengePhases, (phase) => {
229+
const phaseFromTemplate = timelineTemplateMap.get(phase.phaseId);
230+
const phaseDefinition = phaseDefinitionMap.get(phase.phaseId);
231+
const updatedPhase = {
232+
...phase,
233+
predecessor: phaseFromTemplate.predecessor,
234+
description: phaseDefinition.description,
235+
};
236+
if (!_.isUndefined(phase.actualEndDate)) {
237+
return updatedPhase;
238+
}
239+
if (phase.name === "Iterative Review Phase") {
240+
return updatedPhase;
241+
}
242+
const newPhase = _.find(newPhases, (p) => p.phaseId === phase.phaseId);
243+
if (_.isUndefined(newPhase) && !isBeingActivated) {
244+
return updatedPhase;
245+
}
246+
phase.duration = _.defaultTo(_.get(newPhase, "duration"), phase.duration);
247+
if (_.isUndefined(phase.predecessor)) {
248+
if (
249+
isBeingActivated &&
250+
moment(
251+
_.defaultTo(_.get(newPhase, "scheduledStartDate"), phase.scheduledStartDate)
252+
).isSameOrBefore(moment())
253+
) {
254+
phase.isOpen = true;
255+
phase.scheduledStartDate = moment().toDate();
256+
phase.actualStartDate = phase.scheduledStartDate;
257+
} else if (phase.isOpen === false && !_.isUndefined(newPhase.scheduledStartDate)) {
258+
phase.scheduledStartDate = moment(newPhase.scheduledStartDate).toDate();
259+
}
260+
phase.scheduledEndDate = moment(phase.scheduledStartDate)
261+
.add(phase.duration, "seconds")
262+
.toDate();
263+
}
264+
if (!_.isUndefined(newPhase) && !_.isUndefined(newPhase.constraints)) {
265+
phase.constraints = newPhase.constraints;
266+
}
267+
return updatedPhase;
268+
});
269+
for (let phase of updatedPhases) {
270+
if (_.isUndefined(phase.predecessor)) {
271+
continue;
272+
}
273+
if (phase.name === "Iterative Review Phase") {
274+
continue;
275+
}
276+
const precedecessorPhase = _.find(updatedPhases, {
277+
phaseId: phase.predecessor,
278+
});
279+
phase.scheduledStartDate = precedecessorPhase.scheduledEndDate;
280+
phase.scheduledEndDate = moment(phase.scheduledStartDate)
281+
.add(phase.duration, "seconds")
282+
.toDate();
283+
}
284+
return updatedPhases;
285+
}
286+
163287
async validatePhases(phases) {
164288
if (!phases || phases.length === 0) {
165289
return;

src/services/ChallengeService.js

Lines changed: 27 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,17 +1146,12 @@ async function createChallenge(currentUser, challenge, userToken) {
11461146
} else {
11471147
throw new errors.BadRequestError(`trackId and typeId are required to create a challenge`);
11481148
}
1149-
} else {
1150-
if (challenge.phases == null) {
1151-
challenge.phases = [];
1152-
}
1153-
1154-
await phaseHelper.populatePhases(
1155-
challenge.phases,
1156-
challenge.startDate,
1157-
challenge.timelineTemplateId
1158-
);
11591149
}
1150+
challenge.phases = await phaseHelper.populatePhasesForChallengeCreation(
1151+
challenge.phases,
1152+
challenge.startDate,
1153+
challenge.timelineTemplateId
1154+
);
11601155

11611156
// populate challenge terms
11621157
// const projectTerms = await helper.getProjectDefaultTerms(challenge.projectId)
@@ -1708,6 +1703,7 @@ async function update(currentUser, challengeId, data, isFull) {
17081703
});
17091704
}
17101705
}
1706+
let isChallengeBeingActivated = false;
17111707
if (data.status) {
17121708
if (data.status === constants.challengeStatuses.Active) {
17131709
if (
@@ -1728,6 +1724,9 @@ async function update(currentUser, challengeId, data, isFull) {
17281724
"Cannot Activate this project, it has no active billing account."
17291725
);
17301726
}
1727+
if (challenge.status === constants.challengeStatuses.Draft) {
1728+
isChallengeBeingActivated = true;
1729+
}
17311730
}
17321731
if (
17331732
data.status === constants.challengeStatuses.CancelledRequirementsInfeasible ||
@@ -1910,6 +1909,7 @@ async function update(currentUser, challengeId, data, isFull) {
19101909
// TODO: Fix this Tech Debt once legacy is turned off
19111910
const finalStatus = data.status || challenge.status;
19121911
const finalTimelineTemplateId = data.timelineTemplateId || challenge.timelineTemplateId;
1912+
const timelineTemplateChanged = false;
19131913
if (!_.get(data, "legacy.pureV5") && !_.get(challenge, "legacy.pureV5")) {
19141914
if (
19151915
finalStatus !== constants.challengeStatuses.New &&
@@ -1922,6 +1922,7 @@ async function update(currentUser, challengeId, data, isFull) {
19221922
} else if (finalTimelineTemplateId !== challenge.timelineTemplateId) {
19231923
// make sure there are no previous phases if the timeline template has changed
19241924
challenge.phases = [];
1925+
timelineTemplateChanged = true;
19251926
}
19261927

19271928
if (data.prizeSets && data.prizeSets.length > 0) {
@@ -1949,7 +1950,7 @@ async function update(currentUser, challengeId, data, isFull) {
19491950
}
19501951
}
19511952

1952-
if (data.phases || data.startDate) {
1953+
if (data.phases || data.startDate || timelineTemplateChanged) {
19531954
if (
19541955
challenge.status === constants.challengeStatuses.Completed ||
19551956
challenge.status.indexOf(constants.challengeStatuses.Cancelled) > -1
@@ -1958,33 +1959,23 @@ async function update(currentUser, challengeId, data, isFull) {
19581959
`Challenge phase/start date can not be modified for Completed or Cancelled challenges.`
19591960
);
19601961
}
1961-
1962-
if (data.phases && data.phases.length > 0) {
1963-
for (let i = 0; i < challenge.phases.length; i += 1) {
1964-
const updatedPhaseInfo = _.find(
1965-
data.phases,
1966-
(p) => p.phaseId === challenge.phases[i].phaseId
1967-
);
1968-
if (updatedPhaseInfo) {
1969-
_.extend(challenge.phases[i], updatedPhaseInfo);
1970-
}
1971-
}
1972-
if (challenge.phases.length === 0 && data.phases && data.phases.length > 0) {
1973-
challenge.phases = data.phases;
1974-
}
1975-
}
1976-
1977-
const newPhases = _.cloneDeep(challenge.phases) || [];
19781962
const newStartDate = data.startDate || challenge.startDate;
1963+
let newPhases;
1964+
if (timelineTemplateChanged) {
1965+
newPhases = await phaseHelper.populatePhasesForChallengeCreation(
1966+
data.phases,
1967+
newStartDate,
1968+
finalTimelineTemplateId
1969+
);
1970+
} else if (data.startDate || (data.phases && data.phases.length > 0)) {
1971+
newPhases = await phaseHelper.populatePhasesForChallengeUpdate(
1972+
challenge.phases,
1973+
data.phases,
1974+
challenge.timelineTemplateId,
1975+
isChallengeBeingActivated
1976+
);
1977+
}
19791978

1980-
await PhaseService.validatePhases(newPhases);
1981-
1982-
// populate phases
1983-
await phaseHelper.populatePhases(
1984-
newPhases,
1985-
newStartDate,
1986-
data.timelineTemplateId || challenge.timelineTemplateId
1987-
);
19881979
data.phases = newPhases;
19891980
challenge.phases = newPhases;
19901981
data.startDate = newStartDate;

0 commit comments

Comments
 (0)