Skip to content

feat: points & new challenge template #704

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 1 addition & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,7 @@ workflows:
branches:
only:
- dev
- hotfix/budget-update
- CORE-140
- CORE-40

- "build-qa":
context: org-global
Expand Down
6 changes: 6 additions & 0 deletions app-constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ const prizeSetTypes = {
CheckpointPrizes: "checkpoint",
};

const prizeTypes = {
USD: "USD",
POINT: "POINT",
};

const challengeStatuses = {
New: "New",
Draft: "Draft",
Expand Down Expand Up @@ -137,6 +142,7 @@ const SelfServiceNotificationSettings = {
module.exports = {
UserRoles,
prizeSetTypes,
prizeTypes,
challengeStatuses,
validChallengeParams,
EVENT_ORIGINATOR,
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@
"dependencies": {
"@grpc/grpc-js": "^1.8.12",
"@opensearch-project/opensearch": "^2.2.0",
"@topcoder-framework/domain-challenge": "^0.24.0",
"@topcoder-framework/domain-challenge": "^0.24.1",
"@topcoder-framework/domain-acl": "^0.24.0",
"@topcoder-framework/lib-common": "^0.24.0",
"@topcoder-framework/lib-common": "^0.24.1",
"aws-sdk": "^2.1145.0",
"axios": "^0.19.0",
"axios-retry": "^3.4.0",
Expand Down
29 changes: 25 additions & 4 deletions src/common/challenge-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,23 @@ class ChallengeHelper {
await Promise.all(promises);
}

validatePrizeSetsAndGetPrizeType(prizeSets) {
if (_.isEmpty(prizeSets)) return null;

const firstType = _.get(prizeSets, "[0].prizes[0].type", null);
if (!firstType) return null;

const isConsistent = _.every(prizeSets, (prizeSet) =>
_.every(prizeSet.prizes, (prize) => prize.type === firstType)
);

if (!isConsistent) {
throw new errors.BadRequestError("All prizes must be of the same type");
}

return firstType;
}

/**
* Validate Challenge skills.
* @param {Object} challenge the challenge
Expand All @@ -114,7 +131,9 @@ class ChallengeHelper {
if (oldChallenge && oldChallenge.status === constants.challengeStatuses.Completed) {
// Don't allow edit skills for Completed challenges
if (!_.isEqual(ids, _.uniq(_.map(oldChallenge.skills, "id")))) {
throw new errors.BadRequestError("Cannot update skills for challenges with Completed status");
throw new errors.BadRequestError(
"Cannot update skills for challenges with Completed status"
);
}
}

Expand Down Expand Up @@ -356,12 +375,14 @@ class ChallengeHelper {
}

if (data.prizeSets != null) {
ChallengeHelper.convertPSValuesToCents(data.prizeSets)
console.log('Converted prizeSets to cents', data.prizeSets)
const type = data.prizeSets[0]?.prizes[0]?.type;
if (type === constants.prizeTypes.USD) {
ChallengeHelper.convertPSValuesToCents(data.prizeSets)
}

data.prizeSetUpdate = {
prizeSets: [...data.prizeSets],
};
console.log('prizeSetUpdate', data.prizeSetUpdate)
delete data.prizeSets;
}

Expand Down
2 changes: 1 addition & 1 deletion src/common/logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ logger.logFullError = (err, signature) => {
`${err.name} details: ${JSON.stringify(err.details)} input:${JSON.stringify(err._object)}`
);
} else if (err.isAxiosError) {
logger.error(`${err.message} - ${err.response.data}`);
logger.error(`${err.message} - ${JSON.stringify(err.response.data)}`);
} else if (err.httpStatus) {
logger.error(err.message);
} else if (!_.isUndefined(err.code) && err.details && err.metadata) {
Expand Down
2 changes: 1 addition & 1 deletion src/phase-management/PhaseAdvancer.js
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ class PhaseAdvancer {

async #areAllSubmissionsReviewed(challengeId) {
console.log(`Getting review count for challenge ${challengeId}`);
return Promise.resolve(false);
return Promise.resolve(true);
}

async #areAllAppealsResolved(challengeId) {
Expand Down
59 changes: 54 additions & 5 deletions src/phase-management/phase-rules.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
{
"openRules": {
"Open": [
{
"name": "Open Registration & Submission",
"conditions": {
"all": [
{
"fact": "isPastScheduledStartTime",
"operator": "equal",
"value": true
},
{
"fact": "isOpen",
"operator": "notEqual",
"value": true
},
{
"fact": "isClosed",
"operator": "notEqual",
"value": true
}
]
},
"event": {
"type": "canOpen"
}
}
],
"Registration": [
{
"name": "Registration Open",
Expand Down Expand Up @@ -74,11 +101,6 @@
"operator": "notEqual",
"value": true
},
{
"fact": "submissionCount",
"operator": "greaterThanInclusive",
"value": 1
},
{
"fact": "isPredecessorPhaseClosed",
"operator": "equal",
Expand Down Expand Up @@ -184,6 +206,33 @@
]
},
"closeRules": {
"Open": [
{
"name": "Close Registration & Submission",
"conditions": {
"all": [
{
"fact": "isOpen",
"operator": "equal",
"value": true
},
{
"fact": "isPastScheduledEndTime",
"operator": "equal",
"value": true
},
{
"fact": "isClosed",
"operator": "notEqual",
"value": true
}
]
},
"event": {
"type": "canClose"
}
}
],
"Registration": [
{
"name": "Registration Close",
Expand Down
101 changes: 61 additions & 40 deletions src/services/ChallengeService.js
Original file line number Diff line number Diff line change
Expand Up @@ -340,12 +340,6 @@ async function searchChallenges(currentUser, criteria) {
}
boolQuery.push({ range: { "overview.totalPrizes": prizeRangeQuery } });
}

if (criteria.useSchedulingAPI) {
boolQuery.push({
match_phrase: { "legacy.useSchedulingAPI": criteria.useSchedulingAPI },
});
}
if (criteria.selfService) {
boolQuery.push({
match_phrase: { "legacy.selfService": criteria.selfService },
Expand Down Expand Up @@ -379,9 +373,21 @@ async function searchChallenges(currentUser, criteria) {
});
}
if (criteria.currentPhaseName) {
boolQuery.push({
match_phrase: { currentPhaseNames: criteria.currentPhaseName },
});
if (criteria.currentPhaseName === "Registration") {
boolQuery.push({
bool: {
should: [
{ match_phrase: { currentPhaseNames: "Registration" } },
{ match_phrase: { currentPhaseNames: "Open" } },
],
minimum_should_match: 1,
},
});
} else {
boolQuery.push({
match_phrase: { currentPhaseNames: criteria.currentPhaseName },
});
}
}
if (criteria.createdDateStart) {
boolQuery.push({ range: { created: { gte: criteria.createdDateStart } } });
Expand Down Expand Up @@ -930,7 +936,9 @@ searchChallenges.schema = {
*/
async function createChallenge(currentUser, challenge, userToken) {
await challengeHelper.validateCreateChallengeRequest(currentUser, challenge);
let prizeTypeTmp = challengeHelper.validatePrizeSetsAndGetPrizeType(challenge.prizeSets);

console.log("TYPE", prizeTypeTmp);
if (challenge.legacy.selfService) {
// if self-service, create a new project (what about if projectId is provided in the payload? confirm with business!)
if (!challenge.projectId) {
Expand Down Expand Up @@ -1065,9 +1073,17 @@ async function createChallenge(currentUser, challenge, userToken) {
grpcMetadata.set("userId", currentUser.userId);
grpcMetadata.set("token", await getM2MToken());

convertPrizeSetValuesToCents(challenge.prizeSets);
const prizeType = challengeHelper.validatePrizeSetsAndGetPrizeType(challenge.prizeSets);

if (prizeType === constants.prizeTypes.USD) {
convertPrizeSetValuesToCents(challenge.prizeSets);
}

const ret = await challengeDomain.create(challenge, grpcMetadata);
convertPrizeSetValuesToDollars(ret.prizeSets, ret.overview);

if (prizeType === constants.prizeTypes.USD) {
convertPrizeSetValuesToDollars(ret.prizeSets, ret.overview);
}

ret.numOfSubmissions = 0;
ret.numOfRegistrants = 0;
Expand Down Expand Up @@ -1494,7 +1510,11 @@ function validateTask(currentUser, challenge, data, challengeResources) {
*/
async function updateChallenge(currentUser, challengeId, data) {
const challenge = await challengeDomain.lookup(getLookupCriteria("id", challengeId));
convertPrizeSetValuesToDollars(challenge.prizeSets, challenge.overview);
const existingPrizeType = challengeHelper.validatePrizeSetsAndGetPrizeType(challenge.prizeSets);

if (existingPrizeType === constants.prizeTypes.USD) {
convertPrizeSetValuesToDollars(challenge.prizeSets, challenge.overview);
}

const projectId = _.get(challenge, "projectId");

Expand Down Expand Up @@ -1618,15 +1638,6 @@ async function updateChallenge(currentUser, challengeId, data) {
let isChallengeBeingCancelled = false;
if (data.status) {
if (data.status === constants.challengeStatuses.Active) {
if (
!_.get(challenge, "legacy.pureV5Task") &&
!_.get(challenge, "legacy.pureV5") &&
_.isUndefined(_.get(challenge, "legacyId"))
) {
throw new errors.BadRequestError(
"You cannot activate the challenge as it has not been created on legacy yet. Please try again later or contact support."
);
}
// if activating a challenge, the challenge must have a billing account id
if (
(!billingAccountId || billingAccountId === null) &&
Expand Down Expand Up @@ -1799,6 +1810,11 @@ async function updateChallenge(currentUser, challengeId, data) {

if (data.winners && data.winners.length && data.winners.length > 0) {
await validateWinners(data.winners, challengeResources);
if (_.get(challenge, "legacy.pureV5Task", false)) {
_.each(data.winners, (w) => {
w.type = constants.prizeSetTypes.ChallengePrizes;
});
}
}

// Only m2m tokens are allowed to modify the `task.*` information on a challenge
Expand Down Expand Up @@ -1891,25 +1907,28 @@ async function updateChallenge(currentUser, challengeId, data) {
}
}

try {
const updateInput = sanitizeRepeatedFieldsInUpdateRequest(_.omit(data, ["cancelReason"]));
if (!_.isEmpty(updateInput)) {
const grpcMetadata = new GrpcMetadata();
const updateInput = sanitizeRepeatedFieldsInUpdateRequest(_.omit(data, ["cancelReason"]));
if (!_.isEmpty(updateInput)) {
const grpcMetadata = new GrpcMetadata();

grpcMetadata.set("handle", currentUser.handle);
grpcMetadata.set("userId", currentUser.userId);
grpcMetadata.set("token", await getM2MToken());
grpcMetadata.set("handle", currentUser.handle);
grpcMetadata.set("userId", currentUser.userId);
grpcMetadata.set("token", await getM2MToken());

await challengeDomain.update(
{
filterCriteria: getScanCriteria({ id: challengeId }),
updateInput,
},
grpcMetadata
const newPrizeType = challengeHelper.validatePrizeSetsAndGetPrizeType(updateInput.prizeSets);
if (newPrizeType != null && existingPrizeType != null && newPrizeType !== existingPrizeType) {
throw new errors.BadRequestError(
`Cannot change prize type from ${existingPrizeType} to ${newPrizeType}`
);
}
} catch (e) {
throw e;

await challengeDomain.update(
{
filterCriteria: getScanCriteria({ id: challengeId }),
updateInput,
},
grpcMetadata
);
}

const updatedChallenge = await challengeDomain.lookup(getLookupCriteria("id", challengeId));
Expand Down Expand Up @@ -2119,9 +2138,7 @@ updateChallenge.schema = {
userId: Joi.number().integer().positive().required(),
handle: Joi.string().required(),
placement: Joi.number().integer().positive().required(),
type: Joi.string()
.valid(_.values(constants.prizeSetTypes))
.default(constants.prizeSetTypes.ChallengePrizes),
type: Joi.string().valid(_.values(constants.prizeSetTypes)),
})
.unknown(true)
)
Expand Down Expand Up @@ -2423,7 +2440,11 @@ advancePhase.schema = {
};

async function indexChallengeAndPostToKafka(updatedChallenge, track, type) {
convertPrizeSetValuesToDollars(updatedChallenge.prizeSets, updatedChallenge.overview);
const prizeType = challengeHelper.validatePrizeSetsAndGetPrizeType(updatedChallenge.prizeSets);

if (prizeType === constants.prizeTypes.USD) {
convertPrizeSetValuesToDollars(updatedChallenge.prizeSets, updatedChallenge.overview);
}

if (track == null || type == null) {
const trackAndTypeData = await challengeHelper.validateAndGetChallengeTypeAndTrack({
Expand Down
Loading