feat: use static query to get idToNameMap (#36722)

pull/36727/head
Valeriy 2019-08-30 20:07:28 +03:00 committed by mrugesh
parent f50ac6e115
commit 469c3f05c2
6 changed files with 46 additions and 145 deletions

View File

@ -1,56 +0,0 @@
import { Observable } from 'rx';
import { isEmpty } from 'lodash';
import { dasherize } from '../utils';
export default function(Challenge) {
let challengeIdToNameMap;
Challenge.on('dataSourceAttached', () => {
Challenge.findOne$ = Observable.fromNodeCallback(
Challenge.findOne,
Challenge
);
Challenge.findById$ = Observable.fromNodeCallback(
Challenge.findById,
Challenge
);
Challenge.find$ = Observable.fromNodeCallback(Challenge.find, Challenge);
Challenge.find({ isPrivate: false }, (err, challenges) => {
if (err) {
throw Error(err);
}
challengeIdToNameMap = challenges.reduce((map, challenge) => {
const { id, block, dashedName, title, superBlock } = challenge;
map[id] = {
challengeTitle: title,
challengePath: `${superBlock}/${dasherize(block)}/${dashedName}`
};
return map;
}, {});
});
function getIdToNameMap(cb) {
if (isEmpty(challengeIdToNameMap)) {
// We are waiting for the find query to resolve
return setTimeout(() => getIdToNameMap(cb), 50);
}
return cb(null, challengeIdToNameMap);
}
Challenge.getIdToNameMap = getIdToNameMap;
});
Challenge.remoteMethod('getIdToNameMap', {
returns: [
{
arg: 'user',
type: 'object',
root: true
}
],
http: {
path: '/get-id-to-name',
verb: 'GET'
}
});
}

View File

@ -133,13 +133,6 @@
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
},
{
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW",
"property": "getIdToNameMap"
}
],
"methods": {}

View File

@ -1,29 +1,13 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { createSelector } from 'reselect';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import format from 'date-fns/format';
import { find, reverse, sortBy, isEmpty } from 'lodash';
import { find, reverse, sortBy } from 'lodash';
import { Button, Modal, Table } from '@freecodecamp/react-bootstrap';
import { Link } from 'gatsby';
import { Link, useStaticQuery, graphql } from 'gatsby';
import {
challengeIdToNameMapSelector,
fetchIdToNameMap
} from '../../../templates/Challenges/redux';
import { FullWidthRow } from '../../helpers';
import SolutionViewer from '../../settings/SolutionViewer';
const mapStateToProps = createSelector(
challengeIdToNameMapSelector,
idToNameMap => ({
idToNameMap
})
);
const mapDispatchToProps = dispatch =>
bindActionCreators({ fetchIdToNameMap }, dispatch);
import { challengeTypes } from '../../../../utils/challengeTypes';
const propTypes = {
completedMap: PropTypes.arrayOf(
@ -40,7 +24,6 @@ const propTypes = {
)
})
),
fetchIdToNameMap: PropTypes.func.isRequired,
idToNameMap: PropTypes.objectOf(
PropTypes.shape({
challengePath: PropTypes.string,
@ -50,7 +33,7 @@ const propTypes = {
username: PropTypes.string
};
class Timeline extends Component {
class TimelineInner extends Component {
constructor(props) {
super(props);
@ -64,21 +47,14 @@ class Timeline extends Component {
this.viewSolution = this.viewSolution.bind(this);
}
componentDidMount() {
if (isEmpty(this.props.idToNameMap)) {
return this.props.fetchIdToNameMap();
}
return null;
}
renderCompletion(completed) {
const { idToNameMap } = this.props;
const { id, completedDate } = completed;
const { challengeTitle, challengePath } = idToNameMap[id];
const { challengeTitle, challengePath } = idToNameMap.get(id);
return (
<tr key={id}>
<td>
<Link to={`/learn/${challengePath}`}>{challengeTitle}</Link>
<Link to={challengePath}>{challengeTitle}</Link>
</td>
<td className='text-center'>
<time dateTime={format(completedDate, 'YYYY-MM-DDTHH:MM:SSZ')}>
@ -108,9 +84,6 @@ class Timeline extends Component {
render() {
const { completedMap, idToNameMap, username } = this.props;
const { solutionToView: id, solutionOpen } = this.state;
if (isEmpty(idToNameMap)) {
return null;
}
return (
<FullWidthRow>
<h2 className='text-center'>Timeline</h2>
@ -132,7 +105,8 @@ class Timeline extends Component {
{reverse(
sortBy(completedMap, ['completedDate']).filter(challenge => {
return (
challenge.challengeType !== 7 && idToNameMap[challenge.id]
challenge.challengeType !== challengeTypes.step &&
idToNameMap.has(challenge.id)
);
})
).map(this.renderCompletion)}
@ -147,7 +121,9 @@ class Timeline extends Component {
>
<Modal.Header closeButton={true}>
<Modal.Title id='contained-modal-title'>
{`${username}'s Solution to ${idToNameMap[id].challengeTitle}`}
{`${username}'s Solution to ${
idToNameMap.get(id).challengeTitle
}`}
</Modal.Title>
</Modal.Header>
<Modal.Body>
@ -168,10 +144,38 @@ class Timeline extends Component {
}
}
Timeline.displayName = 'Timeline';
Timeline.propTypes = propTypes;
TimelineInner.propTypes = propTypes;
export default connect(
mapStateToProps,
mapDispatchToProps
)(Timeline);
function useIdToNameMap() {
const {
allChallengeNode: { edges }
} = useStaticQuery(graphql`
query challengeNodes {
allChallengeNode {
edges {
node {
fields {
slug
}
id
title
}
}
}
}
`);
const idToNameMap = new Map();
edges.forEach(({ node: { id, title, fields: { slug } } }) => {
idToNameMap.set(id, { challengeTitle: title, challengePath: slug });
});
return idToNameMap;
}
const Timeline = props => {
const idToNameMap = useIdToNameMap();
return <TimelineInner idToNameMap={idToNameMap} {...props} />;
};
Timeline.displayName = 'Timeline';
export default Timeline;

View File

@ -1,18 +0,0 @@
import { call, put, takeEvery } from 'redux-saga/effects';
import { getIdToNameMap } from '../../../utils/ajax';
import { fetchIdToNameMapComplete, fetchIdToNameMapError } from './';
function* fetchIdToNameMapSaga() {
try {
const { data } = yield call(getIdToNameMap);
yield put(fetchIdToNameMapComplete(data));
} catch (e) {
yield put(fetchIdToNameMapError(e));
}
}
export function createIdToNameMapSaga(types) {
return [takeEvery(types.fetchIdToNameMap, fetchIdToNameMapSaga)];
}

View File

@ -2,7 +2,6 @@ import { createAction, handleActions } from 'redux-actions';
import { reducer as reduxFormReducer } from 'redux-form';
import { createTypes } from '../../../../utils/stateManagement';
import { createAsyncTypes } from '../../../utils/createTypes';
import { createPoly } from '../utils/polyvinyl';
import challengeModalEpic from './challenge-modal-epic';
@ -11,7 +10,6 @@ import codeLockEpic from './code-lock-epic';
import createQuestionEpic from './create-question-epic';
import codeStorageEpic from './code-storage-epic';
import { createIdToNameMapSaga } from './id-to-name-map-saga';
import { createExecuteChallengeSaga } from './execute-challenge-saga';
import { createCurrentChallengeSaga } from './current-challenge-saga';
import { challengeTypes } from '../../../../utils/challengeTypes';
@ -21,7 +19,6 @@ export const backendNS = 'backendChallenge';
const initialState = {
challengeFiles: {},
challengeIdToNameMap: {},
challengeMeta: {
id: '',
nextChallengePath: '/',
@ -78,9 +75,7 @@ export const types = createTypes(
'resetChallenge',
'submitChallenge',
'moveToTab',
...createAsyncTypes('fetchIdToNameMap')
'moveToTab'
],
ns
);
@ -94,7 +89,6 @@ export const epics = [
];
export const sagas = [
...createIdToNameMapSaga(types),
...createExecuteChallengeSaga(types),
...createCurrentChallengeSaga(types)
];
@ -115,12 +109,6 @@ export const createFiles = createAction(types.createFiles, challengeFiles =>
)
);
export const fetchIdToNameMap = createAction(types.fetchIdToNameMap);
export const fetchIdToNameMapComplete = createAction(
types.fetchIdToNameMapComplete
);
export const fetchIdToNameMapError = createAction(types.fetchIdToNameMapError);
export const createQuestion = createAction(types.createQuestion);
export const initTests = createAction(types.initTests);
export const updateTests = createAction(types.updateTests);
@ -162,8 +150,6 @@ export const moveToTab = createAction(types.moveToTab);
export const currentTabSelector = state => state[ns].currentTab;
export const challengeFilesSelector = state => state[ns].challengeFiles;
export const challengeIdToNameMapSelector = state =>
state[ns].challengeIdToNameMap;
export const challengeMetaSelector = state => state[ns].challengeMeta;
export const challengeTestsSelector = state => state[ns].challengeTests;
export const consoleOutputSelector = state => state[ns].consoleOut;
@ -230,10 +216,6 @@ const MAX_LOGS_SIZE = 64 * 1024;
export const reducer = handleActions(
{
[types.fetchIdToNameMapComplete]: (state, { payload }) => ({
...state,
challengeIdToNameMap: payload
}),
[types.createFiles]: (state, { payload }) => ({
...state,
challengeFiles: payload

View File

@ -25,10 +25,6 @@ export function getSessionUser() {
return get('/user/get-session-user');
}
export function getIdToNameMap() {
return get('/api/challenges/get-id-to-name');
}
export function getUserProfile(username) {
return get(`/api/users/get-public-profile?username=${username}`);
}