feat: use static query to get idToNameMap (#36722)
parent
f50ac6e115
commit
469c3f05c2
|
@ -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'
|
||||
}
|
||||
});
|
||||
}
|
|
@ -133,13 +133,6 @@
|
|||
"principalType": "ROLE",
|
||||
"principalId": "$everyone",
|
||||
"permission": "ALLOW"
|
||||
},
|
||||
{
|
||||
"accessType": "EXECUTE",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$everyone",
|
||||
"permission": "ALLOW",
|
||||
"property": "getIdToNameMap"
|
||||
}
|
||||
],
|
||||
"methods": {}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)];
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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}`);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue