Skip to content

V4 Changes #31

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 32 commits into from
Nov 28, 2018
Merged
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
31e84ed
Adds support for v4 API and updates challenge service to use it.
a-lowe Jun 27, 2018
156381b
Updates getUserChallenges to use v4.
a-lowe Jun 28, 2018
c65af46
Merge branch 'master' into new-develop-mm
a-lowe Jun 29, 2018
ba0098e
Fixes issues with track being null and TCO events not displaying on e…
a-lowe Jun 30, 2018
81a0e8b
Adds "prepack" script
birdofpreyru Jul 2, 2018
9379580
Allows "-rcXXX" suffix on release tags
birdofpreyru Jul 2, 2018
d2b3912
Fix previous commit
birdofpreyru Jul 2, 2018
aa1bc0c
Another fix
birdofpreyru Jul 2, 2018
7581820
Update of release config
birdofpreyru Jul 2, 2018
bffd66c
Adds delay to challenge refresh on register/unregister to allow time …
a-lowe Jul 9, 2018
0c39928
Merge branch 'develop' into new-develop-mm
birdofpreyru Jul 10, 2018
405ec60
Adds challenge normalizations for legacy Marathon Matches. Fixes sor…
a-lowe Jul 18, 2018
709de69
Update package lock.
a-lowe Jul 18, 2018
af28491
Update version.
a-lowe Jul 18, 2018
8f45e42
Merge branch 'develop' into new-develop-mm
birdofpreyru Aug 16, 2018
bb6b247
After merge
sushilshinde Oct 25, 2018
fabce7b
fixing build issues
sushilshinde Oct 31, 2018
52f94bb
fixing build issues
sushilshinde Oct 31, 2018
3c914fe
Merge pull request #28 from sushilshinde/new-develop-mm
sushilshinde Oct 31, 2018
f05e63e
pushing build artifact - this is for testing only
sushilshinde Oct 31, 2018
14646af
deleting prod build artifacts
sushilshinde Nov 1, 2018
e0250be
issue_1732 fixed linting issues
Nov 1, 2018
d6f3edb
Merge pull request #29 from rashmi73/issue_1732
sushilshinde Nov 3, 2018
ef07dd6
adding prod dist - for testign only
sushilshinde Nov 5, 2018
bc45c27
deleting prod dist
sushilshinde Nov 19, 2018
54b5b14
removing hardcoding
sushilshinde Nov 19, 2018
3e32332
building dev dist
sushilshinde Nov 19, 2018
c761a65
adding prod folder
sushilshinde Nov 22, 2018
726db44
Changed npm version
sushilshinde Nov 28, 2018
1e4b154
Deleting dist folder, updating gitignore
sushilshinde Nov 28, 2018
aae7bd1
Fix eslint configs and tests
ThomasKranitsas Nov 28, 2018
626aa51
Merge pull request #30 from topcoder-platform/new-develop-mm
ThomasKranitsas Nov 28, 2018
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
Next Next commit
Adds support for v4 API and updates challenge service to use it.
  • Loading branch information
a-lowe committed Jun 28, 2018
commit 31e84ed18f4fe9a7ed3e3ad63fe844e5ba39ae02
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v8.11.1
8.11.2
17 changes: 17 additions & 0 deletions src/services/api.js
Original file line number Diff line number Diff line change
@@ -265,3 +265,20 @@ export function getApiV3(token) {
}
return lastApiV3;
}

/*
* Topcoder API v4.
*/

let lastApiV4 = null;
/**
* Returns a new or existing Api object for Topcoder API v4
* @param {String} token Optional. Auth token for Topcoder API v4. (Uses same Token as v3)
* @return {Api} API v3 service object.
*/
export function getApiV4(token) {
if (!lastApiV4 || lastApiV4.private.token !== token) {
lastApiV4 = new Api(config.API.V4, token);
}
return lastApiV4;
}
325 changes: 85 additions & 240 deletions src/services/challenges.js
Original file line number Diff line number Diff line change
@@ -11,89 +11,22 @@ import { decodeToken } from 'tc-accounts';
import logger from '../utils/logger';
import { setErrorIcon, ERROR_ICON_TYPES } from '../utils/errors';
import { COMPETITION_TRACKS, getApiResponsePayloadV3 } from '../utils/tc';
import { getApiV2, getApiV3 } from './api';
import { getApiV2, getApiV3, getApiV4 } from './api';

export const ORDER_BY = {
SUBMISSION_END_DATE: 'submissionEndDate',
};

/**
* Normalize name convention for subtrack
* @param {String} subTrack Challenge `subTrack`.
* @returns {String} Normalized subtrack ID.
*/
function normalizeNameConventionForSubtrack(subTrack) {
switch (subTrack) {
case 'Copilot Posting':
return 'COPILOT_POSTING';
case 'Web Design':
return 'WEB_DESIGNS';
case 'Logo Design':
return 'LOGO_DESIGN';
case 'Banners/Icons':
return 'BANNERS_OR_ICONS';
case 'Application Front-End Design':
return 'APPLICATION_FRONT_END_DESIGN';
case 'Widget or Mobile Screen Design':
return 'WIDGET_OR_MOBILE_SCREEN_DESIGN';
case 'Front-End Flash':
return 'FRONT_END_FLASH';
case 'Print/Presentation':
return 'PRINT_OR_PRESENTATION';
case 'Wireframes':
return 'WIREFRAMES';
case 'Idea Generation':
return 'IDEA_GENERATION';
case 'Studio Other':
return 'STUDIO_OTHER';
case 'Conceptualization':
return 'CONCEPTUALIZATION';
case 'Specification':
return 'SPECIFICATION';
case 'Architecture':
return 'ARCHITECTURE';
case 'Design':
return 'DESIGN';
case 'Development':
return 'DEVELOPMENT';
case 'RIA Build Competition':
return 'RIA_BUILD_COMPETITION';
case 'UI Prototype Competition':
return 'UI_PROTOTYPE_COMPETITION';
case 'Assembly Competition':
return 'ASSEMBLY_COMPETITION';
case 'Test Suites':
return 'TEST_SUITES';
case 'Test Scenarios':
return 'TEST_SCENARIOS';
case 'Content Creation':
return 'CONTENT_CREATION';
case 'Bug Hunt':
return 'BUG_HUNT';
case 'Design First2Finish':
return 'DESIGN_FIRST_2_FINISH';
case 'Code':
return 'CODE';
case 'First2Finish':
return 'FIRST_2_FINISH';
case 'Marathon Match':
case 'DEVELOP_MARATHON_MATCH':
return 'MARATHON_MATCH';
default:
return subTrack;
}
}

/**
* Normalizes a regular challenge details object received from the backend APIs.
* NOTE: It is possible, that this normalization is not necessary after we
* have moved to Topcoder API v3, but it is kept for now to minimize a risk of
* breaking anything.
* @todo Why this one is exported? It should be only used internally!
* @param {Object} v3 Challenge object received from the /v3/challenges/{id}
* @param {Object} v4 Challenge object received from the /v4/challenges/{id}
* endpoint.
* @param {Object} v3Filtered Challenge object received from the
* /v3/challenges?filter=id={id} endpoint.
* @param {Object} v4Filtered Challenge object received from the
* /v4/challenges?filter=id={id} endpoint.
* @param {Object} v3User Challenge object received from the
* /v3//members/{username}/challenges?filter=id={id} endpoint.
* If action was fired for authenticated visitor, v3_user will contain
@@ -104,95 +37,97 @@ function normalizeNameConventionForSubtrack(subTrack) {
* @param {String} username Optional.
* @return {Object} Normalized challenge object.
*/
export function normalizeChallengeDetails(v3, v3Filtered, v3User, username) {
export function normalizeChallengeDetails(v4, v4Filtered, v3User, username) {
// Normalize exising data to make it consistent with the rest of the code
const challenge = {
...v3,

id: v3.challengeId,
reliabilityBonus: v3Filtered.reliabilityBonus || 0,
status: (v3.currentStatus || '').toUpperCase(),

name: v3.challengeName,
projectId: Number(v3.projectId),
forumId: Number(v3.forumId),
introduction: v3.introduction || '',
detailedRequirements: v3.detailedRequirements,
finalSubmissionGuidelines: v3.finalSubmissionGuidelines,
screeningScorecardId: Number(v3.screeningScorecardId),
reviewScorecardId: Number(v3.reviewScorecardId),
numberOfCheckpointsPrizes: v3.numberOfCheckpointsPrizes,
topCheckPointPrize: v3.topCheckPointPrize,
submissionsViewable: v3.submissionsViewable || 'false',
reviewType: v3.reviewType,
allowStockArt: v3.allowStockArt === 'true',
fileTypes: v3.filetypes || [],
environment: v3.environment,
codeRepo: v3.codeRepo,
forumLink: v3.forumLink,
submissionLimit: Number(v3.submissionLimit) || 0,
drPoints: v3.digitalRunPoints,
directUrl: v3.directUrl,
technologies: _.isArray(v3.technology) ? v3.technology.join(', ') : '',
platforms: _.isArray(v3.platforms) ? v3.platforms.join(', ') : '',
prizes: v3.prize || [],
events: v3.event ? [
...v4,

id: v4.challengeId,
reliabilityBonus: v4Filtered.reliabilityBonus || 0,
status: (v4.currentStatus || '').toUpperCase(),

name: v4.challengeName || v4.challengeTitle,
projectId: Number(v4.projectId),
forumId: Number(v4.forumId),
introduction: v4.introduction || '',
detailedRequirements: v4.detailedRequirements === 'null' ? '' : v4.detailedRequirements,
finalSubmissionGuidelines: v4.finalSubmissionGuidelines === 'null' ? '' : v4.finalSubmissionGuidelines,
screeningScorecardId: Number(v4.screeningScorecardId),
reviewScorecardId: Number(v4.reviewScorecardId),
numberOfCheckpointsPrizes: v4.numberOfCheckpointsPrizes,
topCheckPointPrize: v4.topCheckPointPrize,
submissionsViewable: v4.submissionsViewable || 'false',
reviewType: v4.reviewType,
allowStockArt: v4.allowStockArt === 'true',
fileTypes: v4.filetypes || [],
environment: v4.environment,
codeRepo: v4.codeRepo,
forumLink: v4.forumLink,
submissionLimit: Number(v4.submissionLimit) || 0,
drPoints: v4.digitalRunPoints,
directUrl: v4.directUrl,
technologies: v4.technologies || v4.technology || [],
platforms: v4.platforms || [],
prizes: v4.prize || v4.prizes || [],
events: v4.event ? [
{
eventName: v3.event.eventShortDesc,
eventId: v3.event.id,
description: v3.event.eventDescription,
eventName: v4.event.eventShortDesc,
eventId: v4.event.id,
description: v4.event.eventDescription,
}] : [],
mainEvent: v3.event ? {
eventName: v3.event.eventShortDesc,
eventId: v3.event.id,
description: v3.event.eventDescription,
mainEvent: v4.event ? {
eventName: v4.event.eventShortDesc,
eventId: v4.event.id,
description: v4.event.eventDescription,
} : {},
terms: v3.terms,
submissions: v3.submissions,
checkpoints: v3.checkpoints,
documents: v3.documents || [],
numRegistrants: v3.numberOfRegistrants,
numberOfCheckpointSubmissions: v3.numberOfCheckpointSubmissions,
terms: v4.terms,
submissions: v4.submissions,
track: v4.subTrack === 'DEVELOP_MARATHON_MATCH' ? 'DATA_SCIENCE' : v4.track,
subTrack: v4.subTrack === 'DEVELOP_MARATHON_MATCH' ? 'MARATHON_MATCH' : v4.subTrack,
checkpoints: v4.checkpoints,
documents: v4.documents || [],
numRegistrants: v4.numberOfRegistrants,
numberOfCheckpointSubmissions: v4.numberOfCheckpointSubmissions,
registrants: v4.registrants || [],
};

// Fill missing data from v3_filtered
if (v3Filtered) {
if (v4Filtered) {
const groups = {};
if (v3Filtered.groupIds) {
v3Filtered.groupIds.forEach((id) => {
if (v4Filtered.groupIds) {
v4Filtered.groupIds.forEach((id) => {
groups[id] = true;
});
}

// Normalize name convention for subtrack
const newsubTrack = normalizeNameConventionForSubtrack(v3Filtered.subTrack);
// const newsubTrack = normalizeNameConventionForSubtrack(v4Filtered.subTrack);
_.defaults(challenge, {
componentId: v3Filtered.componentId,
contestId: v3Filtered.contestId,
componentId: v4Filtered.componentId,
contestId: v4Filtered.contestId,

track: newsubTrack === 'MARATHON_MATCH' ? 'DATA_SCIENCE' : v3Filtered.track,
subTrack: newsubTrack,
submissionEndDate: v3Filtered.submissionEndDate, // Dates are not correct in v3
submissionEndTimestamp: v3Filtered.submissionEndDate, // Dates are not correct in v3
track: v4Filtered.track,
submissionEndDate: v4Filtered.submissionEndDate, // Dates are not correct in v3
submissionEndTimestamp: v4Filtered.submissionEndDate, // Dates are not correct in v3

/* Taking phases from v3_filtered, because dates are not correct in v3 */
allPhases: v3Filtered.allPhases || [],
allPhases: v4Filtered.allPhases || [],

/* Taking phases from v3_filtered, because dates are not correct in v3 */
currentPhases: v3Filtered.currentPhases || [],
currentPhases: v4Filtered.currentPhases || [],

/* v3 returns incorrect value for numberOfSubmissions for some reason */
numSubmissions: v3Filtered.numSubmissions,
numSubmissions: v4Filtered.numSubmissions,
groups,
});

/* Taking winners from v3_filtered, because winners are returned empty in v3 */
/* TODO: Enforced due to problems with /v3/challenge/{ID} endpoint */
challenge.winners = v3Filtered.winners || [];
challenge.winners = v4Filtered.winners || [];
/* TODO: To compenstate the difference in structure of `submissions`
* in v3 and v3Filetered results (existing code needs v3Filtered version).
* in v3 and v3Filetered results (existing code needs v4Filtered version).
*/
challenge.submissions = v3Filtered.submissions || [];
challenge.submissions = v4Filtered.submissions || [];
}

// Fill missing data from v3_user
@@ -215,7 +150,7 @@ export function normalizeChallengeDetails(v3, v3Filtered, v3User, username) {

// A hot fix to show submissions for on-going challenges
if (!challenge.submissions || !challenge.submissions.length) {
challenge.submissions = v3.registrants
challenge.submissions = challenge.registrants
.filter(r => r.submissionDate || '')
.sort((a, b) => (a.submissionDate || '')
.localeCompare(b.submissionDate || ''));
@@ -243,14 +178,9 @@ export function normalizeChallenge(challenge, username) {
});
}

// Normalize name convention for subtrack
const newsubTrack = normalizeNameConventionForSubtrack(challenge.subTrack);
_.set(challenge, 'subTrack', newsubTrack);
_.set(challenge, 'track', newsubTrack === 'MARATHON_MATCH' ? 'DATA_SCIENCE' : challenge.track);
_.defaults(challenge, {
communities: new Set([COMPETITION_TRACKS[challenge.track]]),
groups,
platforms: '',
registrationOpen,
submissionEndTimestamp: challenge.submissionEndDate,
users: username ? { [username]: true } : {},
@@ -260,72 +190,13 @@ export function normalizeChallenge(challenge, username) {
if (!challenge.totalPrize) {
challenge.totalPrize = challenge.prizes.reduce((sum, x) => sum + x, 0);
}
if (!challenge.technologies) challenge.technologies = '';
/* eslint-enable no-param-reassign */
}
if (!challenge.technologies) challenge.technologies = [];
if (!challenge.platforms) challenge.platforms = [];

/**
* Normalizes a marathon match challenge object received from the backend.
* NOTE: This function is copied from the existing code in the challenge listing
* component. It is possible, that this normalization is not necessary after we
* have moved to Topcoder API v3, but it is kept for now to minimize a risk of
* breaking anything.
* @param {Object} challenge MM challenge object received from the backend.
* @param {String} username Optional.
* @return {Object} Normalized challenge.
*/
export function normalizeMarathonMatch(challenge, username) {
const startDate = _.get(challenge, 'rounds[0].codingStartAt') || challenge.startDate;
const endDate = _.get(challenge, 'rounds[0].codingEndAt') || challenge.endDate;
const endTimestamp = new Date(endDate).getTime();
const status = endTimestamp > Date.now() ? 'Open' : 'Close';
const allPhases = [{
actualStartTime: startDate,
challengeId: challenge.id,
phaseType: 'Registration',
phaseStatus: status,
scheduledEndTime: endDate,
}, {
actualStartTime: startDate,
challengeId: challenge.id,
phaseType: 'Submission',
phaseStatus: status,
scheduledEndTime: endDate,
}];
const groups = {};
if (challenge.groupIds) {
challenge.groupIds.forEach((id) => {
groups[id] = true;
});
if (challenge.subTrack === 'DEVELOP_MARATHON_MATCH') {
challenge.track = 'DATA_SCIENCE';
challenge.subTrack = 'MARATHON_MATCH';
}
_.defaults(challenge, {
challengeCommunity: 'Data',
challengeType: 'Marathon',
allPhases,
currentPhases: allPhases.filter(phase => phase.phaseStatus === 'Open'),
communities: new Set([COMPETITION_TRACKS.DATA_SCIENCE]),
currentPhaseName: endTimestamp > Date.now() ? 'Registration' : '',
groups,
numRegistrants: challenge.userIds ? challenge.userIds.length : 0,
numSubmissions: 0, // currently challenge doesn't return submission value
platforms: '',
prizes: [0],
registrationOpen: endTimestamp > Date.now()
&& (challenge.status !== 'PAST') ? 'Yes' : 'No',
registrationStartDate: challenge.startDate,
submissionEndDate: challenge.endDate,
submissionEndTimestamp: endTimestamp,
technologies: '',
totalPrize: 0,
track: 'DATA_SCIENCE',
status: endTimestamp > Date.now() ? 'ACTIVE' : 'COMPLETED',
subTrack: 'MARATHON_MATCH',
users: username ? { username: true } : {},
});
/* eslint-disable no-param-reassign */
challenge.endDate = endDate;
challenge.startDate = startDate;
if (challenge.status === 'PAST') challenge.status = 'COMPLETED';
/* eslint-enable no-param-reassign */
}

@@ -365,18 +236,22 @@ class ChallengesService {
* query parameter (this function takes care to stringify it properly).
* @param {Object} params Optional. A map of any other parameters beside
* `filter`.
* @param {Boolean} useV4 Optional. Whether a call to v4 API should be used.
*/
const getChallenges = async (
endpoint,
filters = {},
params = {},
useV4 = false,
) => {
const query = {
filter: qs.stringify(filters, { encode: false }),
...params,
};
const url = `${endpoint}?${qs.stringify(query)}`;
const res = await this.private.api.get(url).then(checkError);
const res = useV4
? await this.private.apiV4.get(url).then(checkError)
: await this.private.api.get(url).then(checkError);
return {
challenges: res.content || [],
totalCount: res.metadata.totalCount,
@@ -386,6 +261,7 @@ class ChallengesService {
this.private = {
api: getApiV3(tokenV3),
apiV2: getApiV2(tokenV2),
apiV4: getApiV4(tokenV3),
getChallenges,
tokenV2,
tokenV3,
@@ -465,27 +341,27 @@ class ChallengesService {
}

/**
* Gets challenge details from Topcoder API v3.
* Gets challenge details from Topcoder API v4.
* NOTE: This function also uses API v2 and other v3 endpoints for now, due
* to some information is missing or
* incorrect in the main v3 endpoint. This may change in the future.
* @param {Number|String} challengeId
* @return {Promise} Resolves to the challenge object.
*/
async getChallengeDetails(challengeId) {
const challengeV3 = await this.private.api.get(`/challenges/${challengeId}`)
const challengeV4 = await this.private.apiV4.get(`/challenges/${challengeId}`)
.then(checkError).then(res => res.content);

const challengeV3Filtered = await this.private.getChallenges('/challenges/', { id: challengeId })
const challengeV4Filtered = await this.private.getChallenges('/challenges/', { id: challengeId }, {}, true)
.then(res => res.challenges[0]);

const username = this.private.tokenV3 && decodeToken(this.private.tokenV3).handle;
const challengeV3User = username && await this.getUserChallenges(username, { id: challengeId })
.then(res => res.challenges[0]);

const challenge = normalizeChallengeDetails(
challengeV3,
challengeV3Filtered,
challengeV4,
challengeV4Filtered,
challengeV3User,
username,
);
@@ -530,27 +406,13 @@ class ChallengesService {
* @return {Promise} Resolves to the api response.
*/
getChallenges(filters, params) {
return this.private.getChallenges('/challenges/', filters, params)
return this.private.getChallenges('/challenges/', filters, params, true)
.then((res) => {
res.challenges.forEach(item => normalizeChallenge(item));
return res;
});
}

/**
* Gets marathon matches.
* @param {Object} filters Optional.
* @param {Object} params Optional.
* @return {Promise} Resolve to the api response.
*/
getMarathonMatches(filters, params) {
return this.private.getChallenges('/marathonMatches/', filters, params)
.then((res) => {
res.challenges.forEach(item => normalizeMarathonMatch(item));
return res;
});
}

/**
* Gets SRM matches.
* @param {Object} params
@@ -614,18 +476,6 @@ class ChallengesService {
return res.json();
}

/**
* Gets marathon matches of the specified user.
* @param {String} username User whose challenges we want to fetch.
* @param {Object} filters Optional.
* @param {Number} params Optional.
* @return {Promise} Resolves to the api response.
*/
getUserMarathonMatches(username, filters, params) {
const endpoint = `/members/${username.toLowerCase()}/mms/`;
return this.private.getChallenges(endpoint, filters, params);
}

/**
* Gets count of user's active challenges.
* @param {String} handle Topcoder user handle.
@@ -634,12 +484,7 @@ class ChallengesService {
getActiveChallengesCount(handle) {
const filter = { status: 'ACTIVE' };
const params = { limit: 1, offset: 0 };

const calls = [];
calls.push(this.getUserChallenges(handle, filter, params));
calls.push(this.getUserMarathonMatches(handle, filter, params));

return Promise.all(calls).then(([uch, umm]) => uch.totalCount + umm.totalCount);
return this.getUserChallenges(handle, filter, params).then(res => res.totalCount);
}

/**