Skip to content

Commit d81fd79

Browse files
authored
Merge pull request #600 from topcoder-platform/fix/startdate
2 parents deddab3 + e0abb84 commit d81fd79

File tree

2 files changed

+39
-179
lines changed

2 files changed

+39
-179
lines changed

src/common/phase-helper.js

Lines changed: 34 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
const { GRPC_CHALLENGE_SERVER_HOST, GRPC_CHALLENGE_SERVER_PORT } = process.env;
22

33
const {
4-
DomainHelper: { getLookupCriteria, getScanCriteria },
4+
DomainHelper: { getScanCriteria },
55
} = require("@topcoder-framework/lib-common");
66

7-
const { PhaseDomain, TimelineTemplateDomain } = require("@topcoder-framework/domain-challenge");
7+
const { PhaseDomain } = require("@topcoder-framework/domain-challenge");
88

99
const _ = require("lodash");
1010

@@ -13,153 +13,13 @@ const moment = require("moment");
1313

1414
const errors = require("./errors");
1515

16-
const phaseService = require("../services/PhaseService");
1716
const timelineTemplateService = require("../services/TimelineTemplateService");
1817

1918
const phaseDomain = new PhaseDomain(GRPC_CHALLENGE_SERVER_HOST, GRPC_CHALLENGE_SERVER_PORT);
2019

2120
class ChallengePhaseHelper {
22-
/**
23-
* Populate challenge phases.
24-
* @param {Array} phases the phases to populate
25-
* @param {Date} startDate the challenge start date
26-
* @param {String} timelineTemplateId the timeline template id
27-
*/
28-
async populatePhases(phases, startDate, timelineTemplateId) {
29-
if (_.isUndefined(timelineTemplateId)) {
30-
throw new errors.BadRequestError(`Invalid timeline template ID: ${timelineTemplateId}`);
31-
}
32-
33-
const { timelineTempate, timelineTemplateMap } = await this.getTemplateAndTemplateMap(
34-
timelineTemplateId
35-
);
36-
const { phaseDefinitionMap } = await this.getPhaseDefinitionsAndMap();
37-
38-
if (!phases || phases.length === 0) {
39-
// auto populate phases
40-
for (const p of timelineTempate) {
41-
phases.push({ ...p });
42-
}
43-
}
44-
45-
for (const p of phases) {
46-
const phaseDefinition = phaseDefinitionMap.get(p.phaseId);
47-
48-
// TODO: move to domain-challenge
49-
p.id = uuid();
50-
p.name = phaseDefinition.name;
51-
p.description = phaseDefinition.description;
52-
53-
// set p.open based on current phase
54-
const phaseTemplate = timelineTemplateMap.get(p.phaseId);
55-
if (phaseTemplate) {
56-
if (!p.duration) {
57-
p.duration = phaseTemplate.defaultDuration;
58-
}
59-
60-
if (phaseTemplate.predecessor) {
61-
const predecessor = _.find(phases, {
62-
phaseId: phaseTemplate.predecessor,
63-
});
64-
if (!predecessor) {
65-
throw new errors.BadRequestError(
66-
`Predecessor ${phaseTemplate.predecessor} not found in given phases.`
67-
);
68-
}
69-
p.predecessor = phaseTemplate.predecessor;
70-
console.log("Setting predecessor", p.predecessor, "for phase", p.phaseId);
71-
}
72-
}
73-
}
74-
75-
// calculate dates
76-
if (!startDate) {
77-
return;
78-
}
79-
80-
// sort phases by predecessor
81-
phases.sort((a, b) => {
82-
if (a.predecessor === b.phaseId) {
83-
return 1;
84-
}
85-
if (b.predecessor === a.phaseId) {
86-
return -1;
87-
}
88-
return 0;
89-
});
90-
91-
let isSubmissionPhaseOpen = false;
92-
93-
for (let p of phases) {
94-
const predecessor = timelineTemplateMap.get(p.predecessor);
95-
96-
if (predecessor == null) {
97-
if (p.name === "Registration") {
98-
p.scheduledStartDate = moment(startDate).toDate();
99-
}
100-
if (p.name === "Submission") {
101-
p.scheduledStartDate = moment(startDate).add(5, "minutes").toDate();
102-
}
103-
104-
if (moment(p.scheduledStartDate).isSameOrBefore(moment())) {
105-
p.actualStartDate = p.scheduledStartDate;
106-
} else {
107-
delete p.actualStartDate;
108-
}
109-
110-
p.scheduledEndDate = moment(p.scheduledStartDate).add(p.duration, "seconds").toDate();
111-
if (moment(p.scheduledEndDate).isBefore(moment())) {
112-
delete p.actualEndDate;
113-
} else {
114-
p.actualEndDate = p.scheduledEndDate;
115-
}
116-
} else {
117-
const precedecessorPhase = _.find(phases, {
118-
phaseId: predecessor.phaseId,
119-
});
120-
if (precedecessorPhase == null) {
121-
throw new errors.BadRequestError(
122-
`Predecessor ${predecessor.phaseId} not found in given phases.`
123-
);
124-
}
125-
let phaseEndDate = moment(precedecessorPhase.scheduledEndDate);
126-
if (
127-
precedecessorPhase.actualEndDate != null &&
128-
moment(precedecessorPhase.actualEndDate).isAfter(phaseEndDate)
129-
) {
130-
phaseEndDate = moment(precedecessorPhase.actualEndDate);
131-
} else {
132-
phaseEndDate = moment(precedecessorPhase.scheduledEndDate);
133-
}
134-
135-
p.scheduledStartDate = phaseEndDate.toDate();
136-
p.scheduledEndDate = moment(p.scheduledStartDate).add(p.duration, "seconds").toDate();
137-
}
138-
p.isOpen = moment().isBetween(p.scheduledStartDate, p.scheduledEndDate);
139-
if (p.isOpen) {
140-
if (p.name === "Submission") {
141-
isSubmissionPhaseOpen = true;
142-
}
143-
delete p.actualEndDate;
144-
}
145-
146-
if (moment(p.scheduledStartDate).isAfter(moment())) {
147-
delete p.actualStartDate;
148-
delete p.actualEndDate;
149-
}
150-
151-
if (p.name === "Post-Mortem" && isSubmissionPhaseOpen) {
152-
delete p.actualStartDate;
153-
delete p.actualEndDate;
154-
p.isOpen = false;
155-
}
156-
157-
if (p.constraints == null) {
158-
p.constraints = [];
159-
}
160-
}
161-
}
162-
21+
phaseDefinitionMap = {};
22+
timelineTemplateMap = {};
16323
async populatePhasesForChallengeCreation(phases, startDate, timelineTemplateId) {
16424
if (_.isUndefined(timelineTemplateId)) {
16525
throw new errors.BadRequestError(`Invalid timeline template ID: ${timelineTemplateId}`);
@@ -232,9 +92,7 @@ class ChallengePhaseHelper {
23292
timelineTemplateId,
23393
isBeingActivated
23494
) {
235-
const { timelineTempate, timelineTemplateMap } = await this.getTemplateAndTemplateMap(
236-
timelineTemplateId
237-
);
95+
const { timelineTemplateMap } = await this.getTemplateAndTemplateMap(timelineTemplateId);
23896
const { phaseDefinitionMap } = await this.getPhaseDefinitionsAndMap();
23997
let fixedStartDate = undefined;
24098
const updatedPhases = _.map(challengePhases, (phase) => {
@@ -303,9 +161,9 @@ class ChallengePhaseHelper {
303161
}
304162
if (_.isUndefined(phase.actualEndDate)) {
305163
phase.scheduledEndDate = moment(phase.scheduledStartDate)
306-
.add(phase.duration, "seconds")
307-
.toDate()
308-
.toISOString();
164+
.add(phase.duration, "seconds")
165+
.toDate()
166+
.toISOString();
309167
}
310168
}
311169
return updatedPhases;
@@ -315,41 +173,46 @@ class ChallengePhaseHelper {
315173
if (!phases || phases.length === 0) {
316174
return;
317175
}
318-
const { items: records } = await phaseDomain.scan({ criteria: getScanCriteria({}) });
319-
const map = new Map();
320-
_.each(records, (r) => {
321-
map.set(r.id, r);
322-
});
323-
const invalidPhases = _.filter(phases, (p) => !map.has(p.phaseId));
176+
const { phaseDefinitionMap } = await this.getPhaseDefinitionsAndMap();
177+
const invalidPhases = _.filter(phases, (p) => !phaseDefinitionMap.has(p.phaseId));
324178
if (invalidPhases.length > 0) {
325179
throw new errors.BadRequestError(
326180
`The following phases are invalid: ${toString(invalidPhases)}`
327181
);
328182
}
329183
}
330184

185+
async getPhase(phaseId) {
186+
const { phaseDefinitionMap } = await this.getPhaseDefinitionsAndMap();
187+
return phaseDefinitionMap.get(phaseId);
188+
}
189+
331190
async getPhaseDefinitionsAndMap() {
332-
const { result: records } = await phaseService.searchPhases();
191+
if (_.isEmpty(this.phaseDefinitionMap)) {
192+
const { items: records } = await phaseDomain.scan({ criteria: getScanCriteria({}) });
333193

334-
const map = new Map();
335-
_.each(records, (r) => {
336-
map.set(r.id, r);
337-
});
338-
return { phaseDefinitions: records, phaseDefinitionMap: map };
194+
const map = new Map();
195+
_.each(records, (r) => {
196+
map.set(r.id, r);
197+
});
198+
199+
this.phaseDefinitionMap = { phaseDefinitions: records, phaseDefinitionMap: map };
200+
}
201+
return this.phaseDefinitionMap;
339202
}
340203

341204
async getTemplateAndTemplateMap(timelineTemplateId) {
342-
const records = await timelineTemplateService.getTimelineTemplate(timelineTemplateId);
205+
if (_.isEmpty(this.timelineTemplateMap)) {
206+
const records = await timelineTemplateService.getTimelineTemplate(timelineTemplateId);
343207

344-
const map = new Map();
345-
_.each(records.phases, (r) => {
346-
map.set(r.phaseId, r);
347-
});
208+
const map = new Map();
209+
_.each(records.phases, (r) => {
210+
map.set(r.phaseId, r);
211+
});
348212

349-
return {
350-
timelineTempate: records.phases,
351-
timelineTemplateMap: map,
352-
};
213+
this.timelineTemplateMap = { timelineTempate: records.phases, timelineTemplateMap: map };
214+
}
215+
return this.timelineTemplateMap;
353216
}
354217
}
355218

src/services/ChallengeService.js

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,9 @@ const logger = require("../common/logger");
1818
const errors = require("../common/errors");
1919
const constants = require("../../app-constants");
2020
const HttpStatus = require("http-status-codes");
21-
const moment = require("moment");
22-
const PhaseService = require("./PhaseService");
2321
const ChallengeTypeService = require("./ChallengeTypeService");
2422
const ChallengeTrackService = require("./ChallengeTrackService");
2523
const ChallengeTimelineTemplateService = require("./ChallengeTimelineTemplateService");
26-
const TimelineTemplateService = require("./TimelineTemplateService");
2724
const { BadRequestError } = require("../common/errors");
2825

2926
const phaseHelper = require("../common/phase-helper");
@@ -34,7 +31,7 @@ const { Metadata: GrpcMetadata } = require("@grpc/grpc-js");
3431

3532
const esClient = helper.getESClient();
3633

37-
const { ChallengeDomain, UpdateChallengeInput } = require("@topcoder-framework/domain-challenge");
34+
const { ChallengeDomain } = require("@topcoder-framework/domain-challenge");
3835
const { hasAdminRole } = require("../common/role-helper");
3936
const {
4037
validateChallengeUpdateRequest,
@@ -1054,7 +1051,7 @@ async function createChallenge(currentUser, challenge, userToken) {
10541051
}
10551052

10561053
if (challenge.phases && challenge.phases.length > 0) {
1057-
await PhaseService.validatePhases(challenge.phases);
1054+
await phaseHelper.validatePhases(challenge.phases);
10581055
}
10591056

10601057
// populate phases
@@ -1274,7 +1271,7 @@ createChallenge.schema = {
12741271
*/
12751272
async function getPhasesAndPopulate(data) {
12761273
_.each(data.phases, async (p) => {
1277-
const phase = await PhaseService.getPhase(p.phaseId);
1274+
const phase = await phaseHelper.getPhase(p.phaseId);
12781275
p.name = phase.name;
12791276
if (phase.description) {
12801277
p.description = phase.description;
@@ -1758,10 +1755,10 @@ async function updateChallenge(currentUser, challengeId, data) {
17581755
data.phases = newPhases;
17591756
}
17601757
if (phasesUpdated || data.startDate) {
1761-
data.startDate = convertToISOString(data.phases[0].scheduledStartDate);
1758+
data.startDate = convertToISOString(_.min(_.map(data.phases, "scheduledStartDate")));
17621759
}
17631760
if (phasesUpdated || data.endDate) {
1764-
data.endDate = convertToISOString(data.phases[data.phases.length - 1].scheduledEndDate);
1761+
data.endDate = convertToISOString(_.max(_.map(data.phases, "scheduledEndDate")));
17651762
}
17661763

17671764
if (data.winners && data.winners.length && data.winners.length > 0) {

0 commit comments

Comments
 (0)