Skip to content

Fix bug 1186 #228

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 8 commits into from
May 26, 2017
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
144 changes: 42 additions & 102 deletions components/ChallengeStatus/ChallengeStatus.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import React, { Component, PropTypes } from 'react';
import fetch from 'isomorphic-fetch';
import moment from 'moment';
import _ from 'lodash';
import LeaderboardAvatar from '../LeaderboardAvatar/LeaderboardAvatar';
import ChallengeProgressBar from '../ChallengeProgressBar/ChallengeProgressBar';
import ProgressBarTooltip from '../ChallengeCard/Tooltips/ProgressBarTooltip';
Expand All @@ -15,7 +13,6 @@ import './ChallengeStatus.scss';
// Constants
const ID_LENGTH = 6;
const MAX_VISIBLE_WINNERS = 3;
const MOCK_PHOTO = 'https://acrobatusers.com/assets/images/template/author_generic.jpg';
const STALLED_MSG = 'Stalled';
const DRAFT_MSG = 'In Draft';
const STALLED_TIME_LEFT_MSG = 'Challenge is currently on hold';
Expand Down Expand Up @@ -149,13 +146,15 @@ const getTimeToGo = (start, end) => {
* Returns an user profile object as expected by the UserAvatarTooltip
* @param {String} handle
*/
function getSampleProfile(user) {
const { handle } = user;
function getProfile(user) {
const { handle, placement } = user;
const photoLink = user.photoURL || `i/m/${handle}.jpeg`;
return {
handle,
placement,
country: '',
memberSince: '',
photoLink: `i/m/${handle}.jpeg`,
photoLink,
ratingSummary: [],
};
}
Expand All @@ -173,28 +172,48 @@ class ChallengeStatus extends Component {
DS_CHALLENGE_URL,
FORUM_URL,
};
this.handleHover = this.handleHover.bind(this);
this.getDevelopmentWinners = this.getDevelopmentWinners.bind(this);
this.getDesignWinners = this.getDesignWinners.bind(this);

this.registrantsLink = this.registrantsLink.bind(this);
}

renderLeaderboard() {
const { challenge } = this.props;
const { DS_CHALLENGE_URL, CHALLENGE_URL } = this.state;
const { id, track } = challenge;

const challengeURL = track === 'DATA_SCIENCE' ? DS_CHALLENGE_URL : CHALLENGE_URL;
const leaderboard = this.state.winners && this.state.winners.map(winner => (
<div className="avatar-container" key={winner.handle}>
<UserAvatarTooltip user={getSampleProfile(winner)}>
<LeaderboardAvatar member={winner} />
</UserAvatarTooltip>
</div>
));
let winners = challenge.winners && challenge.winners.filter(winner => winner.type === 'final')
.map(winner => ({
handle: winner.handle,
position: winner.placement,
photoURL: winner.photoURL || `${this.props.MAIN_URL}/i/m/${winner.handle}.jpeg`,
}));

if (winners && winners.length > MAX_VISIBLE_WINNERS) {
const lastItem = {
handle: `+${winners.length - MAX_VISIBLE_WINNERS}`,
isLastItem: true,
};
winners = winners.slice(0, MAX_VISIBLE_WINNERS);
winners.push(lastItem);
}

const leaderboard = winners && winners.map((winner) => {
if (winner.isLastItem) {
return <LeaderboardAvatar key={winner.handle} member={winner} url={`${this.props.detailLink}#winner`} />;
}
const userProfile = getProfile(winner);
return (
<div className="avatar-container" key={winner.handle}>
<UserAvatarTooltip user={userProfile}>
<LeaderboardAvatar member={winner} />
</UserAvatarTooltip>
</div>);
});
return leaderboard || (
<span className="winners" onMouseEnter={this.handleHover}>
<a href={`${challengeURL}${id}`}>Winners</a>
</span>);
<span className="winners">
<a href={`${challengeURL}${id}#winner`}>Results</a>
</span>);
}

renderRegisterButton() {
Expand Down Expand Up @@ -334,16 +353,15 @@ class ChallengeStatus extends Component {
<div>
{this.renderLeaderboard()}
<span className="challenge-stats">
<span>
<span className="num-reg">
<Tooltip content={numRegistrantsTipText(challenge.numRegistrants)}>
<a className="num-reg past" href={this.registrantsLink(challenge, MM_REG)}>
<RegistrantsIcon /> <span className="number">{challenge.numRegistrants}</span>
</a>
</Tooltip>
</span>
<span>
<span className="num-sub">
<Tooltip content={numSubmissionsTipText(challenge.numSubmissions)}>

<a className="num-sub past" href={this.registrantsLink(challenge, MM_SUB)}>
<SubmissionsIcon /> <span className="number">{challenge.numSubmissions}</span>
</a>
Expand All @@ -360,86 +378,6 @@ class ChallengeStatus extends Component {
);
}

getDevelopmentWinners(challengeId) {
return new Promise((resolve, reject) => {
fetch(`${this.props.config.API_URL_V2}/develop/challenges/${challengeId}`)
.then(res => res.json())
.then((data) => {
let winners = data.submissions.filter(sub => sub.placement)
.map(winner => ({
handle: winner.handle,
position: winner.placement,
photoURL: MOCK_PHOTO,
}));
winners = _.uniqWith(winners, _.isEqual);
if (winners.length > MAX_VISIBLE_WINNERS) {
const lastItem = {
handle: `+${winners.length - MAX_VISIBLE_WINNERS}`,
};
winners = winners.slice(0, MAX_VISIBLE_WINNERS);
winners.push(lastItem);
}
resolve(winners);
})
.catch(err => reject(err));
});
}

getDesignWinners(challengeId) {
return new Promise((resolve, reject) => {
fetch(`${this.props.config.API_URL_V2}/design/challenges/result/${challengeId}`)
.then(res => res.json())
.then((data) => {
let winners = data.results.filter(sub => sub.placement)
.map(winner => ({
handle: winner.handle,
position: winner.placement,
photoURL: MOCK_PHOTO,
}));
winners = _.uniqWith(winners, _.isEqual);
if (winners.length > MAX_VISIBLE_WINNERS) {
const lastItem = {
handle: `+${winners.length - MAX_VISIBLE_WINNERS}`,
};
winners = winners.slice(0, MAX_VISIBLE_WINNERS);
winners.push(lastItem);
}
resolve(winners);
})
.catch(err => reject(err));
});
}


getWinners(challengeType, challengeId) {
switch (challengeType) {
case 'develop':
return this.getDevelopmentWinners(challengeId);
case 'design':
return this.getDesignWinners(challengeId);
default:
return this.getDevelopmentWinners(challengeId);
}
}

/**
* Get the list of winners when the user hovers
* over the status
*/
handleHover() {
if (!this.state.winners) {
const { challenge } = this.props;
const { id, track } = challenge;

// We don't have the API for data science challenge
if (track === 'DATA_SCIENCE') {
return;
}
const results = this.getWinners(track.toLowerCase(), id);
results.then(winners => this.setState({ winners }));
}
}

render() {
const { challenge } = this.props;
const status = challenge.status === 'COMPLETED' ? 'completed' : '';
Expand All @@ -456,12 +394,14 @@ ChallengeStatus.defaultProps = {
config: {},
detailLink: '',
sampleWinnerProfile: undefined,
MAIN_URL: process.env.MAIN_URL,
};

ChallengeStatus.propTypes = {
challenge: PropTypes.object,
config: PropTypes.object,
detailLink: PropTypes.string,
MAIN_URL: PropTypes.string,
};

export default ChallengeStatus;
42 changes: 32 additions & 10 deletions components/LeaderboardAvatar/LeaderboardAvatar.jsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,50 @@
import React from 'react'
import React, { Component } from 'react'
import './LeaderboardAvatar.scss'

// Constants
const VISIBLE_CHARACTERS = 3
const MOCK_PHOTO = 'https://acrobatusers.com/assets/images/template/author_generic.jpg'

function LeaderboardAvatar ({member, domain}) {
return (
<a href={`//${domain}/members/${member.handle}`} className={`leaderboard-avatar ${member.position || member.isSmr ? 'dark-gray' : 'light-gray'}`}>
{member.photoURL ? <img src={member.photoURL} className="member-icon"/> : member.handle.slice(0, VISIBLE_CHARACTERS)}
<span className={member.position ? `placement placement-${member.position}` : 'hidden'}>
{member.position}
</span>
</a>
)
class LeaderboardAvatar extends Component {
constructor(props) {
super(props)
this.state = {
member: props.member,
}
this.handleError = this.handleError.bind(this)
}

handleError() {
const { member } = this.state
member.photoURL = MOCK_PHOTO
this.setState({ member })
}

render() {
const { domain, url } = this.props
const { member } = this.state
const targetURL = url ? url : `//${domain}/members/${member.handle}`
return (
<a href={targetURL} className={`leaderboard-avatar ${member.position || member.isSmr ? 'dark-gray' : 'light-gray'}`}>
{member.photoURL ? <img src={member.photoURL} className="member-icon" onError={this.handleError} /> : member.handle.slice(0, VISIBLE_CHARACTERS)}
<span className={member.position ? `placement placement-${member.position}` : 'hidden'}>
{member.position}
</span>
</a>
)
}
}

LeaderboardAvatar.propTypes = {
member: React.PropTypes.object,
domain: React.PropTypes.string,
url: React.PropTypes.string,
}

LeaderboardAvatar.defaultProps = {
member: {},
domain: process.env.domain,
url: ''
}

export default LeaderboardAvatar