-
Notifications
You must be signed in to change notification settings - Fork 6
Allow reopening of registration phase and submission start / end date fix #50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,8 @@ const { hasAdminRole } = require("./role-helper"); | |
| const { ensureAcessibilityToModifiedGroups } = require("./group-helper"); | ||
| const { ChallengeStatusEnum } = require("@prisma/client"); | ||
|
|
||
| const SUBMISSION_PHASE_PRIORITY = ["Topcoder Submission", "Submission"]; | ||
|
|
||
| class ChallengeHelper { | ||
| /** | ||
| * @param {Object} challenge the challenge object | ||
|
|
@@ -356,7 +358,9 @@ class ChallengeHelper { | |
| enrichChallengeForResponse(challenge, track, type, options = {}) { | ||
| if (challenge.phases && challenge.phases.length > 0) { | ||
| const registrationPhase = _.find(challenge.phases, (p) => p.name === "Registration"); | ||
| const submissionPhase = _.find(challenge.phases, (p) => p.name === "Submission"); | ||
| const submissionPhase = | ||
| _.find(challenge.phases, (p) => p.name === SUBMISSION_PHASE_PRIORITY[0]) || | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| _.find(challenge.phases, (p) => p.name === SUBMISSION_PHASE_PRIORITY[1]); | ||
|
|
||
| // select last started open phase as current phase | ||
| _.forEach(challenge.phases, (p) => { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,8 @@ const Decimal = require("decimal.js"); | |
| const constants = require("../../app-constants"); | ||
| const { PrizeSetTypeEnum } = require("@prisma/client"); | ||
| const { dedupeChallengeTerms } = require("./helper"); | ||
|
|
||
| const SUBMISSION_PHASE_PRIORITY = ["Topcoder Submission", "Submission"]; | ||
| /** | ||
| * Convert phases data to prisma model. | ||
| * | ||
|
|
@@ -11,6 +13,7 @@ const { dedupeChallengeTerms } = require("./helper"); | |
| * @param {Object} auditFields createdBy and updatedBy | ||
| */ | ||
| function convertChallengePhaseSchema(challenge, result, auditFields) { | ||
| const phases = _.isArray(challenge.phases) ? challenge.phases : []; | ||
| // keep phase data | ||
| const phaseFields = [ | ||
| "name", | ||
|
|
@@ -25,24 +28,29 @@ function convertChallengePhaseSchema(challenge, result, auditFields) { | |
| "challengeSource", | ||
| ]; | ||
| // current phase names | ||
| result.currentPhaseNames = _.map( | ||
| _.filter(challenge.phases, (p) => p.isOpen === true), | ||
| "name" | ||
| ); | ||
| // get registration date and submission date | ||
| _.forEach(challenge.phases, (p) => { | ||
| if (p.name === "Registration") { | ||
| result.registrationStartDate = p.actualStartDate || p.scheduledStartDate; | ||
| result.registrationEndDate = p.actualEndDate || p.scheduledEndDate; | ||
| } else if (p.name === "Submission") { | ||
| result.submissionStartDate = p.actualStartDate || p.scheduledStartDate; | ||
| result.submissionEndDate = p.actualEndDate || p.scheduledEndDate; | ||
| } | ||
| }); | ||
| result.currentPhaseNames = _.map(_.filter(phases, (p) => p.isOpen === true), "name"); | ||
|
|
||
| const registrationPhase = _.find(phases, (p) => p.name === "Registration"); | ||
| const submissionPhase = | ||
| _.find(phases, (p) => p.name === SUBMISSION_PHASE_PRIORITY[0]) || | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| _.find(phases, (p) => p.name === SUBMISSION_PHASE_PRIORITY[1]); | ||
|
|
||
| if (registrationPhase) { | ||
| result.registrationStartDate = | ||
| registrationPhase.actualStartDate || registrationPhase.scheduledStartDate; | ||
| result.registrationEndDate = | ||
| registrationPhase.actualEndDate || registrationPhase.scheduledEndDate; | ||
| } | ||
| if (submissionPhase) { | ||
| result.submissionStartDate = | ||
| submissionPhase.actualStartDate || submissionPhase.scheduledStartDate; | ||
| result.submissionEndDate = | ||
| submissionPhase.actualEndDate || submissionPhase.scheduledEndDate; | ||
| } | ||
| // set phases array data | ||
| if (!_.isEmpty(challenge.phases)) { | ||
| if (!_.isEmpty(phases)) { | ||
| result.phases = { | ||
| create: _.map(challenge.phases, (p) => { | ||
| create: _.map(phases, (p) => { | ||
| const phaseData = { | ||
| phase: { connect: { id: p.phaseId } }, | ||
| ..._.pick(p, phaseFields), | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,10 @@ const PHASE_RESOURCE_ROLE_REQUIREMENTS = Object.freeze({ | |
| review: "Reviewer", | ||
| "checkpoint review": "Checkpoint Reviewer", | ||
| }); | ||
| const SUBMISSION_PHASE_NAME_SET = new Set(["submission", "topgear submission"]); | ||
| const REGISTRATION_PHASE_NAME = "registration"; | ||
|
|
||
| const normalizePhaseName = (name) => String(name || "").trim().toLowerCase(); | ||
|
|
||
| async function hasPendingScorecardsForPhase(challengePhaseId) { | ||
| if (!config.REVIEW_DB_URL) { | ||
|
|
@@ -561,7 +565,14 @@ async function partiallyUpdateChallengePhase(currentUser, challengeId, id, data) | |
| return reopenedPhaseIdentifiers.has(String(phase.predecessor)); | ||
| }); | ||
|
|
||
| if (dependentOpenPhases.length === 0) { | ||
| const normalizedPhaseName = normalizePhaseName(phaseName); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [💡 |
||
| const hasSubmissionVariantOpen = openPhases.some((phase) => | ||
| SUBMISSION_PHASE_NAME_SET.has(normalizePhaseName(phase?.name)) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| ); | ||
| const allowRegistrationReopenWithoutExplicitDependency = | ||
| normalizedPhaseName === REGISTRATION_PHASE_NAME && hasSubmissionVariantOpen; | ||
|
|
||
| if (dependentOpenPhases.length === 0 && !allowRegistrationReopenWithoutExplicitDependency) { | ||
| throw new errors.ForbiddenError( | ||
| `Cannot reopen ${phaseName} because no currently open phase depends on it` | ||
| ); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -330,7 +330,7 @@ describe('challenge phase service unit tests', () => { | |
| } | ||
| }) | ||
|
|
||
| it('partially update challenge phase - cannot reopen when open phase is not a successor', async () => { | ||
| it('partially update challenge phase - can reopen registration when submission is open', async () => { | ||
| const startDate = new Date('2025-06-01T00:00:00.000Z') | ||
| const endDate = new Date('2025-06-02T00:00:00.000Z') | ||
|
|
||
|
|
@@ -350,6 +350,60 @@ describe('challenge phase service unit tests', () => { | |
| } | ||
| }) | ||
|
|
||
| try { | ||
| const challengePhase = await service.partiallyUpdateChallengePhase( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [ |
||
| authUser, | ||
| data.challenge.id, | ||
| data.challengePhase1Id, | ||
| { | ||
| isOpen: true | ||
| } | ||
| ) | ||
| should.equal(challengePhase.id, data.challengePhase1Id) | ||
| should.equal(challengePhase.isOpen, true) | ||
| should.equal(challengePhase.actualEndDate, null) | ||
| } finally { | ||
| await prisma.challengePhase.update({ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [❗❗ |
||
| where: { id: data.challengePhase1Id }, | ||
| data: { | ||
| isOpen: false, | ||
| actualStartDate: startDate, | ||
| actualEndDate: endDate | ||
| } | ||
| }) | ||
| await prisma.challengePhase.update({ | ||
| where: { id: data.challengePhase2Id }, | ||
| data: { | ||
| isOpen: false, | ||
| predecessor: data.challengePhase1Id, | ||
| name: 'Submission' | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| }) | ||
|
|
||
| it('partially update challenge phase - cannot reopen when open phase is not a successor or submission variant', async () => { | ||
| const startDate = new Date('2025-06-01T00:00:00.000Z') | ||
| const endDate = new Date('2025-06-02T00:00:00.000Z') | ||
|
|
||
| await prisma.challengePhase.update({ | ||
| where: { id: data.challengePhase1Id }, | ||
| data: { | ||
| isOpen: false, | ||
| actualStartDate: startDate, | ||
| actualEndDate: endDate | ||
| } | ||
| }) | ||
| await prisma.challengePhase.update({ | ||
| where: { id: data.challengePhase2Id }, | ||
| data: { | ||
| isOpen: true, | ||
| predecessor: null, | ||
| name: 'Review' | ||
| } | ||
| }) | ||
|
|
||
| try { | ||
| await service.partiallyUpdateChallengePhase(authUser, data.challenge.id, data.challengePhase1Id, { | ||
| isOpen: true | ||
|
|
@@ -366,7 +420,16 @@ describe('challenge phase service unit tests', () => { | |
| where: { id: data.challengePhase2Id }, | ||
| data: { | ||
| isOpen: false, | ||
| predecessor: data.challengePhase1Id | ||
| predecessor: data.challengePhase1Id, | ||
| name: 'Submission' | ||
| } | ||
| }) | ||
| await prisma.challengePhase.update({ | ||
| where: { id: data.challengePhase1Id }, | ||
| data: { | ||
| isOpen: false, | ||
| actualStartDate: startDate, | ||
| actualEndDate: endDate | ||
| } | ||
| }) | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[💡
maintainability]Consider making
SUBMISSION_PHASE_PRIORITYa constant outside of this file if it is used in multiple places across the codebase. This will improve maintainability by ensuring that changes to the priority list are centralized.