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

prod release #97

Merged
merged 58 commits into from
Dec 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
3bd3e51
Payment Generation issue in Work Manager
marioskranitsas Jul 5, 2022
5178b4e
Merge branch 'develop' of https://github.com/topcoder-platform/legacy…
marioskranitsas Jul 5, 2022
f8d8a45
Merge pull request #90 from topcoder-platform/PROD-2397
ThomasKranitsas Jul 5, 2022
3a37d9c
support review feedback
ThomasKranitsas Aug 29, 2022
e0ff01f
clean up
ThomasKranitsas Aug 29, 2022
dbf1374
Merge pull request #93 from topcoder-platform/feature/topgear-reviews
ThomasKranitsas Aug 29, 2022
0fa9d9b
insert reviews in ifx when the iterative review phase is open
ThomasKranitsas Aug 30, 2022
0959e0b
Test for phase syncing for multi-round challenges
jmgasper Sep 9, 2022
ed40d1a
Merge pull request #96 from topcoder-platform/multi-round-sync
jmgasper Sep 9, 2022
bf7172b
Revert "Test for phase syncing for multi-round challenges"
jmgasper Sep 13, 2022
c3b77b3
Merge branch 'develop' of github.com:topcoder-platform/legacy-challen…
jmgasper Sep 13, 2022
ceb4260
follow v5 or time for phase.phase_status_id in legacy
ThomasKranitsas Sep 16, 2022
07e9f14
Test fix for failing multi-round sync issue
jmgasper Sep 28, 2022
3664bad
Merge branch 'develop' of github.com:topcoder-platform/legacy-challen…
jmgasper Sep 28, 2022
25bfb2b
Use the *correct* sequence generator when adding new project phases.
jmgasper Sep 28, 2022
d5b1179
Merge pull request #99 from topcoder-platform/multi-round-sync
jpeg22 Sep 28, 2022
b0976a5
Don’t set fixed start time, actual start time, or actual end time. O…
jmgasper Sep 29, 2022
f003cff
Merge pull request #100 from topcoder-platform/multi-round-sync
jpeg22 Sep 29, 2022
2ffea42
Fix query when inserting challenge phases
jmgasper Oct 2, 2022
bb10113
Merge pull request #101 from topcoder-platform/multi-round-sync
jmgasper Oct 2, 2022
e6a6f6e
Add in phase dependencies test
jmgasper Oct 3, 2022
53da312
Merge pull request #102 from topcoder-platform/multi-round-sync
jmgasper Oct 3, 2022
66d04bc
Restructuring and some additional logging
jmgasper Oct 3, 2022
5a40fe9
Merge pull request #103 from topcoder-platform/multi-round-sync
jmgasper Oct 3, 2022
87aa097
Better return value, not an ODBCResult
jmgasper Oct 3, 2022
e40bbd7
Merge pull request #104 from topcoder-platform/multi-round-sync
jmgasper Oct 3, 2022
dec5650
Missing ref
jmgasper Oct 3, 2022
6e238ff
Merge pull request #105 from topcoder-platform/multi-round-sync
jmgasper Oct 3, 2022
6e8207b
Better logging
jmgasper Oct 3, 2022
c74f864
Merge pull request #106 from topcoder-platform/multi-round-sync
jmgasper Oct 3, 2022
af80491
Additional formality and logging
jmgasper Oct 3, 2022
14e5051
Merge pull request #107 from topcoder-platform/multi-round-sync
jmgasper Oct 3, 2022
1786aff
Update to fix this query result
jmgasper Oct 3, 2022
2f6ae07
Merge pull request #108 from topcoder-platform/multi-round-sync
jmgasper Oct 3, 2022
806d006
Additional logging
jmgasper Oct 3, 2022
b2242ff
Merge pull request #109 from topcoder-platform/multi-round-sync
jmgasper Oct 3, 2022
58c6a36
Try something different
jmgasper Oct 3, 2022
44c176e
Merge pull request #110 from topcoder-platform/multi-round-sync
jmgasper Oct 3, 2022
c927f79
Tweaks to the query structure
jmgasper Oct 3, 2022
5af9443
Merge pull request #111 from topcoder-platform/multi-round-sync
jmgasper Oct 3, 2022
4185c9b
Cleanup
jmgasper Oct 3, 2022
a149dd9
Handle default scorecards
jmgasper Oct 3, 2022
f65c033
Merge pull request #112 from topcoder-platform/multi-round-sync
jmgasper Oct 3, 2022
ebc407e
Expose insertScorecardId
jmgasper Oct 3, 2022
0ffd7e8
Merge pull request #113 from topcoder-platform/multi-round-sync
jmgasper Oct 3, 2022
46b758c
update fixed_start_time
eisbilir Dec 1, 2022
144e84f
handle multiple phases with same name
eisbilir Dec 1, 2022
2a0e5ee
fix phase status flip
eisbilir Dec 1, 2022
925fced
update new phase status
eisbilir Dec 1, 2022
8b5843b
Merge pull request #119 from eisbilir/PLAT-1186
ThomasKranitsas Dec 8, 2022
10ac0c7
Update config.yml
rakibansary Dec 9, 2022
736215b
Merge pull request #122 from topcoder-platform/fix/circle-ci-build
rakibansary Dec 9, 2022
1a29f17
Update config.yml
rakibansary Dec 9, 2022
ed7102c
Merge pull request #123 from topcoder-platform/fix/build-circleci
rakibansary Dec 9, 2022
6c345e4
Merge 'origin/develop' into PLAT-1711
eisbilir Dec 9, 2022
129e3c1
Merge pull request #120 from eisbilir/PLAT-1711
ThomasKranitsas Dec 13, 2022
edd1b3b
Merge branch 'master' into develop
jmgasper Dec 15, 2022
4e1079c
Merge pull request #126 from topcoder-platform/multiround
jmgasper Dec 15, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
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 update
sudo apt install jq
sudo apt install python3-pip
sudo pip install awscli --upgrade
sudo pip install docker-compose
install_deploysuite: &install_deploysuite
Expand Down
20 changes: 19 additions & 1 deletion src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -155,5 +172,6 @@ module.exports = {
challengeStatuses,
PhaseStatusTypes,
prizeTypesIds,
supportedMetadata
supportedMetadata,
scorecardQuestionMapping
}
130 changes: 94 additions & 36 deletions src/services/ProcessorService.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -51,6 +52,30 @@ 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`)

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)
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!`)
}
Expand Down Expand Up @@ -79,42 +104,53 @@ 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
: (_.toInteger(phase.phase_status_id) === constants.PhaseStatusTypes.Scheduled ? constants.PhaseStatusTypes.Scheduled : constants.PhaseStatusTypes.Closed)
await timelineService.updatePhase(
phase.project_phase_id,
legacyId,
v5Equivalent.scheduledStartDate,
v5Equivalent.scheduledEndDate,
v5Equivalent.duration * 1000,
newStatus // phase.phase_status_id
)
// 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`)
let newStatus = _.toInteger(phase.phase_status_id)
if (v5Equivalent.isOpen && _.toInteger(phase.phase_status_id) === constants.PhaseStatusTypes.Closed) {
newStatus = constants.PhaseStatusTypes.Scheduled
}
await timelineService.updatePhase(
phase.project_phase_id,
legacyId,
phase.fixed_start_time ? v5Equivalent.scheduledStartDate : null,
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.
Expand Down Expand Up @@ -701,6 +737,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
Expand Down Expand Up @@ -788,7 +846,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
Expand Down
124 changes: 124 additions & 0 deletions src/services/legacyChallengeReviewService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/**
* 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)'

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)'

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
}
Loading