Skip to content

feat: recommended challenge and thrive articles #4011

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 15 commits into from
Feb 19, 2020
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
5 changes: 1 addition & 4 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -174,23 +174,20 @@ workflows:
filters:
branches:
only:
- develop
- hot-fix-past-challenge-search
- develop
# This is alternate dev env for parallel testing
- "build-test":
context : org-global
filters:
branches:
only:
- develop
- hot-fixes-leaderboard
# This is beta env for production soft releases
- "build-prod-beta":
context : org-global
filters:
branches:
only:
- develop
- feature-contentful
# Production builds are exectuted only on tagged commits to the
# master branch.
Expand Down
5 changes: 5 additions & 0 deletions config/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ module.exports = {
*/
CHALLENGE_DETAILS_REFRESH_DELAY: 3000,

/* Max number of recommended challenges */
CHALLENGE_DETAILS_MAX_NUMBER_RECOMMENDED_CHALLENGES: 3,

COOKIES: {
/* Expiration time [days] for browser cookies set by the App. */
MAXAGE: 7,
Expand Down Expand Up @@ -115,6 +118,8 @@ module.exports = {
FORUMS: 'https://apps.topcoder-dev.com/forums',
HELP: 'https://www.topcoder.com/thrive/tracks?track=Topcoder',

THRIVE: 'https://www.topcoder.com/thrive',

COMMUNITIES: {
BLOCKCHAIN: 'https://blockchain.topcoder-dev.com',
COGNITIVE: 'https://cognitive.topcoder-dev.com',
Expand Down
36 changes: 36 additions & 0 deletions src/assets/images/calendar.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
92 changes: 48 additions & 44 deletions src/shared/actions/challenge-listing/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,26 +80,25 @@ function getActiveChallengesInit(uuid, page, frontFilter) {
return { uuid, page, frontFilter };
}

/** TODO: Inspect if the 2 actions bellow can be removed?
* They do duplicate what is done in `getActiveChallengesDone` but fetch all challenges
* which was refactored in listing-improve
/**
* Get all challenges and match with user challenges
* @param {String} uuid progress id
* @param {String} tokenV3 token v3
* @param {Object} filter filter object
* @param {number} page start page
*/
function getAllActiveChallengesInit(uuid) {
return uuid;
}
function getAllActiveChallengesDone(uuid, tokenV3) {
const filter = { status: 'ACTIVE' };
function getAllActiveChallengesWithUsersDone(uuid, tokenV3, filter, page = 0) {
const service = getService(tokenV3);
const calls = [
getAll(params => service.getChallenges(filter, params)),
getAll(params => service.getChallenges(filter, params), page),
];
let user;
if (tokenV3) {
user = decodeToken(tokenV3).handle;
// Handle any errors on this endpoint so that the non-user specific challenges
// will still be loaded.
calls.push(getAll(params => service.getUserChallenges(user, filter, params)
.catch(() => ({ challenges: [] }))));
.catch(() => ({ challenges: [] }))), page);
}
return Promise.all(calls).then(([ch, uch]) => {
/* uch array contains challenges where the user is participating in
Expand All @@ -120,10 +119,22 @@ function getAllActiveChallengesDone(uuid, tokenV3) {
});
}

return { uuid, challenges: ch };
return { uuid, challenges: ch, ...filter };
});
}

/** TODO: Inspect if the 2 actions bellow can be removed?
* They do duplicate what is done in `getActiveChallengesDone` but fetch all challenges
* which was refactored in listing-improve
*/
function getAllActiveChallengesInit(uuid) {
return uuid;
}
function getAllActiveChallengesDone(uuid, tokenV3) {
const filter = { status: 'ACTIVE' };
return getAllActiveChallengesWithUsersDone(uuid, tokenV3, filter);
}

/**
* Gets 1 page of active challenges (including marathon matches) from the backend.
* Once this action is completed any active challenges saved to the state before
Expand Down Expand Up @@ -200,43 +211,33 @@ function getRestActiveChallengesInit(uuid) {

/**
* Loading all challenges
* @param {*} uuid
* @param {*} tokenV3
* @param {String} uuid progress id
* @param {String} tokenV3 token v3
*/
function getRestActiveChallengesDone(uuid, tokenV3) {
const filter = { status: 'ACTIVE' };
const service = getService(tokenV3);
const calls = [
getAll(params => service.getChallenges(filter, params), 1),
];
let user;
if (tokenV3) {
user = decodeToken(tokenV3).handle;
calls.push(getAll(params => service.getUserChallenges(user, filter, params)
.catch(() => ({ challenges: [] }))), 1);
}
return Promise.all(calls).then(([ch, uch]) => {
/* uch array contains challenges where the user is participating in
* some role. The same challenge are already listed in res array, but they
* are not attributed to the user there. This block of code marks user
* challenges in an efficient way. */
if (uch) {
const map = {};
uch.forEach((item) => { map[item.id] = item; });
ch.forEach((item) => {
if (map[item.id]) {
/* It is fine to reassing, as the array we modifying is created just
* above within the same function. */
/* eslint-disable no-param-reassign */
item.users[user] = true;
item.userDetails = map[item.id].userDetails;
/* eslint-enable no-param-reassign */
}
});
}
return getAllActiveChallengesWithUsersDone(uuid, tokenV3, filter, 1);
}

return { uuid, challenges: ch };
});
/**
* Prepare for getting all recommended challenges
* @param {String} uuid progress id
*/
function getAllRecommendedChallengesInit(uuid) {
return uuid;
}
/**
* Get all recommended challenges
* @param {String} uuid progress id
* @param {String} tokenV3 token v3
* @param {*} recommendedTechnology recommended technoloty
*/
function getAllRecommendedChallengesDone(uuid, tokenV3, recommendedTechnology) {
const filter = {
status: 'ACTIVE',
technologies: recommendedTechnology,
};
return getAllActiveChallengesWithUsersDone(uuid, tokenV3, filter);
}

/**
Expand Down Expand Up @@ -333,6 +334,9 @@ export default createActions({
GET_ALL_ACTIVE_CHALLENGES_INIT: getAllActiveChallengesInit,
GET_ALL_ACTIVE_CHALLENGES_DONE: getAllActiveChallengesDone,

GET_ALL_RECOMMENDED_CHALLENGES_INIT: getAllRecommendedChallengesInit,
GET_ALL_RECOMMENDED_CHALLENGES_DONE: getAllRecommendedChallengesDone,

GET_ACTIVE_CHALLENGES_INIT: getActiveChallengesInit,
GET_ACTIVE_CHALLENGES_DONE: getActiveChallengesDone,

Expand Down
21 changes: 18 additions & 3 deletions src/shared/components/TrackIcon/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,26 @@ export default function TrackIcon({
tcoEligible,
isDataScience,
MAIN_URL,
challengesUrl,
}) {
const TCO_URL = `${MAIN_URL}/tco`;
return (
<span styleName="trackIcon">
<div styleName={`${(isDataScience ? 'data_science' : track.toLowerCase())} main-icon`}>
{Abbreviation[track][subTrack]}
</div>
{challengesUrl ? (
<a
href={`${challengesUrl}?filter[subtracks][0]=${
encodeURIComponent(subTrack)}`}
styleName={`${(isDataScience ? 'data_science' : track.toLowerCase())} main-icon`}
>
{Abbreviation[track][subTrack]}
</a>
) : (
<div
styleName={`${(isDataScience ? 'data_science' : track.toLowerCase())} main-icon`}
>
{Abbreviation[track][subTrack]}
</div>
)}
<a href={`${TCO_URL}`}>
<div styleName={tcoEligible ? `${(isDataScience ? 'data_science' : track.toLowerCase())} tco-icon` : 'hidden'}>
TCO
Expand All @@ -32,6 +45,7 @@ TrackIcon.defaultProps = {
isDataScience: false,
MAIN_URL: config.URL.BASE,
tcoEligible: '',
challengesUrl: '',
};

TrackIcon.propTypes = {
Expand All @@ -40,4 +54,5 @@ TrackIcon.propTypes = {
track: PT.string.isRequired,
subTrack: PT.string.isRequired,
MAIN_URL: PT.string,
challengesUrl: PT.string,
};
5 changes: 4 additions & 1 deletion src/shared/components/TrackIcon/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ $track-space-20: $base-unit * 4;
$track-radius-4: $corner-radius * 2;

.trackIcon {
display: inline-block;
display: flex;
width: $base-unit * 6;
height: $base-unit * 6;
margin-right: $track-space-20;
flex-direction: column;

@include sm {
margin-right: $track-space-15;
Expand All @@ -25,6 +26,8 @@ $track-radius-4: $corner-radius * 2;
padding: $track-code-pad - 1 0 $track-code-pad;
border-radius: $corner-radius;
cursor: pointer;
width: 100%;
height: 100%;

&.design,
&.generic {
Expand Down
61 changes: 52 additions & 9 deletions src/shared/components/challenge-detail/Header/index.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable jsx-a11y/click-events-have-key-events */
/**
* Challenge header component.
* This component renders all other child components part of the header.
Expand Down Expand Up @@ -47,6 +48,8 @@ export default function ChallengeHeader(props) {
selectedView,
showDeadlineDetail,
hasFirstPlacement,
hasThriveArticles,
hasRecommendedChallenges,
isMenuOpened,
submissionEnded,
mySubmissions,
Expand Down Expand Up @@ -244,15 +247,51 @@ export default function ChallengeHeader(props) {
<h1 styleName="challenge-header">
{name}
</h1>
<ChallengeTags
subTrack={subTrack}
track={trackLower}
challengesUrl={challengesUrl}
challengeSubtracksMap={challengeSubtracksMap}
events={eventNames}
technPlatforms={miscTags}
setChallengeListingFilter={setChallengeListingFilter}
/>
<div styleName="tag-container">
<ChallengeTags
subTrack={subTrack}
track={trackLower}
challengesUrl={challengesUrl}
challengeSubtracksMap={challengeSubtracksMap}
events={eventNames}
technPlatforms={miscTags}
setChallengeListingFilter={setChallengeListingFilter}
/>
{(hasRecommendedChallenges || hasThriveArticles) && (
<div styleName="recommend-container">
{hasRecommendedChallenges && (
<div
styleName="recommend-tag link"
role="button"
tabIndex={0}
onClick={
() => {
document.getElementById('recommendedActiveChallenges').scrollIntoView();
}}
>
Recommended Challenges
</div>
)}

{hasRecommendedChallenges && hasThriveArticles && (
<div styleName="recommend-tag separator" />
)}

{hasThriveArticles && (
<div
styleName="recommend-tag link"
role="button"
tabIndex={0}
onClick={
() => {
document.getElementById('recommendedThriveArticles').scrollIntoView();
}}
>Recommended THRIVE Articles
</div>
)}
</div>
)}
</div>
</div>
</div>
<div styleName="prizes-ops-container">
Expand Down Expand Up @@ -420,6 +459,8 @@ Show Deadlines
ChallengeHeader.defaultProps = {
checkpoints: {},
isMenuOpened: false,
hasThriveArticles: false,
hasRecommendedChallenges: false,
};

ChallengeHeader.propTypes = {
Expand Down Expand Up @@ -449,6 +490,8 @@ ChallengeHeader.propTypes = {
}).isRequired,
challengesUrl: PT.string.isRequired,
hasRegistered: PT.bool.isRequired,
hasThriveArticles: PT.bool,
hasRecommendedChallenges: PT.bool,
submissionEnded: PT.bool.isRequired,
numWinners: PT.number.isRequired,
onSelectorClicked: PT.func.isRequired,
Expand Down
Loading