Skip to content

Commit 199ae60

Browse files
authored
Merge pull request #4011 from topcoder-platform/develop
feat: recommended challenge and thrive articles
2 parents e316f4f + ce91682 commit 199ae60

File tree

26 files changed

+1327
-221
lines changed

26 files changed

+1327
-221
lines changed

.circleci/config.yml

+1-4
Original file line numberDiff line numberDiff line change
@@ -174,23 +174,20 @@ workflows:
174174
filters:
175175
branches:
176176
only:
177-
- develop
178-
- hot-fix-past-challenge-search
177+
- develop
179178
# This is alternate dev env for parallel testing
180179
- "build-test":
181180
context : org-global
182181
filters:
183182
branches:
184183
only:
185-
- develop
186184
- hot-fixes-leaderboard
187185
# This is beta env for production soft releases
188186
- "build-prod-beta":
189187
context : org-global
190188
filters:
191189
branches:
192190
only:
193-
- develop
194191
- feature-contentful
195192
# Production builds are exectuted only on tagged commits to the
196193
# master branch.

config/default.js

+5
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ module.exports = {
3232
*/
3333
CHALLENGE_DETAILS_REFRESH_DELAY: 3000,
3434

35+
/* Max number of recommended challenges */
36+
CHALLENGE_DETAILS_MAX_NUMBER_RECOMMENDED_CHALLENGES: 3,
37+
3538
COOKIES: {
3639
/* Expiration time [days] for browser cookies set by the App. */
3740
MAXAGE: 7,
@@ -115,6 +118,8 @@ module.exports = {
115118
FORUMS: 'https://apps.topcoder-dev.com/forums',
116119
HELP: 'https://www.topcoder.com/thrive/tracks?track=Topcoder',
117120

121+
THRIVE: 'https://www.topcoder.com/thrive',
122+
118123
COMMUNITIES: {
119124
BLOCKCHAIN: 'https://blockchain.topcoder-dev.com',
120125
COGNITIVE: 'https://cognitive.topcoder-dev.com',

src/assets/images/calendar.svg

+36
Loading

src/shared/actions/challenge-listing/index.js

+48-44
Original file line numberDiff line numberDiff line change
@@ -80,26 +80,25 @@ function getActiveChallengesInit(uuid, page, frontFilter) {
8080
return { uuid, page, frontFilter };
8181
}
8282

83-
/** TODO: Inspect if the 2 actions bellow can be removed?
84-
* They do duplicate what is done in `getActiveChallengesDone` but fetch all challenges
85-
* which was refactored in listing-improve
83+
/**
84+
* Get all challenges and match with user challenges
85+
* @param {String} uuid progress id
86+
* @param {String} tokenV3 token v3
87+
* @param {Object} filter filter object
88+
* @param {number} page start page
8689
*/
87-
function getAllActiveChallengesInit(uuid) {
88-
return uuid;
89-
}
90-
function getAllActiveChallengesDone(uuid, tokenV3) {
91-
const filter = { status: 'ACTIVE' };
90+
function getAllActiveChallengesWithUsersDone(uuid, tokenV3, filter, page = 0) {
9291
const service = getService(tokenV3);
9392
const calls = [
94-
getAll(params => service.getChallenges(filter, params)),
93+
getAll(params => service.getChallenges(filter, params), page),
9594
];
9695
let user;
9796
if (tokenV3) {
9897
user = decodeToken(tokenV3).handle;
9998
// Handle any errors on this endpoint so that the non-user specific challenges
10099
// will still be loaded.
101100
calls.push(getAll(params => service.getUserChallenges(user, filter, params)
102-
.catch(() => ({ challenges: [] }))));
101+
.catch(() => ({ challenges: [] }))), page);
103102
}
104103
return Promise.all(calls).then(([ch, uch]) => {
105104
/* uch array contains challenges where the user is participating in
@@ -120,10 +119,22 @@ function getAllActiveChallengesDone(uuid, tokenV3) {
120119
});
121120
}
122121

123-
return { uuid, challenges: ch };
122+
return { uuid, challenges: ch, ...filter };
124123
});
125124
}
126125

126+
/** TODO: Inspect if the 2 actions bellow can be removed?
127+
* They do duplicate what is done in `getActiveChallengesDone` but fetch all challenges
128+
* which was refactored in listing-improve
129+
*/
130+
function getAllActiveChallengesInit(uuid) {
131+
return uuid;
132+
}
133+
function getAllActiveChallengesDone(uuid, tokenV3) {
134+
const filter = { status: 'ACTIVE' };
135+
return getAllActiveChallengesWithUsersDone(uuid, tokenV3, filter);
136+
}
137+
127138
/**
128139
* Gets 1 page of active challenges (including marathon matches) from the backend.
129140
* Once this action is completed any active challenges saved to the state before
@@ -200,43 +211,33 @@ function getRestActiveChallengesInit(uuid) {
200211

201212
/**
202213
* Loading all challenges
203-
* @param {*} uuid
204-
* @param {*} tokenV3
214+
* @param {String} uuid progress id
215+
* @param {String} tokenV3 token v3
205216
*/
206217
function getRestActiveChallengesDone(uuid, tokenV3) {
207218
const filter = { status: 'ACTIVE' };
208-
const service = getService(tokenV3);
209-
const calls = [
210-
getAll(params => service.getChallenges(filter, params), 1),
211-
];
212-
let user;
213-
if (tokenV3) {
214-
user = decodeToken(tokenV3).handle;
215-
calls.push(getAll(params => service.getUserChallenges(user, filter, params)
216-
.catch(() => ({ challenges: [] }))), 1);
217-
}
218-
return Promise.all(calls).then(([ch, uch]) => {
219-
/* uch array contains challenges where the user is participating in
220-
* some role. The same challenge are already listed in res array, but they
221-
* are not attributed to the user there. This block of code marks user
222-
* challenges in an efficient way. */
223-
if (uch) {
224-
const map = {};
225-
uch.forEach((item) => { map[item.id] = item; });
226-
ch.forEach((item) => {
227-
if (map[item.id]) {
228-
/* It is fine to reassing, as the array we modifying is created just
229-
* above within the same function. */
230-
/* eslint-disable no-param-reassign */
231-
item.users[user] = true;
232-
item.userDetails = map[item.id].userDetails;
233-
/* eslint-enable no-param-reassign */
234-
}
235-
});
236-
}
219+
return getAllActiveChallengesWithUsersDone(uuid, tokenV3, filter, 1);
220+
}
237221

238-
return { uuid, challenges: ch };
239-
});
222+
/**
223+
* Prepare for getting all recommended challenges
224+
* @param {String} uuid progress id
225+
*/
226+
function getAllRecommendedChallengesInit(uuid) {
227+
return uuid;
228+
}
229+
/**
230+
* Get all recommended challenges
231+
* @param {String} uuid progress id
232+
* @param {String} tokenV3 token v3
233+
* @param {*} recommendedTechnology recommended technoloty
234+
*/
235+
function getAllRecommendedChallengesDone(uuid, tokenV3, recommendedTechnology) {
236+
const filter = {
237+
status: 'ACTIVE',
238+
technologies: recommendedTechnology,
239+
};
240+
return getAllActiveChallengesWithUsersDone(uuid, tokenV3, filter);
240241
}
241242

242243
/**
@@ -333,6 +334,9 @@ export default createActions({
333334
GET_ALL_ACTIVE_CHALLENGES_INIT: getAllActiveChallengesInit,
334335
GET_ALL_ACTIVE_CHALLENGES_DONE: getAllActiveChallengesDone,
335336

337+
GET_ALL_RECOMMENDED_CHALLENGES_INIT: getAllRecommendedChallengesInit,
338+
GET_ALL_RECOMMENDED_CHALLENGES_DONE: getAllRecommendedChallengesDone,
339+
336340
GET_ACTIVE_CHALLENGES_INIT: getActiveChallengesInit,
337341
GET_ACTIVE_CHALLENGES_DONE: getActiveChallengesDone,
338342

src/shared/components/TrackIcon/index.jsx

+18-3
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,26 @@ export default function TrackIcon({
1212
tcoEligible,
1313
isDataScience,
1414
MAIN_URL,
15+
challengesUrl,
1516
}) {
1617
const TCO_URL = `${MAIN_URL}/tco`;
1718
return (
1819
<span styleName="trackIcon">
19-
<div styleName={`${(isDataScience ? 'data_science' : track.toLowerCase())} main-icon`}>
20-
{Abbreviation[track][subTrack]}
21-
</div>
20+
{challengesUrl ? (
21+
<a
22+
href={`${challengesUrl}?filter[subtracks][0]=${
23+
encodeURIComponent(subTrack)}`}
24+
styleName={`${(isDataScience ? 'data_science' : track.toLowerCase())} main-icon`}
25+
>
26+
{Abbreviation[track][subTrack]}
27+
</a>
28+
) : (
29+
<div
30+
styleName={`${(isDataScience ? 'data_science' : track.toLowerCase())} main-icon`}
31+
>
32+
{Abbreviation[track][subTrack]}
33+
</div>
34+
)}
2235
<a href={`${TCO_URL}`}>
2336
<div styleName={tcoEligible ? `${(isDataScience ? 'data_science' : track.toLowerCase())} tco-icon` : 'hidden'}>
2437
TCO
@@ -32,6 +45,7 @@ TrackIcon.defaultProps = {
3245
isDataScience: false,
3346
MAIN_URL: config.URL.BASE,
3447
tcoEligible: '',
48+
challengesUrl: '',
3549
};
3650

3751
TrackIcon.propTypes = {
@@ -40,4 +54,5 @@ TrackIcon.propTypes = {
4054
track: PT.string.isRequired,
4155
subTrack: PT.string.isRequired,
4256
MAIN_URL: PT.string,
57+
challengesUrl: PT.string,
4358
};

src/shared/components/TrackIcon/style.scss

+4-1
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ $track-space-20: $base-unit * 4;
77
$track-radius-4: $corner-radius * 2;
88

99
.trackIcon {
10-
display: inline-block;
10+
display: flex;
1111
width: $base-unit * 6;
1212
height: $base-unit * 6;
1313
margin-right: $track-space-20;
14+
flex-direction: column;
1415

1516
@include sm {
1617
margin-right: $track-space-15;
@@ -25,6 +26,8 @@ $track-radius-4: $corner-radius * 2;
2526
padding: $track-code-pad - 1 0 $track-code-pad;
2627
border-radius: $corner-radius;
2728
cursor: pointer;
29+
width: 100%;
30+
height: 100%;
2831

2932
&.design,
3033
&.generic {

src/shared/components/challenge-detail/Header/index.jsx

+52-9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable jsx-a11y/click-events-have-key-events */
12
/**
23
* Challenge header component.
34
* This component renders all other child components part of the header.
@@ -47,6 +48,8 @@ export default function ChallengeHeader(props) {
4748
selectedView,
4849
showDeadlineDetail,
4950
hasFirstPlacement,
51+
hasThriveArticles,
52+
hasRecommendedChallenges,
5053
isMenuOpened,
5154
submissionEnded,
5255
mySubmissions,
@@ -244,15 +247,51 @@ export default function ChallengeHeader(props) {
244247
<h1 styleName="challenge-header">
245248
{name}
246249
</h1>
247-
<ChallengeTags
248-
subTrack={subTrack}
249-
track={trackLower}
250-
challengesUrl={challengesUrl}
251-
challengeSubtracksMap={challengeSubtracksMap}
252-
events={eventNames}
253-
technPlatforms={miscTags}
254-
setChallengeListingFilter={setChallengeListingFilter}
255-
/>
250+
<div styleName="tag-container">
251+
<ChallengeTags
252+
subTrack={subTrack}
253+
track={trackLower}
254+
challengesUrl={challengesUrl}
255+
challengeSubtracksMap={challengeSubtracksMap}
256+
events={eventNames}
257+
technPlatforms={miscTags}
258+
setChallengeListingFilter={setChallengeListingFilter}
259+
/>
260+
{(hasRecommendedChallenges || hasThriveArticles) && (
261+
<div styleName="recommend-container">
262+
{hasRecommendedChallenges && (
263+
<div
264+
styleName="recommend-tag link"
265+
role="button"
266+
tabIndex={0}
267+
onClick={
268+
() => {
269+
document.getElementById('recommendedActiveChallenges').scrollIntoView();
270+
}}
271+
>
272+
Recommended Challenges
273+
</div>
274+
)}
275+
276+
{hasRecommendedChallenges && hasThriveArticles && (
277+
<div styleName="recommend-tag separator" />
278+
)}
279+
280+
{hasThriveArticles && (
281+
<div
282+
styleName="recommend-tag link"
283+
role="button"
284+
tabIndex={0}
285+
onClick={
286+
() => {
287+
document.getElementById('recommendedThriveArticles').scrollIntoView();
288+
}}
289+
>Recommended THRIVE Articles
290+
</div>
291+
)}
292+
</div>
293+
)}
294+
</div>
256295
</div>
257296
</div>
258297
<div styleName="prizes-ops-container">
@@ -420,6 +459,8 @@ Show Deadlines
420459
ChallengeHeader.defaultProps = {
421460
checkpoints: {},
422461
isMenuOpened: false,
462+
hasThriveArticles: false,
463+
hasRecommendedChallenges: false,
423464
};
424465

425466
ChallengeHeader.propTypes = {
@@ -449,6 +490,8 @@ ChallengeHeader.propTypes = {
449490
}).isRequired,
450491
challengesUrl: PT.string.isRequired,
451492
hasRegistered: PT.bool.isRequired,
493+
hasThriveArticles: PT.bool,
494+
hasRecommendedChallenges: PT.bool,
452495
submissionEnded: PT.bool.isRequired,
453496
numWinners: PT.number.isRequired,
454497
onSelectorClicked: PT.func.isRequired,

0 commit comments

Comments
 (0)