From 3bd3e5137cd270921146890d40215401961b2ed2 Mon Sep 17 00:00:00 2001 From: Marios Kranitsas Date: Tue, 5 Jul 2022 13:13:02 +0300 Subject: [PATCH 01/30] Payment Generation issue in Work Manager --- src/services/ProcessorService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/ProcessorService.js b/src/services/ProcessorService.js index 19cbfdc..e0536d1 100644 --- a/src/services/ProcessorService.js +++ b/src/services/ProcessorService.js @@ -780,7 +780,7 @@ processMessage.schema = { prizeSets: Joi.array().items(Joi.object().keys({ type: Joi.string().valid(_.values(constants.prizeSetTypes)).required(), prizes: Joi.array().items(Joi.object().keys({ - value: Joi.number().positive().required() + value: Joi.number().min(0).required() }).unknown(true)) }).unknown(true)).min(1), tags: Joi.array().items(Joi.string().required()).min(1), // tag names From 3a37d9cb86729df8c5769df50a9e9e3decb448ef Mon Sep 17 00:00:00 2001 From: Thomas Kranitsas Date: Mon, 29 Aug 2022 16:28:12 +0300 Subject: [PATCH 02/30] support review feedback --- src/constants.js | 20 ++- src/services/ProcessorService.js | 19 +++ src/services/legacyChallengeReviewService.js | 148 +++++++++++++++++++ 3 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 src/services/legacyChallengeReviewService.js diff --git a/src/constants.js b/src/constants.js index 60ec940..0e848a1 100644 --- a/src/constants.js +++ b/src/constants.js @@ -48,6 +48,23 @@ const challengeStatuses = { CancelledPaymentFailed: 'Cancelled - Payment Failed' } +const scorecardQuestionMapping = { + 30002212: [ + { + questionId: 30007531, + description: 'Does the submission sufficiently satisfy the requirements as described in the provided specification?' + }, + { + questionId: 30007533, + description: 'How would you rate the work ethic of this submitter?' + }, + { + questionId: 30007532, + description: 'How would you rate the quality of this submitters work?' + } + ] +} + const PhaseStatusTypes = { Scheduled: 1, Open: 2, @@ -150,5 +167,6 @@ module.exports = { challengeStatuses, PhaseStatusTypes, prizeTypesIds, - supportedMetadata + supportedMetadata, + scorecardQuestionMapping } diff --git a/src/services/ProcessorService.js b/src/services/ProcessorService.js index 64d378b..c5698e1 100644 --- a/src/services/ProcessorService.js +++ b/src/services/ProcessorService.js @@ -19,6 +19,7 @@ const paymentService = require('./paymentService') const { createOrSetNumberOfReviewers } = require('./selfServiceReviewerService') const { disableTimelineNotifications } = require('./selfServiceNotificationService') const legacyChallengeService = require('./legacyChallengeService') +const legacyChallengeReviewService = require('./legacyChallengeReviewService') /** * Drop and recreate phases in ifx @@ -713,6 +714,24 @@ async function processMessage (message) { needSyncV4ES = true } if (message.payload.status === constants.challengeStatuses.Completed && challenge.currentStatus !== constants.challengeStatuses.Completed) { + // Try to read reviews and insert them into informix DB + if (message.payload.metadata && message.payload.legacy.reviewScorecardId) { + let orReviewFeedback = _.find(message.payload.metadata, meta => meta.name === 'or_review_feedback') + let orReviewScore = _.find(message.payload.metadata, meta => meta.name === 'or_review_score') + if (!_.isUndefined(orReviewFeedback) && !_.isUndefined(orReviewScore)) { + orReviewFeedback = JSON.parse(orReviewFeedback) + const reviewResponses = [] + _.each(orReviewFeedback, (value, key) => { + const questionId = _.get(_.find(constants.scorecardQuestionMapping[message.payload.legacy.reviewScorecardId], item => _.toString(item.questionId) === _.toString(key) || _.toLower(item.description) === _.toLower(key)), 'questionId') + reviewResponses.push({ + questionId, + answer: value + }) + }) + orReviewScore = _.toNumber(orReviewFeedback) + await legacyChallengeReviewService.insertReview(legacyId, message.payload.legacy.reviewScorecardId, orReviewScore, reviewResponses, createdByUserId) + } + } if (message.payload.task.isTask) { logger.info('Challenge is a TASK') if (!message.payload.winners || message.payload.winners.length === 0) { diff --git a/src/services/legacyChallengeReviewService.js b/src/services/legacyChallengeReviewService.js new file mode 100644 index 0000000..a7bed13 --- /dev/null +++ b/src/services/legacyChallengeReviewService.js @@ -0,0 +1,148 @@ +/** + * Legacy Challenge Service + * Interacts with InformixDB + * Note: this is built to work for topgear challenges and iterative review phases + */ +const _ = require('lodash') +const logger = require('../common/logger') +const util = require('util') +const helper = require('../common/helper') +const IDGenerator = require('../common/idGenerator') +const reviewIdGen = new IDGenerator('review_id_seq') +const reviewItemIdGen = new IDGenerator('review_item_id_seq') + +const ITERATIVE_REVIEWER_RESOURCE_ROLE_ID = 21 +const QUERY_GET_ITERATIVE_REVIEW_RESOURCE_FOR_CHALLENGE = `SELECT limit 1 resource_id as resourceid FROM resource WHERE project_id = %d AND resource_role_id = ${ITERATIVE_REVIEWER_RESOURCE_ROLE_ID}` + +const QUERY_CREATE_REVIEW = 'INSERT INTO review (review_id, resource_id, submission_id, project_phase_id, scorecard_id, committed, score, initial_score, create_user, create_date, modify_user, modify_date) values (?,?,?,?,?,?,?,?,?,CURRENT,?,CURRENT)' + +/** +review_id is a new ID using idGenerator +resource_id, as near as I can tell, should be the universal Id of the resource, not the challenge or project specific IDs used for roles and resources. +submission_id, the most important item, which you should have. +project_phase_id, try null, or any phase that overlaps in time with the challenge phase the reviews are submitted for. I can't find any logic or constraints for this field. +scorecard_id, the second most important item, which you should have. +committed, in the case of a TopGear batch review submission should be true. +score and initial_score are usually aggregated by OR and AP, but you could aggregate them yourself, according to the scorecard weights. Try having AP do it for you. +create_user should be some generic API user name that tells us it was a TopGear batch upload. +create_date should be now (use CURRENT for Informix SQL if you want). +modify_user and modify_date can be null (or just don't include those in the field list). +The ? are just placeholders for your values. I believe Informix uses a single quote for strings. + */ + +const QUERY_CREATE_REVIEW_ITEM = 'INSERT INTO review_item (review_item_id, review_id, scorecard_question_id, upload_id, answer, sort, create_user, create_date, modify_user, modify_date) values (?,?,?,?,?,?,?,CURRENT,?,CURRENT)' + +/* +review_item_id, use your idGenerator ID. +review_id, the review ID from the previous query. +scorecard_question_id, the most important field, which you should have. +upload_id, this is specific to the submission_id, although you could have multiple uploads and you will want the upload_id, which TopGear should have if someone answered a question about a submission, because the submission must have been downloaded from somewhere. +answer, usually a number. +sort, use a constant here if you wish. From what I can tell, this would be used to display certain comments at the top for review appeals. It may have a default sort from the scorecard definition, but it's probably not important since people won't be looking at individual reviews much, I'm guessing. +Same as above for create_user, etc. +*/ + +const QUERY_GET_SUBMISSION = 'SELECT FIRST 1 * FROM submission s INNER JOIN upload u on s.upload_id = u.upload_id WHERE u.project_id = %d AND upload_status_id = 1 AND submission_status_id = 1 ORDER BY u.CREATE_DATE ASC' + +const QUERY_GET_PROJECT_PHASE = 'select pc.parameter scorecard_id, pp.project_phase_id project_phase_id from project_phase pp inner join phase_criteria pc on pc.project_phase_id = pp.project_phase_id where pp.project_id = %d and pp.phase_type_id = 18 and phase_criteria_type_id = 1' + +/** + * Prepare Informix statement + * @param {Object} connection the Informix connection + * @param {String} sql the sql + * @return {Object} Informix statement + */ +async function prepare (connection, sql) { + // logger.debug(`Preparing SQL ${sql}`) + const stmt = await connection.prepareAsync(sql) + return Promise.promisifyAll(stmt) +} + +/** + * Insert review in IFX + * @param {Number} challengeLegacyId the legacy challenge ID + * @param {Number} createdBy the scorecard ID + * @param {Number} score the review score + * @param {Array} responses the review responses + * @param {Number} createdBy the creator user ID + */ +async function insertReview (challengeLegacyId, scorecardId, score, responses, createdBy) { + const connection = await helper.getInformixConnection() + let result = null + let reviewId + try { + const resourceId = await getIterativeReviewerResourceId(connection, challengeLegacyId) + if (!resourceId) throw new Error('Cannot find Iterative Reviewer') + const submissionId = await getSubmissionId(connection, challengeLegacyId) + if (!submissionId) throw new Error('Cannot find Submission') + const projectPhaseId = await getProjectPhaseId(connection, challengeLegacyId) + if (!projectPhaseId) throw new Error('Cannot find Project Phase Id') + reviewId = await reviewIdGen.getNextId() + await connection.beginTransactionAsync() + const query = await prepare(connection, QUERY_CREATE_REVIEW) + result = await query.executeAsync([reviewId, resourceId, submissionId, projectPhaseId, scorecardId, 1, score, score, createdBy, createdBy]) + for (let i = 0; i < responses.length; i += 1) { + await insertReviewItem(connection, reviewId, responses[i], i, createdBy) + } + await connection.commitTransactionAsync() + } catch (e) { + logger.error(`Error in 'insertReview' ${e}, rolling back transaction`) + await connection.rollbackTransactionAsync() + throw e + } finally { + logger.info(`Review ${challengeLegacyId} has been created`) + await connection.closeAsync() + } + return result +} + +/** + * Insert review item in IFX + * @param {Object} connection + * @param {Number} reviewId the review ID + * @param {Object} response the response + * @param {Number} sort the sort + * @param {Number} createdBy the creator user ID + */ +async function insertReviewItem (connection, reviewId, response, sort, createdBy) { + let result = null + const reviewItemId = await reviewItemIdGen.getNextId() + await connection.beginTransactionAsync() + const query = await prepare(connection, QUERY_CREATE_REVIEW_ITEM) + result = await query.executeAsync([reviewItemId, reviewId, response.questionId, null, response.answer, sort, createdBy, createdBy]) + return result +} + +/** + * Gets the iterative reviewer resource id + * @param {Object} connection + * @param {Number} challengeLegacyId + */ +async function getIterativeReviewerResourceId (connection, challengeLegacyId) { + const result = await connection.queryAsync(util.format(QUERY_GET_ITERATIVE_REVIEW_RESOURCE_FOR_CHALLENGE, challengeLegacyId)) + return _.get(result, '[0].resourceid', null) +} + +/** + * Gets the submission id + * @param {Object} connection + * @param {Number} challengeLegacyId + */ +async function getSubmissionId (connection, challengeLegacyId) { + const result = await connection.queryAsync(util.format(QUERY_GET_SUBMISSION, challengeLegacyId)) + return _.get(result, '[0].submission_id', null) +} + +/** + * Gets the submission id + * @param {Object} connection + * @param {Number} challengeLegacyId + */ +async function getProjectPhaseId (connection, challengeLegacyId) { + const result = await connection.queryAsync(util.format(QUERY_GET_PROJECT_PHASE, challengeLegacyId)) + return _.get(result, '[0].project_phase_id', null) +} + +module.exports = { + insertReview +} From e0ff01f21e15d621fef11608b39d4529da77dbc6 Mon Sep 17 00:00:00 2001 From: Thomas Kranitsas Date: Mon, 29 Aug 2022 16:29:24 +0300 Subject: [PATCH 03/30] clean up --- src/services/legacyChallengeReviewService.js | 24 -------------------- 1 file changed, 24 deletions(-) diff --git a/src/services/legacyChallengeReviewService.js b/src/services/legacyChallengeReviewService.js index a7bed13..f003f0a 100644 --- a/src/services/legacyChallengeReviewService.js +++ b/src/services/legacyChallengeReviewService.js @@ -16,32 +16,8 @@ const QUERY_GET_ITERATIVE_REVIEW_RESOURCE_FOR_CHALLENGE = `SELECT limit 1 resour const QUERY_CREATE_REVIEW = 'INSERT INTO review (review_id, resource_id, submission_id, project_phase_id, scorecard_id, committed, score, initial_score, create_user, create_date, modify_user, modify_date) values (?,?,?,?,?,?,?,?,?,CURRENT,?,CURRENT)' -/** -review_id is a new ID using idGenerator -resource_id, as near as I can tell, should be the universal Id of the resource, not the challenge or project specific IDs used for roles and resources. -submission_id, the most important item, which you should have. -project_phase_id, try null, or any phase that overlaps in time with the challenge phase the reviews are submitted for. I can't find any logic or constraints for this field. -scorecard_id, the second most important item, which you should have. -committed, in the case of a TopGear batch review submission should be true. -score and initial_score are usually aggregated by OR and AP, but you could aggregate them yourself, according to the scorecard weights. Try having AP do it for you. -create_user should be some generic API user name that tells us it was a TopGear batch upload. -create_date should be now (use CURRENT for Informix SQL if you want). -modify_user and modify_date can be null (or just don't include those in the field list). -The ? are just placeholders for your values. I believe Informix uses a single quote for strings. - */ - const QUERY_CREATE_REVIEW_ITEM = 'INSERT INTO review_item (review_item_id, review_id, scorecard_question_id, upload_id, answer, sort, create_user, create_date, modify_user, modify_date) values (?,?,?,?,?,?,?,CURRENT,?,CURRENT)' -/* -review_item_id, use your idGenerator ID. -review_id, the review ID from the previous query. -scorecard_question_id, the most important field, which you should have. -upload_id, this is specific to the submission_id, although you could have multiple uploads and you will want the upload_id, which TopGear should have if someone answered a question about a submission, because the submission must have been downloaded from somewhere. -answer, usually a number. -sort, use a constant here if you wish. From what I can tell, this would be used to display certain comments at the top for review appeals. It may have a default sort from the scorecard definition, but it's probably not important since people won't be looking at individual reviews much, I'm guessing. -Same as above for create_user, etc. -*/ - const QUERY_GET_SUBMISSION = 'SELECT FIRST 1 * FROM submission s INNER JOIN upload u on s.upload_id = u.upload_id WHERE u.project_id = %d AND upload_status_id = 1 AND submission_status_id = 1 ORDER BY u.CREATE_DATE ASC' const QUERY_GET_PROJECT_PHASE = 'select pc.parameter scorecard_id, pp.project_phase_id project_phase_id from project_phase pp inner join phase_criteria pc on pc.project_phase_id = pp.project_phase_id where pp.project_id = %d and pp.phase_type_id = 18 and phase_criteria_type_id = 1' From 0fa9d9bea65bf2fcc2ef1a0d2b44de5d35a143f8 Mon Sep 17 00:00:00 2001 From: Thomas Kranitsas Date: Tue, 30 Aug 2022 13:11:34 +0300 Subject: [PATCH 04/30] insert reviews in ifx when the iterative review phase is open --- src/services/ProcessorService.js | 40 ++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/services/ProcessorService.js b/src/services/ProcessorService.js index c5698e1..8a75f06 100644 --- a/src/services/ProcessorService.js +++ b/src/services/ProcessorService.js @@ -699,6 +699,28 @@ async function processMessage (message) { throw new Error(`Error getting challenge by id - Error: ${JSON.stringify(e)}`) } + // If iterative review is open + if (_.find(_.get(message.payload, 'phases'), p => p.isOpen && p.name === 'Iterative Review')) { + // Try to read reviews and insert them into informix DB + if (message.payload.metadata && message.payload.legacy.reviewScorecardId) { + let orReviewFeedback = _.find(message.payload.metadata, meta => meta.name === 'or_review_feedback') + let orReviewScore = _.find(message.payload.metadata, meta => meta.name === 'or_review_score') + if (!_.isUndefined(orReviewFeedback) && !_.isUndefined(orReviewScore)) { + orReviewFeedback = JSON.parse(orReviewFeedback) + const reviewResponses = [] + _.each(orReviewFeedback, (value, key) => { + const questionId = _.get(_.find(constants.scorecardQuestionMapping[message.payload.legacy.reviewScorecardId], item => _.toString(item.questionId) === _.toString(key) || _.toLower(item.description) === _.toLower(key)), 'questionId') + reviewResponses.push({ + questionId, + answer: value + }) + }) + orReviewScore = _.toNumber(orReviewFeedback) + await legacyChallengeReviewService.insertReview(legacyId, message.payload.legacy.reviewScorecardId, orReviewScore, reviewResponses, createdByUserId) + } + } + } + if (message.payload.status && challenge) { // Whether we need to sync v4 ES again let needSyncV4ES = false @@ -714,24 +736,6 @@ async function processMessage (message) { needSyncV4ES = true } if (message.payload.status === constants.challengeStatuses.Completed && challenge.currentStatus !== constants.challengeStatuses.Completed) { - // Try to read reviews and insert them into informix DB - if (message.payload.metadata && message.payload.legacy.reviewScorecardId) { - let orReviewFeedback = _.find(message.payload.metadata, meta => meta.name === 'or_review_feedback') - let orReviewScore = _.find(message.payload.metadata, meta => meta.name === 'or_review_score') - if (!_.isUndefined(orReviewFeedback) && !_.isUndefined(orReviewScore)) { - orReviewFeedback = JSON.parse(orReviewFeedback) - const reviewResponses = [] - _.each(orReviewFeedback, (value, key) => { - const questionId = _.get(_.find(constants.scorecardQuestionMapping[message.payload.legacy.reviewScorecardId], item => _.toString(item.questionId) === _.toString(key) || _.toLower(item.description) === _.toLower(key)), 'questionId') - reviewResponses.push({ - questionId, - answer: value - }) - }) - orReviewScore = _.toNumber(orReviewFeedback) - await legacyChallengeReviewService.insertReview(legacyId, message.payload.legacy.reviewScorecardId, orReviewScore, reviewResponses, createdByUserId) - } - } if (message.payload.task.isTask) { logger.info('Challenge is a TASK') if (!message.payload.winners || message.payload.winners.length === 0) { From 0959e0b38fb92d9125a1ce744651dabda11b9177 Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Fri, 9 Sep 2022 15:40:10 +1000 Subject: [PATCH 05/30] Test for phase syncing for multi-round challenges --- src/constants.js | 7 +++++++ src/services/ProcessorService.js | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/src/constants.js b/src/constants.js index 0e848a1..422afb0 100644 --- a/src/constants.js +++ b/src/constants.js @@ -71,6 +71,12 @@ const PhaseStatusTypes = { Closed: 3 } +const CheckpointPhaseTypes = { + Submission: 'Checkpoint Submission', + Screening: 'Checkpoint Screening', + Review: 'Checkpoint Review', +} + const prizeTypesIds = { Contest: 15, Checkpoint: 14 @@ -166,6 +172,7 @@ module.exports = { createChallengeStatusesMap, challengeStatuses, PhaseStatusTypes, + CheckpointPhaseTypes, prizeTypesIds, supportedMetadata, scorecardQuestionMapping diff --git a/src/services/ProcessorService.js b/src/services/ProcessorService.js index 8a75f06..1051d22 100644 --- a/src/services/ProcessorService.js +++ b/src/services/ProcessorService.js @@ -658,6 +658,11 @@ async function processMessage (message) { if (_.get(message, 'payload.legacy.selfService')) { await disableTimelineNotifications(legacyId, createdByUserId) // disable } + } else { + const v5PhaseType = _.find(message.payload.phases, p => p.name === constants.CheckpointPhaseTypes.Submission) + if (v5PhaseType) { + await recreatePhases(legacyId, message.payload.phases, updatedByUserId) + } } logger.debug('Result from parsePayload:') From bf7172b09d02be33b146b7c03a8617b6ec63d072 Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Tue, 13 Sep 2022 18:49:18 +1000 Subject: [PATCH 06/30] Revert "Test for phase syncing for multi-round challenges" This reverts commit 0959e0b38fb92d9125a1ce744651dabda11b9177. --- src/constants.js | 7 ------- src/services/ProcessorService.js | 5 ----- 2 files changed, 12 deletions(-) diff --git a/src/constants.js b/src/constants.js index 422afb0..0e848a1 100644 --- a/src/constants.js +++ b/src/constants.js @@ -71,12 +71,6 @@ const PhaseStatusTypes = { Closed: 3 } -const CheckpointPhaseTypes = { - Submission: 'Checkpoint Submission', - Screening: 'Checkpoint Screening', - Review: 'Checkpoint Review', -} - const prizeTypesIds = { Contest: 15, Checkpoint: 14 @@ -172,7 +166,6 @@ module.exports = { createChallengeStatusesMap, challengeStatuses, PhaseStatusTypes, - CheckpointPhaseTypes, prizeTypesIds, supportedMetadata, scorecardQuestionMapping diff --git a/src/services/ProcessorService.js b/src/services/ProcessorService.js index 1051d22..8a75f06 100644 --- a/src/services/ProcessorService.js +++ b/src/services/ProcessorService.js @@ -658,11 +658,6 @@ async function processMessage (message) { if (_.get(message, 'payload.legacy.selfService')) { await disableTimelineNotifications(legacyId, createdByUserId) // disable } - } else { - const v5PhaseType = _.find(message.payload.phases, p => p.name === constants.CheckpointPhaseTypes.Submission) - if (v5PhaseType) { - await recreatePhases(legacyId, message.payload.phases, updatedByUserId) - } } logger.debug('Result from parsePayload:') From ceb42609ed0de2c720405cb64bae2b7fe600d19d Mon Sep 17 00:00:00 2001 From: Thomas Kranitsas Date: Fri, 16 Sep 2022 20:45:19 +0300 Subject: [PATCH 07/30] follow v5 or time for phase.phase_status_id in legacy --- src/services/ProcessorService.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/services/ProcessorService.js b/src/services/ProcessorService.js index 1051d22..8b9f30c 100644 --- a/src/services/ProcessorService.js +++ b/src/services/ProcessorService.js @@ -95,13 +95,16 @@ async function syncChallengePhases (legacyId, v5Phases, createdBy, isSelfService // : (new Date().getTime() <= new Date(v5Equivalent.scheduledEndDate).getTime() ? constants.PhaseStatusTypes.Scheduled : constants.PhaseStatusTypes.Closed) // update phase logger.debug(`Will update phase ${phaseName}/${v5Equivalent.name} from ${phase.duration} to duration ${v5Equivalent.duration * 1000} milli`) + const newStatus = v5Equivalent.isOpen + ? constants.PhaseStatusTypes.Open + : (new Date().getTime() <= new Date(v5Equivalent.scheduledEndDate).getTime() ? constants.PhaseStatusTypes.Scheduled : constants.PhaseStatusTypes.Closed) await timelineService.updatePhase( phase.project_phase_id, legacyId, v5Equivalent.scheduledStartDate, v5Equivalent.scheduledEndDate, v5Equivalent.duration * 1000, - phase.phase_status_id + newStatus ) // newStatus) // } else { From 07e9f1454b6eea827037f3fd3ffa521fd625d5b5 Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Wed, 28 Sep 2022 16:09:09 +1000 Subject: [PATCH 08/30] Test fix for failing multi-round sync issue --- src/services/timelineService.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/services/timelineService.js b/src/services/timelineService.js index 81a2d0b..e5ffec3 100644 --- a/src/services/timelineService.js +++ b/src/services/timelineService.js @@ -15,7 +15,7 @@ const QUERY_GET_PHASE_TYPES = 'SELECT phase_type_id, name FROM phase_type_lu' const QUERY_GET_CHALLENGE_PHASES = 'SELECT project_phase_id, scheduled_start_time, scheduled_end_time, duration, phase_status_id, phase_type_id FROM project_phase WHERE project_id = %d' const QUERY_DROP_CHALLENGE_PHASE = 'DELETE FROM project_phase WHERE project_id = ? AND project_phase_id = ?' -const QUERY_INSERT_CHALLENGE_PHASE = 'INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, fixed_start_time, scheduled_start_time, scheduled_end_time, actual_start_time, actual_end_time, duration, create_user, create_date, modify_user, modify_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT, ?, CURRENT)' +const QUERY_INSERT_CHALLENGE_PHASE = 'INSERT INTO project_phase (project_id, phase_type_id, phase_status_id, fixed_start_time, scheduled_start_time, scheduled_end_time, actual_start_time, actual_end_time, duration, create_user, create_date, modify_user, modify_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT, ?, CURRENT)' const QUERY_UPDATE_CHALLENGE_PHASE = 'UPDATE project_phase SET scheduled_start_time = ?, scheduled_end_time = ?, duration = ?, phase_status_id = ? WHERE project_phase_id = %d and project_id = %d' const QUERY_DROP_CHALLENGE_PHASE_CRITERIA = 'DELETE FROM phase_criteria WHERE project_phase_id = ?' @@ -128,7 +128,6 @@ async function createPhase (challengeLegacyId, phaseTypeId, statusTypeId, schedu await connection.beginTransactionAsync() const query = await prepare(connection, QUERY_INSERT_CHALLENGE_PHASE) logger.debug(`Query data: ${JSON.stringify([ - nextId, challengeLegacyId, phaseTypeId, statusTypeId, @@ -142,7 +141,6 @@ async function createPhase (challengeLegacyId, phaseTypeId, statusTypeId, schedu createdBy ])}`) result = await query.executeAsync([ - nextId, challengeLegacyId, phaseTypeId, statusTypeId, From 25bfb2bd2c6059aad09dd91aad284798730bf4cf Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Wed, 28 Sep 2022 16:27:31 +1000 Subject: [PATCH 09/30] Use the *correct* sequence generator when adding new project phases. --- src/services/timelineService.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/services/timelineService.js b/src/services/timelineService.js index e5ffec3..04ce9bb 100644 --- a/src/services/timelineService.js +++ b/src/services/timelineService.js @@ -9,13 +9,13 @@ const momentTZ = require('moment-timezone') const IDGenerator = require('../common/idGenerator') const helper = require('../common/helper') -const phaseIdGen = new IDGenerator('prize_id_seq') +const phaseIdGen = new IDGenerator('project_phase_id_seq') const QUERY_GET_PHASE_TYPES = 'SELECT phase_type_id, name FROM phase_type_lu' const QUERY_GET_CHALLENGE_PHASES = 'SELECT project_phase_id, scheduled_start_time, scheduled_end_time, duration, phase_status_id, phase_type_id FROM project_phase WHERE project_id = %d' const QUERY_DROP_CHALLENGE_PHASE = 'DELETE FROM project_phase WHERE project_id = ? AND project_phase_id = ?' -const QUERY_INSERT_CHALLENGE_PHASE = 'INSERT INTO project_phase (project_id, phase_type_id, phase_status_id, fixed_start_time, scheduled_start_time, scheduled_end_time, actual_start_time, actual_end_time, duration, create_user, create_date, modify_user, modify_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT, ?, CURRENT)' +const QUERY_INSERT_CHALLENGE_PHASE = 'INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, fixed_start_time, scheduled_start_time, scheduled_end_time, actual_start_time, actual_end_time, duration, create_user, create_date, modify_user, modify_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT, ?, CURRENT)' const QUERY_UPDATE_CHALLENGE_PHASE = 'UPDATE project_phase SET scheduled_start_time = ?, scheduled_end_time = ?, duration = ?, phase_status_id = ? WHERE project_phase_id = %d and project_id = %d' const QUERY_DROP_CHALLENGE_PHASE_CRITERIA = 'DELETE FROM phase_criteria WHERE project_phase_id = ?' @@ -128,6 +128,7 @@ async function createPhase (challengeLegacyId, phaseTypeId, statusTypeId, schedu await connection.beginTransactionAsync() const query = await prepare(connection, QUERY_INSERT_CHALLENGE_PHASE) logger.debug(`Query data: ${JSON.stringify([ + nextId, challengeLegacyId, phaseTypeId, statusTypeId, @@ -141,6 +142,7 @@ async function createPhase (challengeLegacyId, phaseTypeId, statusTypeId, schedu createdBy ])}`) result = await query.executeAsync([ + nextId, challengeLegacyId, phaseTypeId, statusTypeId, From b0976a52b3b8f2fa007ba42a686e31b9fc163c6e Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Thu, 29 Sep 2022 16:34:23 +1000 Subject: [PATCH 10/30] =?UTF-8?q?Don=E2=80=99t=20set=20fixed=20start=20tim?= =?UTF-8?q?e,=20actual=20start=20time,=20or=20actual=20end=20time.=20=20OR?= =?UTF-8?q?=20will=20throw=20errors=20if=20you=20do?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/services/timelineService.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/services/timelineService.js b/src/services/timelineService.js index 04ce9bb..bd58075 100644 --- a/src/services/timelineService.js +++ b/src/services/timelineService.js @@ -15,7 +15,7 @@ const QUERY_GET_PHASE_TYPES = 'SELECT phase_type_id, name FROM phase_type_lu' const QUERY_GET_CHALLENGE_PHASES = 'SELECT project_phase_id, scheduled_start_time, scheduled_end_time, duration, phase_status_id, phase_type_id FROM project_phase WHERE project_id = %d' const QUERY_DROP_CHALLENGE_PHASE = 'DELETE FROM project_phase WHERE project_id = ? AND project_phase_id = ?' -const QUERY_INSERT_CHALLENGE_PHASE = 'INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, fixed_start_time, scheduled_start_time, scheduled_end_time, actual_start_time, actual_end_time, duration, create_user, create_date, modify_user, modify_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT, ?, CURRENT)' +const QUERY_INSERT_CHALLENGE_PHASE = 'INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT, ?, CURRENT)' const QUERY_UPDATE_CHALLENGE_PHASE = 'UPDATE project_phase SET scheduled_start_time = ?, scheduled_end_time = ?, duration = ?, phase_status_id = ? WHERE project_phase_id = %d and project_id = %d' const QUERY_DROP_CHALLENGE_PHASE_CRITERIA = 'DELETE FROM phase_criteria WHERE project_phase_id = ?' @@ -133,10 +133,7 @@ async function createPhase (challengeLegacyId, phaseTypeId, statusTypeId, schedu phaseTypeId, statusTypeId, formatDate(scheduledStartDate), - formatDate(scheduledStartDate), formatDate(scheduledEndDate), - formatDate(actualStartDate), - formatDate(actualEndDate), duration, createdBy, createdBy @@ -147,10 +144,7 @@ async function createPhase (challengeLegacyId, phaseTypeId, statusTypeId, schedu phaseTypeId, statusTypeId, formatDate(scheduledStartDate), - formatDate(scheduledStartDate), formatDate(scheduledEndDate), - formatDate(actualStartDate), - formatDate(actualEndDate), duration, createdBy, createdBy From 2ffea42550f9b250fdc4ae23cefdddba3f0112cf Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Mon, 3 Oct 2022 09:43:42 +1100 Subject: [PATCH 11/30] Fix query when inserting challenge phases --- src/services/timelineService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/timelineService.js b/src/services/timelineService.js index bd58075..0793e25 100644 --- a/src/services/timelineService.js +++ b/src/services/timelineService.js @@ -15,7 +15,7 @@ const QUERY_GET_PHASE_TYPES = 'SELECT phase_type_id, name FROM phase_type_lu' const QUERY_GET_CHALLENGE_PHASES = 'SELECT project_phase_id, scheduled_start_time, scheduled_end_time, duration, phase_status_id, phase_type_id FROM project_phase WHERE project_id = %d' const QUERY_DROP_CHALLENGE_PHASE = 'DELETE FROM project_phase WHERE project_id = ? AND project_phase_id = ?' -const QUERY_INSERT_CHALLENGE_PHASE = 'INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT, ?, CURRENT)' +const QUERY_INSERT_CHALLENGE_PHASE = 'INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT, ?, CURRENT)' const QUERY_UPDATE_CHALLENGE_PHASE = 'UPDATE project_phase SET scheduled_start_time = ?, scheduled_end_time = ?, duration = ?, phase_status_id = ? WHERE project_phase_id = %d and project_id = %d' const QUERY_DROP_CHALLENGE_PHASE_CRITERIA = 'DELETE FROM phase_criteria WHERE project_phase_id = ?' From e6a6f6e6c344731befdf39e66608f4951c2a7605 Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Mon, 3 Oct 2022 11:04:50 +1100 Subject: [PATCH 12/30] Add in phase dependencies test --- src/services/timelineService.js | 47 +++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/services/timelineService.js b/src/services/timelineService.js index 0793e25..c7ac1b6 100644 --- a/src/services/timelineService.js +++ b/src/services/timelineService.js @@ -25,6 +25,8 @@ const QUERY_GET_TIMELINE_NOTIFICATION_SETTINGS = 'SELECT value FROM project_info const QUERY_CREATE_TIMELINE_NOTIFICATIONS = 'INSERT INTO project_info (project_id, project_info_type_id, value, create_user, create_date, modify_user, modify_date) VALUES (?, "11", "On", ?, CURRENT, ?, CURRENT)' const QUERY_UPDATE_TIMELINE_NOTIFICATIONS = 'UPDATE project_info SET value = "On", modify_user = ?, modify_date = CURRENT WHERE project_info_type_id = "11" AND project_id = ?' +const QUERY_INSERT_CHALLENGE_PHASE_DEPENDENCY = 'INSERT INTO phase_dependency (dependency_phase_id, dependent_phase_id, dependency_start, dependent_start, lag_time, create_user, create_date, modify_user, modify_date) VALUES (?, ?, ?, 1, 0, ?, CURRENT, ?, CURRENT)' +const QUERY_GET_PROJECT_PHASE_ID = 'SELECT project_phase_id FROM project_phase WHERE project_id = ? AND phase_type_id = ?' /** * Formats a date into a format supported by ifx * @param {String} dateStr the date in string format @@ -64,6 +66,38 @@ async function getPhaseTypes () { return result } +async function insertPhaseDependency(dependencyPhaseId, dependentPhaseId, dependencyStart, createdBy){ + const connection = await helper.getInformixConnection() + let result = null + try { + let query = await prepare(connection, QUERY_INSERT_CHALLENGE_PHASE_DEPENDENCY) + result = await query.executeAsync([dependencyPhaseId, dependentPhaseId, dependencyStart, createdBy, createdBy]) + } catch (e) { + logger.error(`Error in 'insertPhaseDependency' ${e}`) + throw e + } finally { + await connection.closeAsync() + } + return result +} +/** + * Gets phase for the given phase type for the given challenge ID + */ +async function getProjectPhaseId(challengeLegacyId, phaseTypeId) { + const connection = await helper.getInformixConnection() + let result = null + try { + await connection.beginTransactionAsync() + let query = await prepare(connection, QUERY_GET_PROJECT_PHASE_ID) + result = await query.executeAsync([challengeLegacyId, phaseTypeId]) + } catch (e) { + logger.error(`Error in 'getProjectPhaseId' ${e}`) + throw e + } finally { + await connection.closeAsync() + } + return result +} /** * Drop challenge phase * @param {Number} challengeLegacyId the legacy challenge ID @@ -150,6 +184,19 @@ async function createPhase (challengeLegacyId, phaseTypeId, statusTypeId, schedu createdBy ]) await connection.commitTransactionAsync() + + //Handle checkpoint phases + //Magic numbers: 15=checkpoint submission, 16=checkpoint screen, 17=checkpoint review, 1=registration + //For dependencyStart: 1=start, 0=end + if(phaseTypeId==17){ + registrationPhaseId = await this.getProjectPhaseId(challengeLegacyId, 1) + checkpointSubmissionPhaseId = await this.getProjectPhaseId(challengeLegacyId, 15) + checkpointScreeningPhaseId = await this.getProjectPhaseId(challengeLegacyId, 16) + checkpointReviewPhaseId = await this.getProjectPhaseId(challengeLegacyId, 17) + await this.insertPhaseDependency(registrationPhaseId, checkpointSubmissionPhaseId, 1, createdBy) + await this.insertPhaseDependency(checkpointSubmissionPhaseId, checkpointScreeningPhaseId, 0, createdBy) + await this.insertPhaseDependency(checkpointScreeningPhaseId, checkpointReviewPhaseId, 0, createdBy) + } } catch (e) { logger.error(`Error in 'createPhase' ${e}, rolling back transaction`) await connection.rollbackTransactionAsync() From 66d04bc2cc051416bcf54119ad45aae0d05dfa4f Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Mon, 3 Oct 2022 11:34:26 +1100 Subject: [PATCH 13/30] Restructuring and some additional logging --- src/services/ProcessorService.js | 15 +++++++++++++++ src/services/timelineService.js | 18 +++++------------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/services/ProcessorService.js b/src/services/ProcessorService.js index 4af58d2..fbe7831 100644 --- a/src/services/ProcessorService.js +++ b/src/services/ProcessorService.js @@ -52,6 +52,21 @@ async function recreatePhases (legacyId, v5Phases, createdBy) { phase.duration * 1000, createdBy ) + //Handle checkpoint phases + //Magic numbers: 15=checkpoint submission, 16=checkpoint screen, 17=checkpoint review, 1=registration + //For dependencyStart: 1=start, 0=end + if(phaseLegacyId==17){ + logger.info(`Creating phase dependencies for checkpoint phases`) + + registrationPhaseId = await timelineService.getProjectPhaseId(legacyId, 1) + checkpointSubmissionPhaseId = await timelineService.getProjectPhaseId(legacyId, 15) + checkpointScreeningPhaseId = await timelineService.getProjectPhaseId(legacyId, 16) + checkpointReviewPhaseId = await timelineService.getProjectPhaseId(legacyId, 17) + + await timelineService.insertPhaseDependency(registrationPhaseId, checkpointSubmissionPhaseId, 1, createdBy) + await timelineService.insertPhaseDependency(checkpointSubmissionPhaseId, checkpointScreeningPhaseId, 0, createdBy) + await timelineService.insertPhaseDependency(checkpointScreeningPhaseId, checkpointReviewPhaseId, 0, createdBy) + } } else if (!phaseLegacyId) { logger.warn(`Could not create phase ${phase.name} on legacy!`) } diff --git a/src/services/timelineService.js b/src/services/timelineService.js index c7ac1b6..0bf305a 100644 --- a/src/services/timelineService.js +++ b/src/services/timelineService.js @@ -67,6 +67,8 @@ async function getPhaseTypes () { } async function insertPhaseDependency(dependencyPhaseId, dependentPhaseId, dependencyStart, createdBy){ + + logger.info(`Creating phase dependency ${dependencyPhaseId} to ${dependentPhaseId} at ${dependencyStart}`) const connection = await helper.getInformixConnection() let result = null try { @@ -185,18 +187,6 @@ async function createPhase (challengeLegacyId, phaseTypeId, statusTypeId, schedu ]) await connection.commitTransactionAsync() - //Handle checkpoint phases - //Magic numbers: 15=checkpoint submission, 16=checkpoint screen, 17=checkpoint review, 1=registration - //For dependencyStart: 1=start, 0=end - if(phaseTypeId==17){ - registrationPhaseId = await this.getProjectPhaseId(challengeLegacyId, 1) - checkpointSubmissionPhaseId = await this.getProjectPhaseId(challengeLegacyId, 15) - checkpointScreeningPhaseId = await this.getProjectPhaseId(challengeLegacyId, 16) - checkpointReviewPhaseId = await this.getProjectPhaseId(challengeLegacyId, 17) - await this.insertPhaseDependency(registrationPhaseId, checkpointSubmissionPhaseId, 1, createdBy) - await this.insertPhaseDependency(checkpointSubmissionPhaseId, checkpointScreeningPhaseId, 0, createdBy) - await this.insertPhaseDependency(checkpointScreeningPhaseId, checkpointReviewPhaseId, 0, createdBy) - } } catch (e) { logger.error(`Error in 'createPhase' ${e}, rolling back transaction`) await connection.rollbackTransactionAsync() @@ -291,5 +281,7 @@ module.exports = { updatePhase, enableTimelineNotifications, createPhase, - dropPhase + dropPhase, + insertPhaseDependency, + getProjectPhaseId } From 87aa097f9091ff092b3a81e577a05550686c03d7 Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Mon, 3 Oct 2022 12:03:19 +1100 Subject: [PATCH 14/30] Better return value, not an ODBCResult --- src/services/timelineService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/timelineService.js b/src/services/timelineService.js index 0bf305a..e68dab7 100644 --- a/src/services/timelineService.js +++ b/src/services/timelineService.js @@ -98,7 +98,7 @@ async function getProjectPhaseId(challengeLegacyId, phaseTypeId) { } finally { await connection.closeAsync() } - return result + return _.get(result, '[0].project_phase_id', null) } /** * Drop challenge phase From dec565099ecd99c4b1495e5fb75a863db7577c76 Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Mon, 3 Oct 2022 12:25:50 +1100 Subject: [PATCH 15/30] Missing ref --- src/services/timelineService.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/services/timelineService.js b/src/services/timelineService.js index e68dab7..1484e99 100644 --- a/src/services/timelineService.js +++ b/src/services/timelineService.js @@ -2,6 +2,8 @@ * Timeline Service * Interacts with InformixDB */ +const _ = require('lodash') + const logger = require('../common/logger') const util = require('util') const config = require('config') From 6e8207b72a73fc7648fa13e253079fcd6c1bc705 Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Mon, 3 Oct 2022 12:48:02 +1100 Subject: [PATCH 16/30] Better logging --- src/services/timelineService.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/timelineService.js b/src/services/timelineService.js index 1484e99..99b3711 100644 --- a/src/services/timelineService.js +++ b/src/services/timelineService.js @@ -88,6 +88,7 @@ async function insertPhaseDependency(dependencyPhaseId, dependentPhaseId, depend * Gets phase for the given phase type for the given challenge ID */ async function getProjectPhaseId(challengeLegacyId, phaseTypeId) { + logger.info(`Getting project phase ID type ${phaseTypeId} for challenge ${challengeLegacyId}`) const connection = await helper.getInformixConnection() let result = null try { From af80491356bfeeb89a9455fb505e462870457b82 Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Mon, 3 Oct 2022 14:17:10 +1100 Subject: [PATCH 17/30] Additional formality and logging --- src/services/ProcessorService.js | 8 ++++---- src/services/timelineService.js | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/services/ProcessorService.js b/src/services/ProcessorService.js index fbe7831..257c713 100644 --- a/src/services/ProcessorService.js +++ b/src/services/ProcessorService.js @@ -58,10 +58,10 @@ async function recreatePhases (legacyId, v5Phases, createdBy) { if(phaseLegacyId==17){ logger.info(`Creating phase dependencies for checkpoint phases`) - registrationPhaseId = await timelineService.getProjectPhaseId(legacyId, 1) - checkpointSubmissionPhaseId = await timelineService.getProjectPhaseId(legacyId, 15) - checkpointScreeningPhaseId = await timelineService.getProjectPhaseId(legacyId, 16) - checkpointReviewPhaseId = await timelineService.getProjectPhaseId(legacyId, 17) + const registrationPhaseId = await timelineService.getProjectPhaseId(legacyId, 1) + const checkpointSubmissionPhaseId = await timelineService.getProjectPhaseId(legacyId, 15) + const checkpointScreeningPhaseId = await timelineService.getProjectPhaseId(legacyId, 16) + const checkpointReviewPhaseId = await timelineService.getProjectPhaseId(legacyId, 17) await timelineService.insertPhaseDependency(registrationPhaseId, checkpointSubmissionPhaseId, 1, createdBy) await timelineService.insertPhaseDependency(checkpointSubmissionPhaseId, checkpointScreeningPhaseId, 0, createdBy) diff --git a/src/services/timelineService.js b/src/services/timelineService.js index 99b3711..e2c6fd6 100644 --- a/src/services/timelineService.js +++ b/src/services/timelineService.js @@ -101,7 +101,9 @@ async function getProjectPhaseId(challengeLegacyId, phaseTypeId) { } finally { await connection.closeAsync() } - return _.get(result, '[0].project_phase_id', null) + const project_phase_id = _.get(result, '[0].project_phase_id', null) + logger.info(`Project phase ID: ${project_phase_id}`) + return project_phase_id } /** * Drop challenge phase From 1786aff87fdb6575c09baa21eeef98a31cf1604c Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Mon, 3 Oct 2022 14:32:49 +1100 Subject: [PATCH 18/30] Update to fix this query result --- src/services/timelineService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/timelineService.js b/src/services/timelineService.js index e2c6fd6..7dd2bb1 100644 --- a/src/services/timelineService.js +++ b/src/services/timelineService.js @@ -101,7 +101,7 @@ async function getProjectPhaseId(challengeLegacyId, phaseTypeId) { } finally { await connection.closeAsync() } - const project_phase_id = _.get(result, '[0].project_phase_id', null) + const project_phase_id = _.get(result, '[0]', null) logger.info(`Project phase ID: ${project_phase_id}`) return project_phase_id } From 806d0063191ab04e43a3ef0229002784c1218a84 Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Mon, 3 Oct 2022 15:04:02 +1100 Subject: [PATCH 19/30] Additional logging --- src/services/timelineService.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/timelineService.js b/src/services/timelineService.js index 7dd2bb1..a9f2083 100644 --- a/src/services/timelineService.js +++ b/src/services/timelineService.js @@ -101,6 +101,7 @@ async function getProjectPhaseId(challengeLegacyId, phaseTypeId) { } finally { await connection.closeAsync() } + logger.info(`Query result: ${result}`) const project_phase_id = _.get(result, '[0]', null) logger.info(`Project phase ID: ${project_phase_id}`) return project_phase_id From 58c6a367bdc1ded8aa4d6032b976eaedb3105305 Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Mon, 3 Oct 2022 15:32:39 +1100 Subject: [PATCH 20/30] Try something different --- src/services/timelineService.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/services/timelineService.js b/src/services/timelineService.js index a9f2083..864ab02 100644 --- a/src/services/timelineService.js +++ b/src/services/timelineService.js @@ -28,7 +28,7 @@ const QUERY_CREATE_TIMELINE_NOTIFICATIONS = 'INSERT INTO project_info (project_i const QUERY_UPDATE_TIMELINE_NOTIFICATIONS = 'UPDATE project_info SET value = "On", modify_user = ?, modify_date = CURRENT WHERE project_info_type_id = "11" AND project_id = ?' const QUERY_INSERT_CHALLENGE_PHASE_DEPENDENCY = 'INSERT INTO phase_dependency (dependency_phase_id, dependent_phase_id, dependency_start, dependent_start, lag_time, create_user, create_date, modify_user, modify_date) VALUES (?, ?, ?, 1, 0, ?, CURRENT, ?, CURRENT)' -const QUERY_GET_PROJECT_PHASE_ID = 'SELECT project_phase_id FROM project_phase WHERE project_id = ? AND phase_type_id = ?' +const QUERY_GET_PROJECT_PHASE_ID = 'SELECT project_phase_id FROM project_phase WHERE project_id = %d AND phase_type_id = %d' /** * Formats a date into a format supported by ifx * @param {String} dateStr the date in string format @@ -93,8 +93,7 @@ async function getProjectPhaseId(challengeLegacyId, phaseTypeId) { let result = null try { await connection.beginTransactionAsync() - let query = await prepare(connection, QUERY_GET_PROJECT_PHASE_ID) - result = await query.executeAsync([challengeLegacyId, phaseTypeId]) + result = await connection.queryAsync(util.format(QUERY_GET_PROJECT_PHASE_ID, challengeLegacyId, phaseTypeId)) } catch (e) { logger.error(`Error in 'getProjectPhaseId' ${e}`) throw e From c927f79b6a55ccf3dac695bb8795488be403fd3b Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Mon, 3 Oct 2022 16:01:48 +1100 Subject: [PATCH 21/30] Tweaks to the query structure --- src/services/timelineService.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/timelineService.js b/src/services/timelineService.js index 864ab02..80fa9c7 100644 --- a/src/services/timelineService.js +++ b/src/services/timelineService.js @@ -28,7 +28,7 @@ const QUERY_CREATE_TIMELINE_NOTIFICATIONS = 'INSERT INTO project_info (project_i const QUERY_UPDATE_TIMELINE_NOTIFICATIONS = 'UPDATE project_info SET value = "On", modify_user = ?, modify_date = CURRENT WHERE project_info_type_id = "11" AND project_id = ?' const QUERY_INSERT_CHALLENGE_PHASE_DEPENDENCY = 'INSERT INTO phase_dependency (dependency_phase_id, dependent_phase_id, dependency_start, dependent_start, lag_time, create_user, create_date, modify_user, modify_date) VALUES (?, ?, ?, 1, 0, ?, CURRENT, ?, CURRENT)' -const QUERY_GET_PROJECT_PHASE_ID = 'SELECT project_phase_id FROM project_phase WHERE project_id = %d AND phase_type_id = %d' +const QUERY_GET_PROJECT_PHASE_ID = 'SELECT project_phase_id as project_phase_id FROM project_phase WHERE project_id = %d AND phase_type_id = %d' /** * Formats a date into a format supported by ifx * @param {String} dateStr the date in string format @@ -101,7 +101,7 @@ async function getProjectPhaseId(challengeLegacyId, phaseTypeId) { await connection.closeAsync() } logger.info(`Query result: ${result}`) - const project_phase_id = _.get(result, '[0]', null) + const project_phase_id = _.get(result, '[0].project_phase_id', null) logger.info(`Project phase ID: ${project_phase_id}`) return project_phase_id } From 4185c9bedb0c65ff375442bc2f151a61c33dce54 Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Mon, 3 Oct 2022 16:57:30 +1100 Subject: [PATCH 22/30] Cleanup --- src/services/timelineService.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/services/timelineService.js b/src/services/timelineService.js index 80fa9c7..2047122 100644 --- a/src/services/timelineService.js +++ b/src/services/timelineService.js @@ -100,7 +100,6 @@ async function getProjectPhaseId(challengeLegacyId, phaseTypeId) { } finally { await connection.closeAsync() } - logger.info(`Query result: ${result}`) const project_phase_id = _.get(result, '[0].project_phase_id', null) logger.info(`Project phase ID: ${project_phase_id}`) return project_phase_id From a149dd9948cfb07091b1d1c70a3a3f2a0484a723 Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Mon, 3 Oct 2022 17:15:36 +1100 Subject: [PATCH 23/30] Handle default scorecards --- src/services/ProcessorService.js | 9 +++++++++ src/services/timelineService.js | 22 ++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/services/ProcessorService.js b/src/services/ProcessorService.js index 257c713..271a9a3 100644 --- a/src/services/ProcessorService.js +++ b/src/services/ProcessorService.js @@ -66,6 +66,15 @@ async function recreatePhases (legacyId, v5Phases, createdBy) { await timelineService.insertPhaseDependency(registrationPhaseId, checkpointSubmissionPhaseId, 1, createdBy) await timelineService.insertPhaseDependency(checkpointSubmissionPhaseId, checkpointScreeningPhaseId, 0, createdBy) await timelineService.insertPhaseDependency(checkpointScreeningPhaseId, checkpointReviewPhaseId, 0, createdBy) + + logger.info(`Creating default scorecard records for checkpoint phases`) + //30001364 is the default checkpoint screening scorecard for studio (https://software.topcoder-dev.com/review/actions/ViewScorecard?scid=30001364) + await timelineService.insertScorecardId(checkpointScreeningPhaseId, 30001364, createdBy) + + //30001364 is the default checkpoint review scorecard for studio (https://software.topcoder-dev.com/review/actions/ViewScorecard?scid=30001004) + await timelineService.insertScorecardId(checkpointReviewPhaseId, 30001004, createdBy) + + } } else if (!phaseLegacyId) { logger.warn(`Could not create phase ${phase.name} on legacy!`) diff --git a/src/services/timelineService.js b/src/services/timelineService.js index 2047122..5030bd4 100644 --- a/src/services/timelineService.js +++ b/src/services/timelineService.js @@ -29,6 +29,9 @@ const QUERY_UPDATE_TIMELINE_NOTIFICATIONS = 'UPDATE project_info SET value = "On const QUERY_INSERT_CHALLENGE_PHASE_DEPENDENCY = 'INSERT INTO phase_dependency (dependency_phase_id, dependent_phase_id, dependency_start, dependent_start, lag_time, create_user, create_date, modify_user, modify_date) VALUES (?, ?, ?, 1, 0, ?, CURRENT, ?, CURRENT)' const QUERY_GET_PROJECT_PHASE_ID = 'SELECT project_phase_id as project_phase_id FROM project_phase WHERE project_id = %d AND phase_type_id = %d' + +const QUERY_INSERT_CHALLENGE_PHASE_SCORECARD_ID = 'INSERT INTO phase_criteria (project_phase_id, phase_criteria_type_id, parameter, create_user, create_date, modify_user, modify_date) VALUES (?, 1, ?, ?, CURRENT, ?, CURRENT)' + /** * Formats a date into a format supported by ifx * @param {String} dateStr the date in string format @@ -84,6 +87,25 @@ async function insertPhaseDependency(dependencyPhaseId, dependentPhaseId, depend } return result } + + +async function insertScorecardId(projectPhaseId, scorecardId, createdBy){ + + logger.info(`Creating scorecard ID ${projectPhaseId} use scorecard ${scorecardId}`) + const connection = await helper.getInformixConnection() + let result = null + try { + let query = await prepare(connection, QUERY_INSERT_CHALLENGE_PHASE_SCORECARD_ID) + result = await query.executeAsync([projectPhaseId, scorecardId, createdBy, createdBy]) + } catch (e) { + logger.error(`Error in 'insertScorecardId' ${e}`) + throw e + } finally { + await connection.closeAsync() + } + return result +} + /** * Gets phase for the given phase type for the given challenge ID */ From ebc407e622fdb2234be8114fd582831358812757 Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Mon, 3 Oct 2022 21:16:53 +1100 Subject: [PATCH 24/30] Expose insertScorecardId --- src/services/timelineService.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/services/timelineService.js b/src/services/timelineService.js index 5030bd4..f6a88f9 100644 --- a/src/services/timelineService.js +++ b/src/services/timelineService.js @@ -309,5 +309,6 @@ module.exports = { createPhase, dropPhase, insertPhaseDependency, - getProjectPhaseId + getProjectPhaseId, + insertScorecardId } From 46b758cfd2d3cdae48460f4457af63c921ecea43 Mon Sep 17 00:00:00 2001 From: eisbilir Date: Thu, 1 Dec 2022 17:38:10 +0300 Subject: [PATCH 25/30] update fixed_start_time --- src/services/ProcessorService.js | 1 + src/services/timelineService.js | 12 ++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/services/ProcessorService.js b/src/services/ProcessorService.js index 271a9a3..1856046 100644 --- a/src/services/ProcessorService.js +++ b/src/services/ProcessorService.js @@ -125,6 +125,7 @@ async function syncChallengePhases (legacyId, v5Phases, createdBy, isSelfService await timelineService.updatePhase( phase.project_phase_id, legacyId, + phase.fixed_start_time ? v5Equivalent.scheduledStartDate : null, v5Equivalent.scheduledStartDate, v5Equivalent.scheduledEndDate, v5Equivalent.duration * 1000, diff --git a/src/services/timelineService.js b/src/services/timelineService.js index f6a88f9..fb8a2bf 100644 --- a/src/services/timelineService.js +++ b/src/services/timelineService.js @@ -15,10 +15,10 @@ const phaseIdGen = new IDGenerator('project_phase_id_seq') const QUERY_GET_PHASE_TYPES = 'SELECT phase_type_id, name FROM phase_type_lu' -const QUERY_GET_CHALLENGE_PHASES = 'SELECT project_phase_id, scheduled_start_time, scheduled_end_time, duration, phase_status_id, phase_type_id FROM project_phase WHERE project_id = %d' +const QUERY_GET_CHALLENGE_PHASES = 'SELECT project_phase_id, fixed_start_time, scheduled_start_time, scheduled_end_time, duration, phase_status_id, phase_type_id FROM project_phase WHERE project_id = %d' const QUERY_DROP_CHALLENGE_PHASE = 'DELETE FROM project_phase WHERE project_id = ? AND project_phase_id = ?' const QUERY_INSERT_CHALLENGE_PHASE = 'INSERT INTO project_phase (project_phase_id, project_id, phase_type_id, phase_status_id, scheduled_start_time, scheduled_end_time, duration, create_user, create_date, modify_user, modify_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT, ?, CURRENT)' -const QUERY_UPDATE_CHALLENGE_PHASE = 'UPDATE project_phase SET scheduled_start_time = ?, scheduled_end_time = ?, duration = ?, phase_status_id = ? WHERE project_phase_id = %d and project_id = %d' +const QUERY_UPDATE_CHALLENGE_PHASE = 'UPDATE project_phase SET fixed_start_time = ?, scheduled_start_time = ?, scheduled_end_time = ?, duration = ?, phase_status_id = ? WHERE project_phase_id = %d and project_id = %d' const QUERY_DROP_CHALLENGE_PHASE_CRITERIA = 'DELETE FROM phase_criteria WHERE project_phase_id = ?' @@ -37,6 +37,9 @@ const QUERY_INSERT_CHALLENGE_PHASE_SCORECARD_ID = 'INSERT INTO phase_criteria (p * @param {String} dateStr the date in string format */ function formatDate (dateStr) { + if (!dateStr) { + return null + } const date = momentTZ.tz(dateStr, config.TIMEZONE).format('YYYY-MM-DD HH:mm:ss') logger.info(`Formatting date ${dateStr} New Date ${date}`) return date @@ -228,18 +231,19 @@ async function createPhase (challengeLegacyId, phaseTypeId, statusTypeId, schedu * Update a phase in IFX * @param {Number} phaseId the phase ID * @param {Number} challengeLegacyId the legacy challenge ID + * @param {Date} fixedStartTime the fixed start date * @param {Date} startTime the scheduled start date * @param {Date} endTime the scheduled end date * @param {Date} duration the duration * @param {Number} statusTypeId the status type ID */ -async function updatePhase (phaseId, challengeLegacyId, startTime, endTime, duration, statusTypeId) { +async function updatePhase (phaseId, challengeLegacyId, fixedStartTime, startTime, endTime, duration, statusTypeId) { const connection = await helper.getInformixConnection() let result = null try { // await connection.beginTransactionAsync() const query = await prepare(connection, util.format(QUERY_UPDATE_CHALLENGE_PHASE, phaseId, challengeLegacyId)) - result = await query.executeAsync([formatDate(startTime), formatDate(endTime), duration, statusTypeId]) + result = await query.executeAsync([formatDate(fixedStartTime), formatDate(startTime), formatDate(endTime), duration, statusTypeId]) // await connection.commitTransactionAsync() } catch (e) { logger.error(`Error in 'updatePhase' ${e}, rolling back transaction`) From 144e84fb98c4d71b06502266024b20893a16440d Mon Sep 17 00:00:00 2001 From: eisbilir Date: Thu, 1 Dec 2022 19:27:32 +0300 Subject: [PATCH 26/30] handle multiple phases with same name --- src/services/ProcessorService.js | 79 ++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 35 deletions(-) diff --git a/src/services/ProcessorService.js b/src/services/ProcessorService.js index 271a9a3..fd1563c 100644 --- a/src/services/ProcessorService.js +++ b/src/services/ProcessorService.js @@ -104,42 +104,51 @@ async function syncChallengePhases (legacyId, v5Phases, createdBy, isSelfService const phasesFromIFx = await timelineService.getChallengePhases(legacyId) logger.debug(`Phases from v5: ${JSON.stringify(v5Phases)}`) logger.debug(`Phases from IFX: ${JSON.stringify(phasesFromIFx)}`) - for (const phase of phasesFromIFx) { - const phaseName = _.get(_.find(phaseTypes, pt => pt.phase_type_id === phase.phase_type_id), 'name') - const v5Equivalent = _.find(v5Phases, p => p.name === phaseName) - logger.info(`v4 Phase: ${JSON.stringify(phase)}, v5 Equiv: ${JSON.stringify(v5Equivalent)}`) - if (v5Equivalent) { - // Compare duration and status - // if (v5Equivalent.duration * 1000 !== phase.duration * 1 || isSelfService) { - // || - // (v5Equivalent.isOpen && _.toInteger(phase.phase_status_id) === constants.PhaseStatusTypes.Closed) || - // (!v5Equivalent.isOpen && _.toInteger(phase.phase_status_id) === constants.PhaseStatusTypes.Open)) { - // const newStatus = v5Equivalent.isOpen - // ? constants.PhaseStatusTypes.Open - // : (new Date().getTime() <= new Date(v5Equivalent.scheduledEndDate).getTime() ? constants.PhaseStatusTypes.Scheduled : constants.PhaseStatusTypes.Closed) - // update phase - logger.debug(`Will update phase ${phaseName}/${v5Equivalent.name} from ${phase.duration} to duration ${v5Equivalent.duration * 1000} milli`) - const newStatus = v5Equivalent.isOpen - ? constants.PhaseStatusTypes.Open - : (new Date().getTime() <= new Date(v5Equivalent.scheduledEndDate).getTime() ? constants.PhaseStatusTypes.Scheduled : constants.PhaseStatusTypes.Closed) - await timelineService.updatePhase( - phase.project_phase_id, - legacyId, - v5Equivalent.scheduledStartDate, - v5Equivalent.scheduledEndDate, - v5Equivalent.duration * 1000, - newStatus - ) - // newStatus) - // } else { - // logger.info(`Durations for ${phaseName} match: ${v5Equivalent.duration * 1000} === ${phase.duration}`) - // } - } else { - logger.info(`No v5 Equivalent Found for ${phaseName}`) + let phaseGroups = {} + _.forEach(phasesFromIFx, p => { + if (!phaseGroups[p.phase_type_id]) { + phaseGroups[p.phase_type_id] = [] } - if (isSelfService && phaseName === 'Review') { - // make sure to set the required reviewers to 2 - await createOrSetNumberOfReviewers(_.toString(phase.project_phase_id), _.toString(numOfReviewers), _.toString(createdBy)) + phaseGroups[p.phase_type_id].push(p) + }) + _.forEach(_.cloneDeep(phaseGroups), (pg, pt) => { + phaseGroups[pt] = _.sortBy(pg, 'scheduled_start_time') + }) + + for (const key of _.keys(phaseGroups)) { + let phaseOrder = 0 + let v5Equivalents = undefined + for (const phase of phaseGroups[key]) { + const phaseName = _.get(_.find(phaseTypes, pt => pt.phase_type_id === phase.phase_type_id), 'name') + if (_.isUndefined(v5Equivalents)) { + v5Equivalents = _.sortBy(_.filter(v5Phases, p => p.name === phaseName), 'scheduledStartDate') + } + if (v5Equivalents.length > 0) { + if (v5Equivalents.length === phaseGroups[key].length) { + const v5Equivalent = v5Equivalents[phaseOrder] + logger.debug(`Will update phase ${phaseName}/${v5Equivalent.name} from ${phase.duration} to duration ${v5Equivalent.duration * 1000} milli`) + const newStatus = v5Equivalent.isOpen + ? constants.PhaseStatusTypes.Open + : (new Date().getTime() <= new Date(v5Equivalent.scheduledEndDate).getTime() ? constants.PhaseStatusTypes.Scheduled : constants.PhaseStatusTypes.Closed) + await timelineService.updatePhase( + phase.project_phase_id, + legacyId, + v5Equivalent.scheduledStartDate, + v5Equivalent.scheduledEndDate, + v5Equivalent.duration * 1000, + newStatus + ) + } else { + logger.info(`number of ${phaseName} does not match`) + } + } else { + logger.info(`No v5 Equivalent Found for ${phaseName}`) + } + if (isSelfService && phaseName === 'Review') { + // make sure to set the required reviewers to 2 + await createOrSetNumberOfReviewers(_.toString(phase.project_phase_id), _.toString(numOfReviewers), _.toString(createdBy)) + } + phaseOrder = phaseOrder + 1 } } // TODO: What about iterative reviews? There can be many for the same challenge. From 2a0e5ee06fefc5c956d4084b9613cff07ddba263 Mon Sep 17 00:00:00 2001 From: eisbilir Date: Thu, 1 Dec 2022 19:28:22 +0300 Subject: [PATCH 27/30] fix phase status flip --- src/services/ProcessorService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/ProcessorService.js b/src/services/ProcessorService.js index fd1563c..081feec 100644 --- a/src/services/ProcessorService.js +++ b/src/services/ProcessorService.js @@ -129,7 +129,7 @@ async function syncChallengePhases (legacyId, v5Phases, createdBy, isSelfService logger.debug(`Will update phase ${phaseName}/${v5Equivalent.name} from ${phase.duration} to duration ${v5Equivalent.duration * 1000} milli`) const newStatus = v5Equivalent.isOpen ? constants.PhaseStatusTypes.Open - : (new Date().getTime() <= new Date(v5Equivalent.scheduledEndDate).getTime() ? constants.PhaseStatusTypes.Scheduled : constants.PhaseStatusTypes.Closed) + : _.toInteger(phase.phase_status_id) await timelineService.updatePhase( phase.project_phase_id, legacyId, From 925fcedecea689211d49745572df3c07d015154c Mon Sep 17 00:00:00 2001 From: eisbilir Date: Thu, 1 Dec 2022 19:39:42 +0300 Subject: [PATCH 28/30] update new phase status --- src/services/ProcessorService.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/services/ProcessorService.js b/src/services/ProcessorService.js index 081feec..d7ba9be 100644 --- a/src/services/ProcessorService.js +++ b/src/services/ProcessorService.js @@ -127,6 +127,10 @@ async function syncChallengePhases (legacyId, v5Phases, createdBy, isSelfService if (v5Equivalents.length === phaseGroups[key].length) { const v5Equivalent = v5Equivalents[phaseOrder] logger.debug(`Will update phase ${phaseName}/${v5Equivalent.name} from ${phase.duration} to duration ${v5Equivalent.duration * 1000} milli`) + let newStatus = _.toInteger(phase.phase_status_id) + if (v5Equivalent.isOpen && _.toInteger(phase.phase_status_id) === constants.PhaseStatusTypes.Closed) { + newStatus = constants.PhaseStatusTypes.Scheduled + } const newStatus = v5Equivalent.isOpen ? constants.PhaseStatusTypes.Open : _.toInteger(phase.phase_status_id) From 10ac0c77bb73fc6fd6aa09c536345e548c95058e Mon Sep 17 00:00:00 2001 From: Rakib Ansary Saikot Date: Fri, 9 Dec 2022 19:55:21 +0600 Subject: [PATCH 29/30] Update config.yml --- .circleci/config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3a78bbe..67abcd0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,11 +1,12 @@ version: 2 defaults: &defaults docker: - - image: circleci/python:2.7-stretch-browsers + - image: cimg/python:3.11.0-browsers install_dependency: &install_dependency name: Installation of build and deployment dependencies. command: | sudo apt install jq + sudo apt install python3-pip sudo pip install awscli --upgrade sudo pip install docker-compose install_deploysuite: &install_deploysuite From 1a29f17dd35b944cb009523b0e3711ad3ed6b6b6 Mon Sep 17 00:00:00 2001 From: Rakib Ansary Saikot Date: Fri, 9 Dec 2022 20:11:33 +0600 Subject: [PATCH 30/30] Update config.yml --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 67abcd0..9c4f07a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,6 +5,7 @@ defaults: &defaults install_dependency: &install_dependency name: Installation of build and deployment dependencies. command: | + sudo apt update sudo apt install jq sudo apt install python3-pip sudo pip install awscli --upgrade