Merge pull request #10173 from BerkeleyTrue/fix/challenge-completed

Fix(challenges): Update user data on challenge complete
This commit is contained in:
Mrugesh Mohapatra
2016-08-12 11:41:23 +05:30
committed by GitHub
8 changed files with 112 additions and 66 deletions

View File

@ -94,12 +94,18 @@ export const updateUserLang = createAction(
types.updateUserLang, types.updateUserLang,
(username, lang) => ({ username, lang }) (username, lang) => ({ username, lang })
); );
export const updateAppLang = createAction(types.updateAppLang);
// updateCompletedChallenges(username: String) => Action // updateUserChallenge(
export const updateCompletedChallenges = createAction( // username: String,
types.updateCompletedChallenges // challengeInfo: Object
// ) => Action
export const updateUserChallenge = createAction(
types.updateUserChallenge,
(username, challengeInfo) => ({ username, challengeInfo })
); );
export const updateAppLang = createAction(types.updateAppLang);
// used when server needs client to redirect // used when server needs client to redirect
export const delayedRedirect = createAction(types.delayedRedirect); export const delayedRedirect = createAction(types.delayedRedirect);

View File

@ -1,6 +1,6 @@
import types from './types'; import types from './types';
const { updateUserPoints, updateCompletedChallenges } = types; const { updateUserPoints } = types;
const initialState = { const initialState = {
superBlock: {}, superBlock: {},
block: {}, block: {},
@ -15,22 +15,6 @@ export default function entities(state = initialState, action) {
type, type,
payload: { email, username, points, flag, languageTag } = {} payload: { email, username, points, flag, languageTag } = {}
} = action; } = action;
if (type === updateCompletedChallenges) {
const username = action.payload;
const completedChallengeMap = state.user[username].challengeMap || {};
return {
...state,
challenge: Object.keys(state.challenge)
.reduce((map, key) => {
const challenge = state.challenge[key];
map[key] = {
...challenge,
isCompleted: !!completedChallengeMap[challenge.id]
};
return map;
}, {})
};
}
if (action.meta && action.meta.entities) { if (action.meta && action.meta.entities) {
return { return {
...state, ...state,
@ -98,5 +82,22 @@ export default function entities(state = initialState, action) {
} }
}; };
} }
if (action.type === types.updateUserChallenge) {
const { challengeInfo } = action.payload;
return {
...state,
user: {
...state.user,
[username]: {
...state.user[username],
challengeMap: {
...state.user[username].challengeMap,
[challengeInfo.id]: challengeInfo
}
}
}
};
}
return state; return state;
} }

View File

@ -3,7 +3,6 @@ import types from './types';
import { import {
addUser, addUser,
updateThisUser, updateThisUser,
updateCompletedChallenges,
createErrorObservable, createErrorObservable,
showSignIn, showSignIn,
updateTheme, updateTheme,
@ -24,7 +23,6 @@ export default function getUserSaga(action$, getState, { services }) {
const isNightMode = user.theme === 'night'; const isNightMode = user.theme === 'night';
return Observable.of( return Observable.of(
addUser(entities), addUser(entities),
updateCompletedChallenges(result),
updateThisUser(result), updateThisUser(result),
isNightMode ? updateTheme(user.theme) : null, isNightMode ? updateTheme(user.theme) : null,
isNightMode ? addThemeToBody(user.theme) : null isNightMode ? addThemeToBody(user.theme) : null

View File

@ -12,7 +12,7 @@ export default createTypes([
'updateUserFlag', 'updateUserFlag',
'updateUserEmail', 'updateUserEmail',
'updateUserLang', 'updateUserLang',
'updateCompletedChallenges', 'updateUserChallenge',
'showSignIn', 'showSignIn',
'loadCurrentChallenge', 'loadCurrentChallenge',
'updateMyCurrentChallenge', 'updateMyCurrentChallenge',

View File

@ -8,24 +8,35 @@ import debug from 'debug';
import { updateCurrentChallenge } from '../../redux/actions'; import { updateCurrentChallenge } from '../../redux/actions';
import { makePanelHiddenSelector } from '../../redux/selectors'; import { makePanelHiddenSelector } from '../../redux/selectors';
import { userSelector } from '../../../../redux/selectors';
import { closeMapDrawer } from '../../../../redux/actions'; import { closeMapDrawer } from '../../../../redux/actions';
const bindableActions = { closeMapDrawer, updateCurrentChallenge }; const bindableActions = { closeMapDrawer, updateCurrentChallenge };
const makeMapStateToProps = () => createSelector( const makeMapStateToProps = () => createSelector(
userSelector,
(_, props) => props.dashedName, (_, props) => props.dashedName,
state => state.entities.challenge, state => state.entities.challenge,
makePanelHiddenSelector(), makePanelHiddenSelector(),
(dashedName, challengeMap, isHidden) => { (
{ user: { challengeMap: userChallengeMap } },
dashedName,
challengeMap,
isHidden
) => {
const challenge = challengeMap[dashedName] || {}; const challenge = challengeMap[dashedName] || {};
let isCompleted = false;
if (userChallengeMap) {
isCompleted = !!userChallengeMap[challenge.id];
}
return { return {
dashedName, dashedName,
challenge, challenge,
isHidden, isHidden,
isCompleted,
title: challenge.title, title: challenge.title,
block: challenge.block, block: challenge.block,
isLocked: challenge.isLocked, isLocked: challenge.isLocked,
isRequired: challenge.isRequired, isRequired: challenge.isRequired,
isCompleted: challenge.isCompleted,
isComingSoon: challenge.isComingSoon, isComingSoon: challenge.isComingSoon,
isDev: debug.enabled('fcc:*') isDev: debug.enabled('fcc:*')
}; };

View File

@ -59,7 +59,10 @@ export const updateFile = createAction(
export const updateFiles = createAction(types.updateFiles); export const updateFiles = createAction(types.updateFiles);
// rechallenge // rechallenge
export const executeChallenge = createAction(types.executeChallenge); export const executeChallenge = createAction(
types.executeChallenge,
() => null
);
export const updateMain = createAction(types.updateMain); export const updateMain = createAction(types.updateMain);
export const frameMain = createAction(types.frameMain); export const frameMain = createAction(types.frameMain);

View File

@ -2,22 +2,30 @@ import { Observable } from 'rx';
import types from './types'; import types from './types';
import { moveToNextChallenge } from './actions'; import { moveToNextChallenge } from './actions';
import {
createErrorObservable,
updateUserPoints
} from '../../../redux/actions';
import { makeToast } from '../../../toasts/redux/actions';
import { challengeSelector } from './selectors'; import { challengeSelector } from './selectors';
import { randomCompliment } from '../../../utils/get-words'; import { randomCompliment } from '../../../utils/get-words';
import {
createErrorObservable,
updateUserPoints,
updateUserChallenge
} from '../../../redux/actions';
import { backEndProject } from '../../../utils/challengeTypes'; import { backEndProject } from '../../../utils/challengeTypes';
import { makeToast } from '../../../toasts/redux/actions';
import { postJSON$ } from '../../../../utils/ajax-stream'; import { postJSON$ } from '../../../../utils/ajax-stream';
function postChallenge(url, body, username) { function postChallenge(url, username, _csrf, challengeInfo) {
const body = { ...challengeInfo, _csrf };
const saveChallenge$ = postJSON$(url, body) const saveChallenge$ = postJSON$(url, body)
.retry(3) .retry(3)
.map(({ points }) => { .flatMap(({ points, lastUpdated, completedDate }) => {
return updateUserPoints(username, points); return Observable.of(
updateUserPoints(username, points),
updateUserChallenge(
username,
{ ...challengeInfo, lastUpdated, completedDate }
)
);
}) })
.catch(createErrorObservable); .catch(createErrorObservable);
const challengeCompleted$ = Observable.of(moveToNextChallenge()); const challengeCompleted$ = Observable.of(moveToNextChallenge());
@ -44,12 +52,13 @@ function submitModern(type, state) {
app: { user, csrfToken }, app: { user, csrfToken },
challengesApp: { files } challengesApp: { files }
} = state; } = state;
const body = { const challengeInfo = { id, files };
id, return postChallenge(
_csrf: csrfToken, '/modern-challenge-completed',
files user,
}; csrfToken,
return postChallenge('/modern-challenge-completed', body, user); challengeInfo
);
} }
} }
return Observable.just(makeToast({ message: 'Not quite there, yet.' })); return Observable.just(makeToast({ message: 'Not quite there, yet.' }));
@ -62,16 +71,16 @@ function submitProject(type, state, { solution, githubLink }) {
const { const {
app: { user, csrfToken } app: { user, csrfToken }
} = state; } = state;
const body = { const challengeInfo = { id, challengeType, solution };
id,
challengeType,
solution,
_csrf: csrfToken
};
if (challengeType === backEndProject) { if (challengeType === backEndProject) {
body.githubLink = githubLink; challengeInfo.githubLink = githubLink;
} }
return postChallenge('/project-completed', body, user); return postChallenge(
'/project-completed',
user,
csrfToken,
challengeInfo
);
} }
function submitSimpleChallenge(type, state) { function submitSimpleChallenge(type, state) {
@ -81,11 +90,13 @@ function submitSimpleChallenge(type, state) {
const { const {
app: { user, csrfToken } app: { user, csrfToken }
} = state; } = state;
const body = { const challengeInfo = { id };
id, return postChallenge(
_csrf: csrfToken '/challenge-completed',
}; user,
return postChallenge('/challenge-completed', body, user); csrfToken,
challengeInfo
);
} }
const submitTypes = { const submitTypes = {

View File

@ -61,7 +61,12 @@ function buildUserUpdate(
log('user update data', updateData); log('user update data', updateData);
return { alreadyCompleted, updateData }; return {
alreadyCompleted,
updateData,
completedDate: finalChallenge.completedDate,
lastUpdated: finalChallenge.lastUpdated
};
} }
export default function(app) { export default function(app) {
@ -138,14 +143,14 @@ export default function(app) {
files files
} = req.body; } = req.body;
const { alreadyCompleted, updateData } = buildUserUpdate( const {
alreadyCompleted,
updateData,
lastUpdated
} = buildUserUpdate(
user, user,
id, id,
{ { id, files, completedDate }
id,
files,
completedDate
}
); );
const points = alreadyCompleted ? user.points : user.points + 1; const points = alreadyCompleted ? user.points : user.points + 1;
@ -156,7 +161,9 @@ export default function(app) {
if (type === 'json') { if (type === 'json') {
return res.json({ return res.json({
points, points,
alreadyCompleted alreadyCompleted,
completedDate,
lastUpdated
}); });
} }
return res.sendStatus(200); return res.sendStatus(200);
@ -184,7 +191,11 @@ export default function(app) {
const completedDate = Date.now(); const completedDate = Date.now();
const { id, solution, timezone } = req.body; const { id, solution, timezone } = req.body;
const { alreadyCompleted, updateData } = buildUserUpdate( const {
alreadyCompleted,
updateData,
lastUpdated
} = buildUserUpdate(
req.user, req.user,
id, id,
{ id, solution, completedDate }, { id, solution, completedDate },
@ -200,7 +211,9 @@ export default function(app) {
if (type === 'json') { if (type === 'json') {
return res.json({ return res.json({
points, points,
alreadyCompleted alreadyCompleted,
completedDate,
lastUpdated
}); });
} }
return res.sendStatus(200); return res.sendStatus(200);
@ -253,7 +266,8 @@ export default function(app) {
.flatMap(() => { .flatMap(() => {
const { const {
alreadyCompleted, alreadyCompleted,
updateData updateData,
lastUpdated
} = buildUserUpdate(user, completedChallenge.id, completedChallenge); } = buildUserUpdate(user, completedChallenge.id, completedChallenge);
return user.update$(updateData) return user.update$(updateData)
@ -262,7 +276,9 @@ export default function(app) {
if (type === 'json') { if (type === 'json') {
return res.send({ return res.send({
alreadyCompleted, alreadyCompleted,
points: alreadyCompleted ? user.points : user.points + 1 points: alreadyCompleted ? user.points : user.points + 1,
completedDate: completedChallenge.completedDate,
lastUpdated
}); });
} }
return res.status(200).send(true); return res.status(200).send(true);