Skip to content

Commit

Permalink
Merge pull request #10041 from BerkeleyTrue/regression/add-current-ch…
Browse files Browse the repository at this point in the history
…allenge

Fix(accounts): show challenge info on user profile
  • Loading branch information
raisedadead committed Aug 2, 2016
2 parents 2ea1c5f + 606bfd7 commit 0a90d0f
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 179 deletions.
13 changes: 1 addition & 12 deletions common/app/routes/challenges/redux/fetch-challenges-saga.js
Expand Up @@ -12,21 +12,10 @@ import {
delayedRedirect,
createErrorObservable
} from '../../../redux/actions';
import createNameIdMap from '../../../../utils/create-name-id-map';

const { fetchChallenge, fetchChallenges, replaceChallenge } = types;

function createNameIdMap(entities) {
const { challenge } = entities;
return {
...entities,
challengeIdToName: Object.keys(challenge)
.reduce((map, challengeName) => {
map[challenge[challengeName].id] = challenge[challengeName].dashedName;
return map;
}, {})
};
}

export default function fetchChallengesSaga(action$, getState, { services }) {
return action$
.filter(({ type }) => (
Expand Down
12 changes: 12 additions & 0 deletions common/utils/create-name-id-map.js
@@ -0,0 +1,12 @@
// createNameIdMap(entities: Object) => Object
export default function createNameIdMap(entities) {
const { challenge } = entities;
return {
...entities,
challengeIdToName: Object.keys(challenge)
.reduce((map, challengeName) => {
map[challenge[challengeName].id] = challenge[challengeName].dashedName;
return map;
}, {})
};
}
49 changes: 33 additions & 16 deletions server/boot/user.js
Expand Up @@ -3,15 +3,12 @@ import moment from 'moment-timezone';
import { Observable } from 'rx';
import debugFactory from 'debug';

import supportedLanguages from '../../common/utils/supported-languages';
import {
frontEndChallengeId,
dataVisChallengeId,
backEndChallengeId
} from '../utils/constantStrings.json';

import certTypes from '../utils/certTypes.json';

import {
ifNoUser401,
ifNoUserRedirectTo
Expand All @@ -22,6 +19,9 @@ import {
calcCurrentStreak,
calcLongestStreak
} from '../utils/user-stats';
import supportedLanguages from '../../common/utils/supported-languages';
import createNameIdMap from '../../common/utils/create-name-id-map';
import { cachedMap } from '../utils/map';

const debug = debugFactory('fcc:boot:user');
const sendNonUserToMap = ifNoUserRedirectTo('/map');
Expand Down Expand Up @@ -85,25 +85,35 @@ function getChallengeGroup(challenge) {
return 'challenges';
}

// buildDisplayChallenges(challengeMap: Object, tz: String) => Observable[{
// buildDisplayChallenges(
// entities: { challenge: Object, challengeIdToName: Object },
// challengeMap: Object,
// tz: String
// ) => Observable[{
// algorithms: Array,
// projects: Array,
// challenges: Array
// }]
function buildDisplayChallenges(challengeMap = {}, timezone) {
return Observable.from(Object.keys(challengeMap))
.map(challengeId => challengeMap[challengeId])
.map(challenge => {
let finalChallenge = { ...challenge };
if (challenge.completedDate) {
function buildDisplayChallenges(
{ challenge: challengeMap = {}, challengeIdToName },
userChallengeMap = {},
timezone
) {
return Observable.from(Object.keys(userChallengeMap))
.map(challengeId => userChallengeMap[challengeId])
.map(userChallenge => {
const challengeId = userChallenge.id;
const challenge = challengeMap[ challengeIdToName[challengeId] ];
let finalChallenge = { ...userChallenge, ...challenge };
if (userChallenge.completedDate) {
finalChallenge.completedDate = moment
.tz(challenge.completedDate, timezone)
.tz(userChallenge.completedDate, timezone)
.format(dateFormat);
}

if (challenge.lastUpdated) {
if (userChallenge.lastUpdated) {
finalChallenge.lastUpdated = moment
.tz(challenge.lastUpdated, timezone)
.tz(userChallenge.lastUpdated, timezone)
.format(dateFormat);
}

Expand All @@ -128,6 +138,8 @@ module.exports = function(app) {
const router = app.loopback.Router();
const api = app.loopback.Router();
const User = app.models.User;
const Block = app.models.Block;
const map$ = cachedMap(Block);
function findUserByUsername$(username, fields) {
return observeQuery(
User,
Expand Down Expand Up @@ -187,7 +199,7 @@ module.exports = function(app) {
(req, res) => res.redirect(req.url.replace('full-stack', 'back-end'))
);

router.get('/:username', returnUser);
router.get('/:username', showUserProfile);

app.use('/:lang', router);
app.use(api);
Expand Down Expand Up @@ -248,7 +260,7 @@ module.exports = function(app) {
return res.redirect('/' + username);
}

function returnUser(req, res, next) {
function showUserProfile(req, res, next) {
const username = req.params.username.toLowerCase();
const { user } = req;

Expand Down Expand Up @@ -313,7 +325,12 @@ module.exports = function(app) {
});
}

return buildDisplayChallenges(userPortfolio.challengeMap, timezone)
return map$.map(({ entities }) => createNameIdMap(entities))
.flatMap(entities => buildDisplayChallenges(
entities,
userPortfolio.challengeMap,
timezone
))
.map(displayChallenges => ({
...userPortfolio,
...displayChallenges,
Expand Down
128 changes: 3 additions & 125 deletions server/services/map.js
@@ -1,134 +1,12 @@
import _ from 'lodash';
import { Observable } from 'rx';
import { Schema, valuesOf, arrayOf, normalize } from 'normalizr';
import debug from 'debug';
import { nameify, unDasherize } from '../utils';
import supportedLanguages from '../../common/utils/supported-languages';
import { unDasherize } from '../utils';
import { mapChallengeToLang, cachedMap, getMapForLang } from '../utils/map';

const isDev = process.env.NODE_ENV !== 'production';
const isBeta = !!process.env.BETA;
const challengesRegex = /^(bonfire|waypoint|zipline|basejump|checkpoint)/i;
const log = debug('fcc:services:challenges');
const challenge = new Schema('challenge', { idAttribute: 'dashedName' });
const block = new Schema('block', { idAttribute: 'dashedName' });
const superBlock = new Schema('superBlock', { idAttribute: 'dashedName' });

block.define({
challenges: arrayOf(challenge)
});

superBlock.define({
blocks: arrayOf(block)
});

const mapSchema = valuesOf(superBlock);

/*
* interface ChallengeMap {
* result: [superBlockDashedName: String]
* entities: {
* superBlock: {
* [superBlockDashedName: String]: {
* blocks: [blockDashedName: String]
* }
* },
* block: {
* [blockDashedName: String]: {
* challenges: [challengeDashedName: String]
* }
* },
* challenge: {
* [challengeDashedName: String]: Challenge
* }
* }
* }
*/
function cachedMap(Block) {
const query = {
include: 'challenges',
order: ['superOrder ASC', 'order ASC']
};
return Block.find$(query)
.flatMap(blocks => Observable.from(blocks.map(block => block.toJSON())))
.reduce((map, block) => {
if (map[block.superBlock]) {
map[block.superBlock].blocks.push(block);
} else {
map[block.superBlock] = {
title: _.startCase(block.superBlock),
order: block.superOrder,
name: nameify(_.startCase(block.superBlock)),
dashedName: block.superBlock,
blocks: [block],
message: block.superBlockMessage
};
}
return map;
}, {})
.map(map => normalize(map, mapSchema))
.map(map => {
// make sure challenges are in the right order
map.entities.block = Object.keys(map.entities.block)
// turn map into array
.map(key => map.entities.block[key])
// perform re-order
.map(block => {
block.challenges = block.challenges.reduce((accu, dashedName) => {
const index = map.entities.challenge[dashedName].suborder;
accu[index - 1] = dashedName;
return accu;
}, []);
return block;
})
// turn back into map
.reduce((blockMap, block) => {
blockMap[block.dashedName] = block;
return blockMap;
}, {});
return map;
})
.map(map => {
// re-order superBlocks result
const result = Object.keys(map.result).reduce((result, supName) => {
const index = map.entities.superBlock[supName].order;
result[index] = supName;
return result;
}, []);
return {
...map,
result
};
})
.shareReplay();
}

function mapChallengeToLang({ translations = {}, ...challenge }, lang) {
if (!supportedLanguages[lang]) {
lang = 'en';
}
const translation = translations[lang] || {};
if (lang !== 'en') {
challenge = {
...challenge,
...translation
};
}
return challenge;
}

function getMapForLang(lang) {
return ({ entities: { challenge: challengeMap, ...entities }, result }) => {
entities.challenge = Object.keys(challengeMap)
.reduce((translatedChallengeMap, key) => {
translatedChallengeMap[key] = mapChallengeToLang(
challengeMap[key],
lang
);
return translatedChallengeMap;
}, {});
return { result, entities };
};
}
const log = debug('fcc:services:map');

function shouldNotFilterComingSoon({ isComingSoon, isBeta: challengeIsBeta }) {
return isDev ||
Expand Down

0 comments on commit 0a90d0f

Please sign in to comment.