diff --git a/src/common/phase-helper.js b/src/common/phase-helper.js index 231991fe..2336117d 100644 --- a/src/common/phase-helper.js +++ b/src/common/phase-helper.js @@ -52,9 +52,9 @@ class ChallengePhaseHelper { ); if ( !_.isUndefined(fixedStartDate) && - moment(scheduledStartDate).isBefore(moment(fixedStartDate)) + moment(scheduledStartDate).isSameOrBefore(moment(fixedStartDate)) ) { - scheduledStartDate = fixedStartDate; + scheduledStartDate = moment(fixedStartDate).add(5, "minutes").toDate().toISOString(); } phase.scheduledStartDate = moment(scheduledStartDate).toDate().toISOString(); phase.scheduledEndDate = moment(phase.scheduledStartDate) @@ -108,7 +108,7 @@ class ChallengePhaseHelper { if (updatedPhase.name === "Post-Mortem") { updatedPhase.predecessor = "a93544bc-c165-4af4-b55e-18f3593b457a"; } - if (_.isUndefined(updatedPhase.actualEndDate) && updatedPhase.name !== "Iterative Review") { + if (_.isUndefined(updatedPhase.actualEndDate)) { updatedPhase.duration = _.defaultTo(_.get(newPhase, "duration"), updatedPhase.duration); } if (_.isUndefined(updatedPhase.predecessor)) { @@ -118,9 +118,9 @@ class ChallengePhaseHelper { ); if ( !_.isUndefined(fixedStartDate) && - moment(scheduledStartDate).isBefore(moment(fixedStartDate)) + moment(scheduledStartDate).isSameOrBefore(moment(fixedStartDate)) ) { - scheduledStartDate = fixedStartDate; + scheduledStartDate = moment(fixedStartDate).add(5, "minutes").toDate().toISOString(); } if (isBeingActivated && moment(scheduledStartDate).isSameOrBefore(moment())) { updatedPhase.isOpen = true; @@ -134,7 +134,11 @@ class ChallengePhaseHelper { .toDate() .toISOString(); } - if (!_.isUndefined(newPhase) && !_.isUndefined(newPhase.constraints)) { + if ( + _.isUndefined(phase.actualEndDate) && + !_.isUndefined(newPhase) && + !_.isUndefined(newPhase.constraints) + ) { updatedPhase.constraints = newPhase.constraints; } if (_.isUndefined(fixedStartDate)) { @@ -170,6 +174,17 @@ class ChallengePhaseHelper { return updatedPhases; } + handlePhasesAfterCancelling(phases) { + return _.map(phases, (phase) => { + const shouldClosePhase = _.includes(["Registration", "Submission", "Checkpoint Submission"], phase.name); + return { + ...phase, + isOpen: shouldClosePhase ? false : phase.isOpen, + actualEndDate: shouldClosePhase ? moment().toDate().toISOString() : phase.actualEndDate, + }; + }); + } + async validatePhases(phases) { if (!phases || phases.length === 0) { return; diff --git a/src/services/ChallengeService.js b/src/services/ChallengeService.js index c5f5f0b3..2b544007 100644 --- a/src/services/ChallengeService.js +++ b/src/services/ChallengeService.js @@ -42,6 +42,7 @@ const { convertToISOString, } = require("../common/challenge-helper"); const deepEqual = require("deep-equal"); +const { cloneDeep } = require("lodash"); const challengeDomain = new ChallengeDomain(GRPC_CHALLENGE_SERVER_HOST, GRPC_CHALLENGE_SERVER_PORT); @@ -1492,23 +1493,8 @@ async function validateWinners(winners, challengeId) { async function updateChallenge(currentUser, challengeId, data) { const challenge = await challengeDomain.lookup(getLookupCriteria("id", challengeId)); - // Remove fields from data that are not allowed to be updated and that match the existing challenge - data = sanitizeData(sanitizeChallenge(data), challenge); - console.debug("Sanitized Data:", data); - - if (data.phases != null && data.startDate == null) { - data.startDate = challenge.startDate; - } - - validateChallengeUpdateRequest(currentUser, challenge, data); - const projectId = _.get(challenge, "projectId"); - let sendActivationEmail = false; - let sendSubmittedEmail = false; - let sendCompletedEmail = false; - let sendRejectedEmail = false; - const { billingAccountId, markup } = await projectHelper.getProjectBillingInformation(projectId); if (billingAccountId && _.isUndefined(_.get(challenge, "billing.billingAccountId"))) { @@ -1517,10 +1503,22 @@ async function updateChallenge(currentUser, challengeId, data) { } // Make sure the user cannot change the direct project ID - if (data.legacy && data.legacy.directProjectId) { - _.unset(data, "legacy.directProjectId"); + if (data.legacy) { + data.legacy = _.assign({},challenge.legacy, data.legacy) + _.set(data, "legacy.directProjectId", challenge.legacy.directProjectId); } + // Remove fields from data that are not allowed to be updated and that match the existing challenge + data = sanitizeData(sanitizeChallenge(data), challenge); + console.debug("Sanitized Data:", data); + + validateChallengeUpdateRequest(currentUser, challenge, data); + + let sendActivationEmail = false; + let sendSubmittedEmail = false; + let sendCompletedEmail = false; + let sendRejectedEmail = false; + /* BEGIN self-service stuffs */ // TODO: At some point in the future this should be moved to a Self-Service Challenge Helper @@ -1589,11 +1587,24 @@ async function updateChallenge(currentUser, challengeId, data) { logger.debug(`There was an error trying to update the project: ${e.message}`); } } + + if ( + data.status === constants.challengeStatuses.CancelledRequirementsInfeasible || + data.status === constants.challengeStatuses.CancelledPaymentFailed + ) { + try { + await helper.cancelProject(challenge.projectId, data.cancelReason, currentUser); + } catch (e) { + logger.debug(`There was an error trying to cancel the project: ${e.message}`); + } + sendRejectedEmail = true; + } } /* END self-service stuffs */ let isChallengeBeingActivated = false; + let isChallengeBeingCancelled = false; if (data.status) { if (data.status === constants.challengeStatuses.Active) { if ( @@ -1619,16 +1630,18 @@ async function updateChallenge(currentUser, challengeId, data) { } } - if ( - data.status === constants.challengeStatuses.CancelledRequirementsInfeasible || - data.status === constants.challengeStatuses.CancelledPaymentFailed - ) { - try { - await helper.cancelProject(challenge.projectId, cancelReason, currentUser); - } catch (e) { - logger.debug(`There was an error trying to cancel the project: ${e.message}`); - } - sendRejectedEmail = true; + if (_.includes([ + constants.challengeStatuses.Cancelled, + constants.challengeStatuses.CancelledRequirementsInfeasible, + constants.challengeStatuses.CancelledPaymentFailed, + constants.challengeStatuses.CancelledFailedReview, + constants.challengeStatuses.CancelledFailedScreening, + constants.challengeStatuses.CancelledZeroSubmissions, + constants.challengeStatuses.CancelledWinnerUnresponsive, + constants.challengeStatuses.CancelledClientRequest, + constants.challengeStatuses.CancelledZeroRegistrations, + ], data.status)) { + isChallengeBeingCancelled = true; } if (data.status === constants.challengeStatuses.Completed) { @@ -1723,9 +1736,9 @@ async function updateChallenge(currentUser, challengeId, data) { let phasesUpdated = false; if ( - (data.phases && data.phases.length > 0) || + ((data.phases && data.phases.length > 0) || isChallengeBeingActivated || - timelineTemplateChanged + timelineTemplateChanged) && !isChallengeBeingCancelled ) { if ( challenge.status === constants.challengeStatuses.Completed || @@ -1754,6 +1767,10 @@ async function updateChallenge(currentUser, challengeId, data) { phasesUpdated = true; data.phases = newPhases; } + if (isChallengeBeingCancelled && challenge.phases && challenge.phases.length > 0) { + data.phases = phaseHelper.handlePhasesAfterCancelling(challenge.phases); + phasesUpdated = true; + } if (phasesUpdated || data.startDate) { data.startDate = convertToISOString(_.min(_.map(data.phases, "scheduledStartDate"))); } @@ -1855,8 +1872,7 @@ async function updateChallenge(currentUser, challengeId, data) { } try { - const updateInput = sanitizeRepeatedFieldsInUpdateRequest(data); - + const updateInput = sanitizeRepeatedFieldsInUpdateRequest(_.omit(data, ['cancelReason'])); if (!_.isEmpty(updateInput)) { const grpcMetadata = new GrpcMetadata(); @@ -1969,7 +1985,7 @@ updateChallenge.schema = { .valid(_.values(constants.reviewTypes)) .insensitive() .default(constants.reviewTypes.Internal), - confidentialityType: Joi.string().default(config.DEFAULT_CONFIDENTIALITY_TYPE), + confidentialityType: Joi.string().allow(null,'').empty(null,'').default(config.DEFAULT_CONFIDENTIALITY_TYPE), directProjectId: Joi.number(), forumId: Joi.number().integer(), isTask: Joi.boolean(),