Skip to content
This repository was archived by the owner on Jan 23, 2025. It is now read-only.

Commit ed6a7e1

Browse files
authored
Merge pull request #97 from topcoder-platform/develop
prod release
2 parents 64a953e + 4e1079c commit ed6a7e1

File tree

5 files changed

+317
-51
lines changed

5 files changed

+317
-51
lines changed

Diff for: .circleci/config.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
version: 2
22
defaults: &defaults
33
docker:
4-
- image: circleci/python:2.7-stretch-browsers
4+
- image: cimg/python:3.11.0-browsers
55
install_dependency: &install_dependency
66
name: Installation of build and deployment dependencies.
77
command: |
8+
sudo apt update
89
sudo apt install jq
10+
sudo apt install python3-pip
911
sudo pip install awscli --upgrade
1012
sudo pip install docker-compose
1113
install_deploysuite: &install_deploysuite

Diff for: src/constants.js

+19-1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,23 @@ const challengeStatuses = {
4848
CancelledPaymentFailed: 'Cancelled - Payment Failed'
4949
}
5050

51+
const scorecardQuestionMapping = {
52+
30002212: [
53+
{
54+
questionId: 30007531,
55+
description: 'Does the submission sufficiently satisfy the requirements as described in the provided specification?'
56+
},
57+
{
58+
questionId: 30007533,
59+
description: 'How would you rate the work ethic of this submitter?'
60+
},
61+
{
62+
questionId: 30007532,
63+
description: 'How would you rate the quality of this submitters work?'
64+
}
65+
]
66+
}
67+
5168
const PhaseStatusTypes = {
5269
Scheduled: 1,
5370
Open: 2,
@@ -155,5 +172,6 @@ module.exports = {
155172
challengeStatuses,
156173
PhaseStatusTypes,
157174
prizeTypesIds,
158-
supportedMetadata
175+
supportedMetadata,
176+
scorecardQuestionMapping
159177
}

Diff for: src/services/ProcessorService.js

+94-36
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const paymentService = require('./paymentService')
1919
const { createOrSetNumberOfReviewers } = require('./selfServiceReviewerService')
2020
const { disableTimelineNotifications } = require('./selfServiceNotificationService')
2121
const legacyChallengeService = require('./legacyChallengeService')
22+
const legacyChallengeReviewService = require('./legacyChallengeReviewService')
2223

2324
/**
2425
* Drop and recreate phases in ifx
@@ -51,6 +52,30 @@ async function recreatePhases (legacyId, v5Phases, createdBy) {
5152
phase.duration * 1000,
5253
createdBy
5354
)
55+
//Handle checkpoint phases
56+
//Magic numbers: 15=checkpoint submission, 16=checkpoint screen, 17=checkpoint review, 1=registration
57+
//For dependencyStart: 1=start, 0=end
58+
if(phaseLegacyId==17){
59+
logger.info(`Creating phase dependencies for checkpoint phases`)
60+
61+
const registrationPhaseId = await timelineService.getProjectPhaseId(legacyId, 1)
62+
const checkpointSubmissionPhaseId = await timelineService.getProjectPhaseId(legacyId, 15)
63+
const checkpointScreeningPhaseId = await timelineService.getProjectPhaseId(legacyId, 16)
64+
const checkpointReviewPhaseId = await timelineService.getProjectPhaseId(legacyId, 17)
65+
66+
await timelineService.insertPhaseDependency(registrationPhaseId, checkpointSubmissionPhaseId, 1, createdBy)
67+
await timelineService.insertPhaseDependency(checkpointSubmissionPhaseId, checkpointScreeningPhaseId, 0, createdBy)
68+
await timelineService.insertPhaseDependency(checkpointScreeningPhaseId, checkpointReviewPhaseId, 0, createdBy)
69+
70+
logger.info(`Creating default scorecard records for checkpoint phases`)
71+
//30001364 is the default checkpoint screening scorecard for studio (https://software.topcoder-dev.com/review/actions/ViewScorecard?scid=30001364)
72+
await timelineService.insertScorecardId(checkpointScreeningPhaseId, 30001364, createdBy)
73+
74+
//30001364 is the default checkpoint review scorecard for studio (https://software.topcoder-dev.com/review/actions/ViewScorecard?scid=30001004)
75+
await timelineService.insertScorecardId(checkpointReviewPhaseId, 30001004, createdBy)
76+
77+
78+
}
5479
} else if (!phaseLegacyId) {
5580
logger.warn(`Could not create phase ${phase.name} on legacy!`)
5681
}
@@ -79,42 +104,53 @@ async function syncChallengePhases (legacyId, v5Phases, createdBy, isSelfService
79104
const phasesFromIFx = await timelineService.getChallengePhases(legacyId)
80105
logger.debug(`Phases from v5: ${JSON.stringify(v5Phases)}`)
81106
logger.debug(`Phases from IFX: ${JSON.stringify(phasesFromIFx)}`)
82-
for (const phase of phasesFromIFx) {
83-
const phaseName = _.get(_.find(phaseTypes, pt => pt.phase_type_id === phase.phase_type_id), 'name')
84-
const v5Equivalent = _.find(v5Phases, p => p.name === phaseName)
85-
logger.info(`v4 Phase: ${JSON.stringify(phase)}, v5 Equiv: ${JSON.stringify(v5Equivalent)}`)
86-
if (v5Equivalent) {
87-
// Compare duration and status
88-
// if (v5Equivalent.duration * 1000 !== phase.duration * 1 || isSelfService) {
89-
// ||
90-
// (v5Equivalent.isOpen && _.toInteger(phase.phase_status_id) === constants.PhaseStatusTypes.Closed) ||
91-
// (!v5Equivalent.isOpen && _.toInteger(phase.phase_status_id) === constants.PhaseStatusTypes.Open)) {
92-
// const newStatus = v5Equivalent.isOpen
93-
// ? constants.PhaseStatusTypes.Open
94-
// : (new Date().getTime() <= new Date(v5Equivalent.scheduledEndDate).getTime() ? constants.PhaseStatusTypes.Scheduled : constants.PhaseStatusTypes.Closed)
95-
// update phase
96-
logger.debug(`Will update phase ${phaseName}/${v5Equivalent.name} from ${phase.duration} to duration ${v5Equivalent.duration * 1000} milli`)
97-
const newStatus = v5Equivalent.isOpen
98-
? constants.PhaseStatusTypes.Open
99-
: (_.toInteger(phase.phase_status_id) === constants.PhaseStatusTypes.Scheduled ? constants.PhaseStatusTypes.Scheduled : constants.PhaseStatusTypes.Closed)
100-
await timelineService.updatePhase(
101-
phase.project_phase_id,
102-
legacyId,
103-
v5Equivalent.scheduledStartDate,
104-
v5Equivalent.scheduledEndDate,
105-
v5Equivalent.duration * 1000,
106-
newStatus // phase.phase_status_id
107-
)
108-
// newStatus)
109-
// } else {
110-
// logger.info(`Durations for ${phaseName} match: ${v5Equivalent.duration * 1000} === ${phase.duration}`)
111-
// }
112-
} else {
113-
logger.info(`No v5 Equivalent Found for ${phaseName}`)
107+
let phaseGroups = {}
108+
_.forEach(phasesFromIFx, p => {
109+
if (!phaseGroups[p.phase_type_id]) {
110+
phaseGroups[p.phase_type_id] = []
114111
}
115-
if (isSelfService && phaseName === 'Review') {
116-
// make sure to set the required reviewers to 2
117-
await createOrSetNumberOfReviewers(_.toString(phase.project_phase_id), _.toString(numOfReviewers), _.toString(createdBy))
112+
phaseGroups[p.phase_type_id].push(p)
113+
})
114+
_.forEach(_.cloneDeep(phaseGroups), (pg, pt) => {
115+
phaseGroups[pt] = _.sortBy(pg, 'scheduled_start_time')
116+
})
117+
118+
for (const key of _.keys(phaseGroups)) {
119+
let phaseOrder = 0
120+
let v5Equivalents = undefined
121+
for (const phase of phaseGroups[key]) {
122+
const phaseName = _.get(_.find(phaseTypes, pt => pt.phase_type_id === phase.phase_type_id), 'name')
123+
if (_.isUndefined(v5Equivalents)) {
124+
v5Equivalents = _.sortBy(_.filter(v5Phases, p => p.name === phaseName), 'scheduledStartDate')
125+
}
126+
if (v5Equivalents.length > 0) {
127+
if (v5Equivalents.length === phaseGroups[key].length) {
128+
const v5Equivalent = v5Equivalents[phaseOrder]
129+
logger.debug(`Will update phase ${phaseName}/${v5Equivalent.name} from ${phase.duration} to duration ${v5Equivalent.duration * 1000} milli`)
130+
let newStatus = _.toInteger(phase.phase_status_id)
131+
if (v5Equivalent.isOpen && _.toInteger(phase.phase_status_id) === constants.PhaseStatusTypes.Closed) {
132+
newStatus = constants.PhaseStatusTypes.Scheduled
133+
}
134+
await timelineService.updatePhase(
135+
phase.project_phase_id,
136+
legacyId,
137+
phase.fixed_start_time ? v5Equivalent.scheduledStartDate : null,
138+
v5Equivalent.scheduledStartDate,
139+
v5Equivalent.scheduledEndDate,
140+
v5Equivalent.duration * 1000,
141+
newStatus
142+
)
143+
} else {
144+
logger.info(`number of ${phaseName} does not match`)
145+
}
146+
} else {
147+
logger.info(`No v5 Equivalent Found for ${phaseName}`)
148+
}
149+
if (isSelfService && phaseName === 'Review') {
150+
// make sure to set the required reviewers to 2
151+
await createOrSetNumberOfReviewers(_.toString(phase.project_phase_id), _.toString(numOfReviewers), _.toString(createdBy))
152+
}
153+
phaseOrder = phaseOrder + 1
118154
}
119155
}
120156
// TODO: What about iterative reviews? There can be many for the same challenge.
@@ -701,6 +737,28 @@ async function processMessage (message) {
701737
throw new Error(`Error getting challenge by id - Error: ${JSON.stringify(e)}`)
702738
}
703739

740+
// If iterative review is open
741+
if (_.find(_.get(message.payload, 'phases'), p => p.isOpen && p.name === 'Iterative Review')) {
742+
// Try to read reviews and insert them into informix DB
743+
if (message.payload.metadata && message.payload.legacy.reviewScorecardId) {
744+
let orReviewFeedback = _.find(message.payload.metadata, meta => meta.name === 'or_review_feedback')
745+
let orReviewScore = _.find(message.payload.metadata, meta => meta.name === 'or_review_score')
746+
if (!_.isUndefined(orReviewFeedback) && !_.isUndefined(orReviewScore)) {
747+
orReviewFeedback = JSON.parse(orReviewFeedback)
748+
const reviewResponses = []
749+
_.each(orReviewFeedback, (value, key) => {
750+
const questionId = _.get(_.find(constants.scorecardQuestionMapping[message.payload.legacy.reviewScorecardId], item => _.toString(item.questionId) === _.toString(key) || _.toLower(item.description) === _.toLower(key)), 'questionId')
751+
reviewResponses.push({
752+
questionId,
753+
answer: value
754+
})
755+
})
756+
orReviewScore = _.toNumber(orReviewFeedback)
757+
await legacyChallengeReviewService.insertReview(legacyId, message.payload.legacy.reviewScorecardId, orReviewScore, reviewResponses, createdByUserId)
758+
}
759+
}
760+
}
761+
704762
if (message.payload.status && challenge) {
705763
// Whether we need to sync v4 ES again
706764
let needSyncV4ES = false
@@ -788,7 +846,7 @@ processMessage.schema = {
788846
prizeSets: Joi.array().items(Joi.object().keys({
789847
type: Joi.string().valid(_.values(constants.prizeSetTypes)).required(),
790848
prizes: Joi.array().items(Joi.object().keys({
791-
value: Joi.number().positive().required()
849+
value: Joi.number().min(0).required()
792850
}).unknown(true))
793851
}).unknown(true)).min(1),
794852
tags: Joi.array().items(Joi.string().required()).min(1), // tag names

Diff for: src/services/legacyChallengeReviewService.js

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/**
2+
* Legacy Challenge Service
3+
* Interacts with InformixDB
4+
* Note: this is built to work for topgear challenges and iterative review phases
5+
*/
6+
const _ = require('lodash')
7+
const logger = require('../common/logger')
8+
const util = require('util')
9+
const helper = require('../common/helper')
10+
const IDGenerator = require('../common/idGenerator')
11+
const reviewIdGen = new IDGenerator('review_id_seq')
12+
const reviewItemIdGen = new IDGenerator('review_item_id_seq')
13+
14+
const ITERATIVE_REVIEWER_RESOURCE_ROLE_ID = 21
15+
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}`
16+
17+
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)'
18+
19+
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)'
20+
21+
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'
22+
23+
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'
24+
25+
/**
26+
* Prepare Informix statement
27+
* @param {Object} connection the Informix connection
28+
* @param {String} sql the sql
29+
* @return {Object} Informix statement
30+
*/
31+
async function prepare (connection, sql) {
32+
// logger.debug(`Preparing SQL ${sql}`)
33+
const stmt = await connection.prepareAsync(sql)
34+
return Promise.promisifyAll(stmt)
35+
}
36+
37+
/**
38+
* Insert review in IFX
39+
* @param {Number} challengeLegacyId the legacy challenge ID
40+
* @param {Number} createdBy the scorecard ID
41+
* @param {Number} score the review score
42+
* @param {Array} responses the review responses
43+
* @param {Number} createdBy the creator user ID
44+
*/
45+
async function insertReview (challengeLegacyId, scorecardId, score, responses, createdBy) {
46+
const connection = await helper.getInformixConnection()
47+
let result = null
48+
let reviewId
49+
try {
50+
const resourceId = await getIterativeReviewerResourceId(connection, challengeLegacyId)
51+
if (!resourceId) throw new Error('Cannot find Iterative Reviewer')
52+
const submissionId = await getSubmissionId(connection, challengeLegacyId)
53+
if (!submissionId) throw new Error('Cannot find Submission')
54+
const projectPhaseId = await getProjectPhaseId(connection, challengeLegacyId)
55+
if (!projectPhaseId) throw new Error('Cannot find Project Phase Id')
56+
reviewId = await reviewIdGen.getNextId()
57+
await connection.beginTransactionAsync()
58+
const query = await prepare(connection, QUERY_CREATE_REVIEW)
59+
result = await query.executeAsync([reviewId, resourceId, submissionId, projectPhaseId, scorecardId, 1, score, score, createdBy, createdBy])
60+
for (let i = 0; i < responses.length; i += 1) {
61+
await insertReviewItem(connection, reviewId, responses[i], i, createdBy)
62+
}
63+
await connection.commitTransactionAsync()
64+
} catch (e) {
65+
logger.error(`Error in 'insertReview' ${e}, rolling back transaction`)
66+
await connection.rollbackTransactionAsync()
67+
throw e
68+
} finally {
69+
logger.info(`Review ${challengeLegacyId} has been created`)
70+
await connection.closeAsync()
71+
}
72+
return result
73+
}
74+
75+
/**
76+
* Insert review item in IFX
77+
* @param {Object} connection
78+
* @param {Number} reviewId the review ID
79+
* @param {Object} response the response
80+
* @param {Number} sort the sort
81+
* @param {Number} createdBy the creator user ID
82+
*/
83+
async function insertReviewItem (connection, reviewId, response, sort, createdBy) {
84+
let result = null
85+
const reviewItemId = await reviewItemIdGen.getNextId()
86+
await connection.beginTransactionAsync()
87+
const query = await prepare(connection, QUERY_CREATE_REVIEW_ITEM)
88+
result = await query.executeAsync([reviewItemId, reviewId, response.questionId, null, response.answer, sort, createdBy, createdBy])
89+
return result
90+
}
91+
92+
/**
93+
* Gets the iterative reviewer resource id
94+
* @param {Object} connection
95+
* @param {Number} challengeLegacyId
96+
*/
97+
async function getIterativeReviewerResourceId (connection, challengeLegacyId) {
98+
const result = await connection.queryAsync(util.format(QUERY_GET_ITERATIVE_REVIEW_RESOURCE_FOR_CHALLENGE, challengeLegacyId))
99+
return _.get(result, '[0].resourceid', null)
100+
}
101+
102+
/**
103+
* Gets the submission id
104+
* @param {Object} connection
105+
* @param {Number} challengeLegacyId
106+
*/
107+
async function getSubmissionId (connection, challengeLegacyId) {
108+
const result = await connection.queryAsync(util.format(QUERY_GET_SUBMISSION, challengeLegacyId))
109+
return _.get(result, '[0].submission_id', null)
110+
}
111+
112+
/**
113+
* Gets the submission id
114+
* @param {Object} connection
115+
* @param {Number} challengeLegacyId
116+
*/
117+
async function getProjectPhaseId (connection, challengeLegacyId) {
118+
const result = await connection.queryAsync(util.format(QUERY_GET_PROJECT_PHASE, challengeLegacyId))
119+
return _.get(result, '[0].project_phase_id', null)
120+
}
121+
122+
module.exports = {
123+
insertReview
124+
}

0 commit comments

Comments
 (0)