diff --git a/__tests__/__snapshots__/index.js.snap b/__tests__/__snapshots__/index.js.snap
index 18663275..e4330bd4 100644
--- a/__tests__/__snapshots__/index.js.snap
+++ b/__tests__/__snapshots__/index.js.snap
@@ -303,7 +303,6 @@ Object {
"default": undefined,
"getService": [Function],
"normalizeChallenge": [Function],
- "normalizeChallengeDetails": [Function],
},
"communities": Object {
"default": undefined,
diff --git a/docs/services.challenges.md b/docs/services.challenges.md
index bf0c48d9..0ef01ac2 100644
--- a/docs/services.challenges.md
+++ b/docs/services.challenges.md
@@ -18,7 +18,7 @@ This module provides a service for convenient manipulation with
* [.close(challengeId, winnerId)](#module_services.challenges..ChallengesService+close) ⇒ Promise
* [.createTask(projectId, accountId, title, description, assignee, payment)](#module_services.challenges..ChallengesService+createTask) ⇒ Promise
* [.getChallengeDetails(challengeId)](#module_services.challenges..ChallengesService+getChallengeDetails) ⇒ Promise
- * [.getChallengeSubtracks()](#module_services.challenges..ChallengesService+getChallengeSubtracks) ⇒ Promise
+ * [.getChallengeTypes()](#module_services.challenges..ChallengesService+getChallengeTypes) ⇒ Promise
* [.getChallengeTags()](#module_services.challenges..ChallengesService+getChallengeTags) ⇒ Promise
* [.getChallenges(filters, params)](#module_services.challenges..ChallengesService+getChallenges) ⇒ Promise
* [.getMarathonMatches(filters, params)](#module_services.challenges..ChallengesService+getMarathonMatches) ⇒ Promise
@@ -119,7 +119,7 @@ Challenge service.
* [.close(challengeId, winnerId)](#module_services.challenges..ChallengesService+close) ⇒ Promise
* [.createTask(projectId, accountId, title, description, assignee, payment)](#module_services.challenges..ChallengesService+createTask) ⇒ Promise
* [.getChallengeDetails(challengeId)](#module_services.challenges..ChallengesService+getChallengeDetails) ⇒ Promise
- * [.getChallengeSubtracks()](#module_services.challenges..ChallengesService+getChallengeSubtracks) ⇒ Promise
+ * [.getChallengeTypes()](#module_services.challenges..ChallengesService+getChallengeTypes) ⇒ Promise
* [.getChallengeTags()](#module_services.challenges..ChallengesService+getChallengeTags) ⇒ Promise
* [.getChallenges(filters, params)](#module_services.challenges..ChallengesService+getChallenges) ⇒ Promise
* [.getMarathonMatches(filters, params)](#module_services.challenges..ChallengesService+getMarathonMatches) ⇒ Promise
@@ -203,9 +203,9 @@ incorrect in the main v3 endpoint. This may change in the future.
| --- | --- |
| challengeId | Number
\| String
|
-
+
-#### challengesService.getChallengeSubtracks() ⇒ Promise
+#### challengesService.getChallengeTypes() ⇒ Promise
Gets possible challenge subtracks.
**Kind**: instance method of [ChallengesService
](#module_services.challenges..ChallengesService)
diff --git a/src/actions/member-tasks.js b/src/actions/member-tasks.js
index b82d5bb7..9b9496a7 100644
--- a/src/actions/member-tasks.js
+++ b/src/actions/member-tasks.js
@@ -62,8 +62,8 @@ function getDone(uuid, projectId, pageNum, tokenV3) {
isTask: 1,
projectId,
}, {
- limit: PAGE_SIZE,
- offset: pageNum * PAGE_SIZE,
+ perPage: PAGE_SIZE,
+ page: pageNum + 1,
}).then(({ challenges, totalCount }) => ({
projectId,
tasks: challenges,
diff --git a/src/actions/members.js b/src/actions/members.js
index 1a500cb5..45b9606a 100644
--- a/src/actions/members.js
+++ b/src/actions/members.js
@@ -144,7 +144,7 @@ async function getActiveChallengesInit(handle, uuid) {
* @returns {Object} Payload
*/
async function getActiveChallengesDone(handle, uuid, tokenV3) {
- const filter = { status: 'ACTIVE' };
+ const filter = { status: 'Active' };
const service = getChallengesService(tokenV3);
/* TODO: Reuse `getAll` from `actions/challenge-listing`
/* after it moved from `community-app` to here.
@@ -152,8 +152,8 @@ async function getActiveChallengesDone(handle, uuid, tokenV3) {
function getAll(getter, page = 0, prev = null) {
const PAGE_SIZE = 50;
return getter({
- limit: PAGE_SIZE,
- offset: page * PAGE_SIZE,
+ perPage: PAGE_SIZE,
+ page: page + 1,
}).then(({ challenges: chunk }) => {
if (!chunk.length) return prev || [];
return getAll(getter, 1 + page, prev ? prev.concat(chunk) : chunk);
@@ -247,7 +247,7 @@ async function getSubtrackChallengesDone(
refresh,
) {
const filter = {
- status: 'completed',
+ status: 'Completed',
hasUserSubmittedForReview: 'true',
track,
subTrack,
diff --git a/src/actions/stats.js b/src/actions/stats.js
index 44612dcd..f9048b29 100644
--- a/src/actions/stats.js
+++ b/src/actions/stats.js
@@ -42,7 +42,7 @@ async function getCommunityStatsDone(community, uuid, challenges, token) {
/* TODO: At the moment, this component loads challenge objects to calculate
* the number of challenges and the total prize. Probably in future, we'll
* have a special API to get these data. */
- let filtered = challenges.filter(x => x.status === 'ACTIVE');
+ let filtered = challenges.filter(x => x.status === 'Active');
if (community.challengeFilter) {
const filterFunction = Filter.getFilterFunction(community.challengeFilter);
filtered = filtered.filter(filterFunction);
diff --git a/src/reducers/challenge.js b/src/reducers/challenge.js
index b2edf441..26cee382 100644
--- a/src/reducers/challenge.js
+++ b/src/reducers/challenge.js
@@ -462,7 +462,7 @@ export function factory(options = {}) {
const checkpointsPromise = track === 'design' ? (
redux.resolveAction(actions.challenge.fetchCheckpointsDone(tokens.tokenV2, challengeId))
) : null;
- const resultsPromise = _.get(details, 'payload.status', '') === 'COMPLETED' ? (
+ const resultsPromise = _.get(details, 'payload.status', '') === 'Completed' ? (
redux.resolveAction(actions.challenge.loadResultsDone(tokens, challengeId, track))
) : null;
return Promise.all([details, checkpointsPromise, resultsPromise]);
diff --git a/src/services/__mocks__/challenges.js b/src/services/__mocks__/challenges.js
index f881884e..0dc59e7c 100644
--- a/src/services/__mocks__/challenges.js
+++ b/src/services/__mocks__/challenges.js
@@ -134,7 +134,7 @@ export function normalizeChallengeDetails(v3, v3Filtered, v3User, v2, username)
// Fill some derived data
const registrationOpen = _.some(
challenge.allPhases,
- phase => phase.phaseType === 'Registration' && phase.phaseStatus === 'Open',
+ phase => phase.name === 'Registration' && phase.isOpen,
) ? 'Yes' : 'No';
_.defaults(challenge, {
communities: new Set([COMPETITION_TRACKS[challenge.track]]),
@@ -163,7 +163,7 @@ export function normalizeChallengeDetails(v3, v3Filtered, v3User, v2, username)
* @return {Object} Normalized challenge.
*/
export function normalizeChallenge(challenge, username) {
- const registrationOpen = challenge.allPhases.filter(d => d.phaseType === 'Registration')[0].phaseStatus === 'Open' ? 'Yes' : 'No';
+ const registrationOpen = challenge.allPhases.filter(d => d.name === 'Registration')[0].isOpen ? 'Yes' : 'No';
const groups = {};
if (challenge.groupIds) {
challenge.groupIds.forEach((id) => {
@@ -250,10 +250,10 @@ class ChallengesService {
}
/**
- * Gets possible challenge subtracks.
- * @return {Promise} Resolves to the array of subtrack names.
+ * Gets possible challenge types.
+ * @return {Promise} Resolves to the array of challenge type names.
*/
- getChallengeSubtracks() {
+ getChallengeTypes() {
return Promise.all([
this.private.apiV2.get('/design/challengetypes')
.then(res => (res.ok ? res.json() : new Error(res.statusText))),
diff --git a/src/services/challenges.js b/src/services/challenges.js
index 53ae2f0a..a7cdaf3f 100644
--- a/src/services/challenges.js
+++ b/src/services/challenges.js
@@ -18,150 +18,6 @@ export const ORDER_BY = {
SUBMISSION_END_DATE: 'submissionEndDate',
};
-/**
- * Normalizes a regular challenge details object received from the backend APIs.
- * @todo Why this one is exported? It should be only used internally!
- * @param {Object} challenge Challenge object received from the /challenges/{id}
- * endpoint.
- * @param {Object} filtered Challenge object received from the
- * /challenges?filter=id={id} endpoint.
- * @param {Object} user Challenge object received from the
- * /members/{username}/challenges?filter=id={id} endpoint.
- * If action was fired for authenticated visitor, `user` will contain
- * details fetched specifically for the user (thus may include additional
- * data comparing to the standard API response for the challenge details,
- * stored in `filtered`).
- * @param {String} username Optional.
- * @return {Object} Normalized challenge object.
- */
-export function normalizeChallengeDetails(challenge, filtered, user, username) {
- // Normalize exising data to make it consistent with the rest of the code
- const finalChallenge = {
- ...challenge,
-
- id: challenge.challengeId,
- reliabilityBonus: _.get(filtered, 'reliabilityBonus', 0),
- status: (challenge.currentStatus || '').toUpperCase(),
-
- allPhases: [],
- currentPhases: [],
- name: challenge.challengeName || challenge.challengeTitle,
- projectId: Number(challenge.projectId),
- forumId: Number(challenge.forumId),
- introduction: challenge.introduction || '',
- detailedRequirements: challenge.detailedRequirements === 'null' ? '' : challenge.detailedRequirements,
- finalSubmissionGuidelines: challenge.finalSubmissionGuidelines === 'null' ? '' : challenge.finalSubmissionGuidelines,
- screeningScorecardId: Number(challenge.screeningScorecardId),
- reviewScorecardId: Number(challenge.reviewScorecardId),
- numberOfCheckpointsPrizes: challenge.numberOfCheckpointsPrizes,
- topCheckPointPrize: challenge.topCheckPointPrize,
- submissionsViewable: challenge.submissionsViewable || 'false',
- reviewType: challenge.reviewType,
- allowStockArt: challenge.allowStockArt === 'true',
- fileTypes: challenge.filetypes || [],
- environment: challenge.environment,
- codeRepo: challenge.codeRepo,
- forumLink: challenge.forumLink,
- submissionLimit: Number(challenge.submissionLimit) || 0,
- drPoints: challenge.digitalRunPoints,
- directUrl: challenge.directUrl,
- technologies: challenge.technologies || challenge.technology || [],
- platforms: challenge.platforms || [],
- prizes: challenge.prize || challenge.prizes || [],
- events: _.map(challenge.event, e => ({
- eventName: e.eventShortDesc,
- eventId: e.id,
- description: e.eventDescription,
- })),
- terms: challenge.terms,
- submissions: challenge.submissions,
- track: _.toUpper(challenge.challengeCommunity),
- subTrack: challenge.subTrack,
- checkpoints: challenge.checkpoints,
- documents: challenge.documents || [],
- numRegistrants: challenge.numberOfRegistrants,
- numberOfCheckpointSubmissions: challenge.numberOfCheckpointSubmissions,
- registrants: challenge.registrants || [],
- };
-
- // Winners have different field names, needs to be normalized to match `filtered` and `challenge`
- finalChallenge.winners = _.map(
- challenge.winners,
- (winner, index) => ({
- ...winner,
- handle: winner.submitter,
- placement: winner.rank || index + 1, // Legacy MMs do not have a rank but are sorted by points
- }),
- );
-
- if (finalChallenge.subTrack === 'MARATHON_MATCH') {
- finalChallenge.track = 'DATA_SCIENCE';
- }
-
- // It's not clear if this will be the main event, will need to be investigated
- finalChallenge.mainEvent = finalChallenge.events[0] || {};
-
- /* It's unclear if these normalization steps are still required for `challenge` */
- // Fill missing data from filtered
- if (filtered) {
- const groups = {};
- if (filtered.groupIds) {
- filtered.groupIds.forEach((id) => {
- groups[id] = true;
- });
- }
-
- _.merge(finalChallenge, {
- componentId: filtered.componentId,
- contestId: filtered.contestId,
-
- submissionEndDate: filtered.submissionEndDate, // Dates are not correct in `challenge`
- submissionEndTimestamp: filtered.submissionEndDate, // Dates are not correct in `challenge`
-
- /* Taking phases from filtered, because dates are not correct in `challenge` */
- allPhases: filtered.allPhases || [],
-
- /* Taking phases from filtered, because dates are not correct in `challenge` */
- currentPhases: filtered.currentPhases || [],
-
- /* `challenge` has incorrect value for numberOfSubmissions for some reason */
- numSubmissions: filtered.numSubmissions,
- groups,
- });
- }
-
- // Fill missing data from user
- if (user) {
- _.defaults(finalChallenge, {
- userDetails: user.userDetails,
- });
- }
-
- // Fill some derived data
- const registrationOpen = _.some(
- finalChallenge.allPhases,
- phase => phase.phaseType === 'Registration' && phase.phaseStatus === 'Open',
- ) ? 'Yes' : 'No';
- _.defaults(finalChallenge, {
- communities: new Set([COMPETITION_TRACKS[finalChallenge.track]]),
- registrationOpen,
- users: username ? { [username]: true } : {},
- });
-
- // A hot fix to show submissions for on-going challenges
- if (!finalChallenge.submissions || !finalChallenge.submissions.length) {
- finalChallenge.submissions = finalChallenge.registrants
- .filter(r => r.submissionDate || '')
- .sort((a, b) => (a.submissionDate || '')
- .localeCompare(b.submissionDate || ''));
- }
-
- if (!finalChallenge.allPhases) finalChallenge.allPhases = [];
- if (!finalChallenge.track) finalChallenge.track = '';
-
- return finalChallenge;
-}
-
/**
* Normalizes a regular challenge object received from the backend.
* NOTE: This function is copied from the existing code in the challenge listing
@@ -173,31 +29,38 @@ export function normalizeChallengeDetails(challenge, filtered, user, username) {
* @param {String} username Optional.
*/
export function normalizeChallenge(challenge, username) {
- const registrationOpen = challenge.allPhases.filter(d => d.phaseType === 'Registration')[0].phaseStatus === 'Open' ? 'Yes' : 'No';
+ const phases = challenge.allPhases || challenge.phases || [];
+ let registrationOpen = phases.filter(d => d.name === 'Registration')[0];
+ if (registrationOpen && registrationOpen.isOpen) {
+ registrationOpen = 'Yes';
+ } else {
+ registrationOpen = 'No';
+ }
const groups = {};
- if (challenge.groupIds) {
- challenge.groupIds.forEach((id) => {
+ if (challenge.groups) {
+ challenge.groups.forEach((id) => {
groups[id] = true;
});
}
/* eslint-disable no-param-reassign */
- if (!challenge.prizes) challenge.prizes = challenge.prize || [];
- if (!challenge.totalPrize) {
- challenge.totalPrize = challenge.prizes.reduce((sum, x) => sum + x, 0);
- }
- if (!challenge.technologies) challenge.technologies = [];
+ if (!challenge.prizeSets) challenge.prizeSets = [];
+ if (!challenge.tags) challenge.tags = [];
if (!challenge.platforms) challenge.platforms = [];
- if (challenge.subTrack === 'DEVELOP_MARATHON_MATCH') {
- challenge.track = 'DATA_SCIENCE';
+ if (challenge.type === 'Marathon Match') {
+ challenge.legacy.track = 'DATA_SCIENCE';
}
/* eslint-enable no-param-reassign */
+ let submissionEndTimestamp = phases.filter(d => d.name === 'Submission')[0];
+ if (submissionEndTimestamp) {
+ submissionEndTimestamp = submissionEndTimestamp.scheduledEndDate;
+ }
_.defaults(challenge, {
- communities: new Set([COMPETITION_TRACKS[challenge.track]]),
+ communities: new Set([COMPETITION_TRACKS[challenge.legacy.track]]),
groups,
registrationOpen,
- submissionEndTimestamp: challenge.submissionEndDate,
+ submissionEndTimestamp,
users: username ? { [username]: true } : {},
});
}
@@ -220,6 +83,29 @@ async function checkError(res) {
return jsonRes;
}
+/**
+ * Helper method that checks for HTTP error response v5 and throws Error in this case.
+ * @param {Object} res HTTP response object
+ * @return {Object} API JSON response object
+ * @private
+ */
+async function checkErrorV5(res) {
+ if (!res.ok) {
+ if (res.status >= 500) {
+ setErrorIcon(ERROR_ICON_TYPES.API, '/challenges', res.statusText);
+ }
+ throw new Error(res.statusText);
+ }
+ const jsonRes = (await res.json());
+ if (jsonRes.message) {
+ throw new Error(res.message);
+ }
+ return {
+ result: jsonRes,
+ headers: res.headers,
+ };
+}
+
/**
* Challenge service.
*/
@@ -245,22 +131,59 @@ class ChallengesService {
params = {},
) => {
const query = {
- filter: qs.stringify(filters, { encode: false }),
+ ...filters,
...params,
};
const url = `${endpoint}?${qs.stringify(query)}`;
- const res = await this.private.api.get(url).then(checkError);
+ const res = await this.private.apiV5.get(url).then(checkErrorV5);
return {
- challenges: res.content || [],
- totalCount: res.metadata.totalCount,
- meta: res.metadata,
+ challenges: res.result || [],
+ totalCount: res.headers.get('x-total'),
+ meta: {
+ allChallengesCount: res.headers.get('x-total'),
+ myChallengesCount: 0,
+ ongoingChallengesCount: 0,
+ openChallengesCount: 0,
+ totalCount: res.headers.get('x-total'),
+ },
+ };
+ };
+ /**
+ * Private function being re-used in all methods related to getting
+ * challenges. It handles query-related arguments in the uniform way:
+ * @param {String} endpoint API endpoint, where the request will be send.
+ * @param {Object} filters Optional. A map of filters to pass as `filter`
+ * query parameter (this function takes care to stringify it properly).
+ * @param {Object} params Optional. A map of any other parameters beside
+ * `filter`.
+ */
+ const getMemberChallenges = async (
+ endpoint,
+ filters = {},
+ params = {},
+ ) => {
+ const memberId = decodeToken(this.private.tokenV3).userId;
+ const query = {
+ ...params,
+ ...filters,
+ memberId,
+ };
+ const url = `${endpoint}?${qs.stringify(_.omit(query, ['limit', 'offset', 'technologies']))}`;
+ const res = await this.private.apiV5.get(url).then(checkError);
+ const totalCount = res.length;
+ return {
+ challenges: res || [],
+ totalCount,
};
};
this.private = {
api: getApi('V4', tokenV3),
+ apiV5: getApi('V5', tokenV3),
apiV2: getApi('V2', tokenV2),
+ apiV3: getApi('V3', tokenV3),
getChallenges,
+ getMemberChallenges,
tokenV2,
tokenV3,
memberService: getMembersService(),
@@ -274,7 +197,12 @@ class ChallengesService {
* is rejected.
*/
async activate(challengeId) {
- let res = await this.private.api.post(`/challenges/${challengeId}/activate`);
+ const params = {
+ status: 'Active',
+ };
+
+ let res = await this.private.apiV5.patch(`/challenge/${challengeId}`, params);
+
if (!res.ok) throw new Error(res.statusText);
res = (await res.json()).result;
if (res.status !== 200) throw new Error(res.content);
@@ -284,15 +212,14 @@ class ChallengesService {
/**
* Closes the specified challenge.
* @param {Number} challengeId
- * @param {Number} winnerId Optional. ID of the assignee to declare the
- * winner.
* @return {Promise} Resolves to null value in case of success; otherwise it
* is rejected.
*/
- async close(challengeId, winnerId) {
- let url = `/challenges/${challengeId}/close`;
- if (winnerId) url = `${url}?winnerId=${winnerId}`;
- let res = await this.private.api.post(url);
+ async close(challengeId) {
+ const params = {
+ status: 'Completed',
+ };
+ let res = await this.private.apiV5.patch(`/challenges/${challengeId}`, params);
if (!res.ok) throw new Error(res.statusText);
res = (await res.json()).result;
if (res.status !== 200) throw new Error(res.content);
@@ -310,7 +237,7 @@ class ChallengesService {
* @param {String} submissionGuidelines
* @param {Number} copilotId
* @param {Number} copilotFee
- * @param {?} technologies
+ * @param {?} tags
* @return {Promise} Resolves to the created challenge object (payment task).
*/
async createTask(
@@ -323,24 +250,41 @@ class ChallengesService {
submissionGuidelines,
copilotId,
copilotFee,
- technologies,
+ tags,
) {
+ const registrationPhase = await this.private.apiV5.get('/challenge-phases?name=Registration');
+
const payload = {
param: {
- assignees: [assignee],
- billingAccountId: accountId,
- confidentialityType: 'public',
- detailedRequirements: description,
- submissionGuidelines,
- milestoneId: 1,
name: title,
- technologies,
- prizes: payment ? [payment] : [],
+ typeId: 'e885273d-aeda-42c0-917d-bfbf979afbba',
+ description,
+ legacy: {
+ track: 'FIRST_2_FINISH',
+ reviewType: 'INTERNAL',
+ confidentialityType: 'public',
+ billingAccountId: accountId,
+ },
+ phases: [
+ {
+ phaseId: registrationPhase.id,
+ scheduledEndDate: moment().toISOString(),
+ },
+ ],
+ prizeSets: [
+ {
+ type: 'Challenge Prizes',
+ description: 'Challenge Prize',
+ prizes: [
+ {
+ value: payment,
+ type: 'First Placement',
+ },
+ ],
+ },
+ ],
+ tags,
projectId,
- registrationStartsAt: moment().toISOString(),
- reviewType: 'INTERNAL',
- subTrack: 'FIRST_2_FINISH',
- task: true,
},
};
if (copilotId) {
@@ -349,7 +293,7 @@ class ChallengesService {
copilotFee,
});
}
- let res = await this.private.api.postJson('/challenges', payload);
+ let res = await this.private.apiV5.postJson('/challenges', payload);
if (!res.ok) throw new Error(res.statusText);
res = (await res.json()).result;
if (res.status !== 200) throw new Error(res.content);
@@ -365,26 +309,10 @@ class ChallengesService {
* @return {Promise} Resolves to the challenge object.
*/
async getChallengeDetails(challengeId) {
- const challenge = await this.private.api.get(`/challenges/${challengeId}`)
- .then(checkError).then(res => res.content);
-
const challengeFiltered = await this.private.getChallenges('/challenges/', { id: challengeId })
.then(res => res.challenges[0]);
- const username = this.private.tokenV3 && decodeToken(this.private.tokenV3).handle;
- const challengeUser = username && await this.getUserChallenges(username, { id: challengeId })
- .then(res => res.challenges[0]).catch(() => null);
-
- const finalChallenge = normalizeChallengeDetails(
- challenge,
- challengeFiltered,
- challengeUser,
- username,
- );
-
- finalChallenge.fetchedWithAuth = Boolean(this.private.api.private.token);
-
- return finalChallenge;
+ return challengeFiltered;
}
/**
@@ -393,22 +321,22 @@ class ChallengesService {
* @return {Promise} Resolves to the challenge registrants array.
*/
async getChallengeRegistrants(challengeId) {
- const challenge = await this.private.api.get(`/challenges/${challengeId}`)
- .then(checkError).then(res => res.content);
- return challenge.registrants;
+ const registrants = await this.private.apiV5.get(`/resources/challengeId=${challengeId}`)
+ .then(checkError).then(res => res);
+ return registrants || [];
}
/**
- * Gets possible challenge subtracks.
+ * Gets possible challenge types.
* @return {Promise} Resolves to the array of subtrack names.
*/
- getChallengeSubtracks() {
- return this.private.api.get('/challenge-types')
+ getChallengeTypes() {
+ return this.private.apiV5.get('/challenge-types')
.then(res => (res.ok ? res.json() : new Error(res.statusText)))
.then(res => (
- res.result.status === 200
- ? res.result.content
- : new Error(res.result.content)
+ res.message
+ ? new Error(res.message)
+ : res
));
}
@@ -443,39 +371,61 @@ class ChallengesService {
/**
* Gets SRM matches.
* @param {Object} params
+ * @param {string} typeId Challenge SRM TypeId
* @return {Promise}
*/
async getSrms(params) {
- const res = await this.private.api.get(`/srms/?${qs.stringify(params)}`);
+ const res = await this.private.apiV5.get(`/challenges/?${qs.stringify(params)}`);
return getApiResponsePayload(res);
}
+ static updateFiltersParamsForGettingMemberChallenges(filters, params) {
+ if (params && params.perPage) {
+ // eslint-disable-next-line no-param-reassign
+ params.offset = (params.page - 1) * params.perPage;
+ // eslint-disable-next-line no-param-reassign
+ params.limit = params.perPage;
+ }
+ }
+
/**
* Gets challenges of the specified user.
- * @param {String} username User whose challenges we want to fetch.
+ * @param {String} userId User id whose challenges we want to fetch.
* @param {Object} filters Optional.
* @param {Number} params Optional.
* @return {Promise} Resolves to the api response.
*/
- getUserChallenges(username, filters, params) {
- const endpoint = `/members/${username.toLowerCase()}/challenges/`;
- return this.private.getChallenges(endpoint, filters, params)
+ getUserChallenges(userId, filters, params) {
+ const userFilters = _.cloneDeep(filters);
+ ChallengesService.updateFiltersParamsForGettingMemberChallenges(userFilters, params);
+ const query = {
+ ...params,
+ ...userFilters,
+ memberId: userId,
+ };
+ const endpoint = '/challenges';
+ const url = `${endpoint}?${qs.stringify(_.omit(query, ['limit', 'offset', 'technologies']))}`;
+
+ return this.private.apiV5.get(url)
.then((res) => {
- res.challenges.forEach(item => normalizeChallenge(item, username));
+ res.challenges.forEach(item => normalizeChallenge(item, userId));
return res;
});
}
/**
* Gets marathon matches of the specified user.
- * @param {String} username User whose challenges we want to fetch.
+ * @param {String} userId 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);
+ async getUserMarathonMatches(userId) {
+ const marathonTypeId = 'c2579605-e294-4967-b3db-875ef85240cd';
+ const url = `/challenges?typeId=${marathonTypeId}&memberId=${userId}`;
+
+ const res = await this.private.apiV5.get(url);
+ return res;
}
/**
@@ -485,19 +435,33 @@ class ChallengesService {
* @return {Promise}
*/
async getUserSrms(handle, params) {
- const url = `/members/${handle}/srms/?${qs.stringify(params)}`;
- const res = await this.private.api.get(url);
+ const challenges = await this.private.apiV5.get(`/resources?memberHandle=${handle}`);
+ let newParams = params;
+ if (challenges) {
+ const { challengeId } = challenges[0];
+ newParams = {
+ ...params,
+ challengeId,
+ };
+ }
+
+ const url = `/challenges/${qs.stringify(newParams)}`;
+ const res = await this.private.apiV5.get(url);
return getApiResponsePayload(res);
}
/**
* Registers user to the specified challenge.
* @param {String} challengeId
+ * @param {String} memberHandle
+ * @param {String} roleId
* @return {Promise}
*/
- async register(challengeId) {
- const endpoint = `/challenges/${challengeId}/register`;
- const res = await this.private.api.postJson(endpoint);
+ async register(challengeId, memberHandle, roleId) {
+ const params = {
+ challengeId, memberHandle, roleId,
+ };
+ const res = await this.private.apiV5.post('/resources', params);
if (!res.ok) throw new Error(res.statusText);
return res.json();
}
@@ -505,11 +469,15 @@ class ChallengesService {
/**
* Unregisters user from the specified challenge.
* @param {String} challengeId
+ * @param {String} memberHandle
+ * @param {String} roleId
* @return {Promise}
*/
- async unregister(challengeId) {
- const endpoint = `/challenges/${challengeId}/unregister`;
- const res = await this.private.api.post(endpoint);
+ async unregister(challengeId, memberHandle, roleId) {
+ const params = {
+ challengeId, memberHandle, roleId,
+ };
+ const res = await this.private.apiV5.delete('/resources', params);
if (!res.ok) throw new Error(res.statusText);
return res.json();
}
@@ -520,7 +488,7 @@ class ChallengesService {
* @return {Action} Resolves to the api response.
*/
getActiveChallengesCount(handle) {
- const filter = { status: 'ACTIVE' };
+ const filter = { status: 'Active' };
const params = { limit: 1, offset: 0 };
return this.getUserChallenges(handle, filter, params).then(res => res.totalCount);
}
@@ -579,9 +547,8 @@ class ChallengesService {
* @return {Promise}
*/
async updateChallenge(challenge) {
- const URL = `/challenges/${challenge.id}`;
- const body = { param: challenge };
- let res = await this.private.api.putJson(URL, body);
+ const url = `/challenges/${challenge.id}`;
+ let res = await this.private.apiV5.put(url, challenge);
if (!res.ok) throw new Error(res.statusText);
res = (await res.json()).result;
if (res.status !== 200) throw new Error(res.content);
@@ -602,10 +569,10 @@ class ChallengesService {
*/
async getUserRolesInChallenge(challengeId) {
const user = decodeToken(this.private.tokenV3);
- const username = user.handle || user.payload.handle;
- const url = `/members/${username.toLowerCase()}/challenges`;
- const data = await this.private.getChallenges(url, { id: challengeId });
- return data.challenges[0].userDetails.roles;
+ const url = `/resources?challengeId=${challengeId}?memberHandle=${user.handle}`;
+ const resources = await this.private.apiV5.get(url);
+ if (resources) return _.map(resources, 'roleId');
+ throw new Error(`Failed to fetch user role from challenge #${challengeId}`);
}
}
diff --git a/src/utils/challenge/filter.js b/src/utils/challenge/filter.js
index 28e00654..30fe6210 100644
--- a/src/utils/challenge/filter.js
+++ b/src/utils/challenge/filter.js
@@ -70,11 +70,6 @@ import { COMPETITION_TRACKS, REVIEW_OPPORTUNITY_TYPES } from '../tc';
* from the filter state object, and returns true or false depending on it.
*/
-function filterByEndDate(challenge, state) {
- if (!state.endDate) return true;
- return moment(state.endDate).isAfter(challenge.registrationStartDate);
-}
-
function filterByGroupIds(challenge, state) {
if (!state.groupIds) return true;
return state.groupIds.some(id => challenge.groups[id]);
@@ -86,16 +81,17 @@ function filterByRegistrationOpen(challenge, state) {
if (challenge.registrationOpen) {
return challenge.registrationOpen === 'Yes';
}
- if (challenge.subTrack === 'MARATHON_MATCH') {
- return challenge.status !== 'PAST';
+ if (challenge.challengeType && challenge.challengeType.name === 'Marathon Match') {
+ return challenge.status !== 'Past';
}
- const registrationPhase = challenge.allPhases.find(item => item.phaseType === 'Registration');
- if (!registrationPhase || registrationPhase.phaseStatus !== 'Open') {
+ const challengePhases = challenge.allPhases || challenge.phases || [];
+ const registrationPhase = challengePhases.find(item => item.name === 'Registration')[0];
+ if (!registrationPhase || !registrationPhase.isOpen) {
return false;
}
if (challenge.track === 'DESIGN') {
- const checkpointPhase = challenge.allPhases.find(item => item.phaseType === 'Checkpoint Submission');
- return !checkpointPhase || checkpointPhase.phaseStatus !== 'Closed';
+ const checkpointPhase = challengePhases.find(item => item.name === 'Checkpoint Submission')[0];
+ return !checkpointPhase || !checkpointPhase.isOpen;
}
return true;
};
@@ -115,12 +111,28 @@ function filterByReviewOpportunityType(opp, state) {
function filterByStartDate(challenge, state) {
if (!state.startDate) return true;
- return moment(state.startDate).isBefore(challenge.submissionEndDate);
+ const submissionPhase = challenge.phases.filter(d => d.name === 'Submission')[0];
+ if (submissionPhase) {
+ return moment(state.startDate).isBefore(submissionPhase.scheduledEndDate);
+ }
+ return false;
+}
+
+function filterByEndDate(challenge, state) {
+ if (!state.endDate) return true;
+ const registrationPhase = challenge.phases.filter(d => d.name === 'Registration')[0];
+ if (registrationPhase) {
+ return moment(state.endDate).isAfter(registrationPhase.scheduledStartDate);
+ }
+ return false;
}
function filterByStarted(challenge, state) {
if (_.isUndefined(state.started)) return true;
- return moment(challenge.registrationStartDate).isBefore(Date.now());
+ if (!challenge.phases) {
+ return true;
+ }
+ return _.some(challenge.phases, { isOpen: true, name: 'Registration' });
}
function filterByStatus(challenge, state) {
@@ -128,28 +140,16 @@ function filterByStatus(challenge, state) {
return state.status.includes(challenge.status);
}
-function filterBySubtracks(challenge, state) {
- if (!state.subtracks) return true;
-
- /* TODO: Although this is taken from the current code in prod,
- * it probably does not work in all cases. It should be double-checked,
- * why challenge subtracks in challenge objects are different from those
- * return from the API as the list of possible subtracks. */
- const filterSubtracks = state.subtracks.map(item => item.toLowerCase().replace(/[_ ]/g, ''));
- const challengeSubtrack = challenge.subTrack.toLowerCase().replace(/[_ ]/g, '');
- return filterSubtracks.includes(challengeSubtrack);
-}
-
function filterByTags(challenge, state) {
if (!state.tags) return true;
- const { platforms, technologies } = challenge;
- const str = `${platforms} ${technologies}`.toLowerCase();
+ const { platforms, tags } = challenge;
+ const str = `${platforms} ${tags}`.toLowerCase();
return state.tags.some(tag => str.includes(tag.toLowerCase()));
}
function filterByText(challenge, state) {
if (!state.text) return true;
- const str = `${challenge.name} ${challenge.platforms} ${challenge.technologies}`
+ const str = `${challenge.name} ${challenge.tags} ${challenge.platforms} ${challenge.tags}`
.toLowerCase();
return str.includes(state.text.toLowerCase());
}
@@ -160,13 +160,18 @@ function filterByTrack(challenge, state) {
/* Development challenges having Data Science tech tag, still should be
* included into data science track. */
if (state.tracks[COMPETITION_TRACKS.DATA_SCIENCE]
- && _.includes(challenge.technologies, 'Data Science')) {
+ && _.includes(challenge.tags, 'Data Science')) {
return true;
}
return _.keys(state.tracks).some(track => challenge.communities.has(track));
}
+function filterBySubtracks(challenge, state) {
+ if (!state.subtracks) return true;
+ return state.subtracks.includes(challenge.typeId);
+}
+
function filterByUpcoming(challenge, state) {
if (_.isUndefined(state.upcoming)) return true;
return moment().isBefore(challenge.registrationStartDate);