feat: use static query to get idToNameMap (#36722)
This commit is contained in:
@ -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",
|
"principalType": "ROLE",
|
||||||
"principalId": "$everyone",
|
"principalId": "$everyone",
|
||||||
"permission": "ALLOW"
|
"permission": "ALLOW"
|
||||||
},
|
|
||||||
{
|
|
||||||
"accessType": "EXECUTE",
|
|
||||||
"principalType": "ROLE",
|
|
||||||
"principalId": "$everyone",
|
|
||||||
"permission": "ALLOW",
|
|
||||||
"property": "getIdToNameMap"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"methods": {}
|
"methods": {}
|
||||||
|
@ -1,29 +1,13 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
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 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 { 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 { FullWidthRow } from '../../helpers';
|
||||||
import SolutionViewer from '../../settings/SolutionViewer';
|
import SolutionViewer from '../../settings/SolutionViewer';
|
||||||
|
import { challengeTypes } from '../../../../utils/challengeTypes';
|
||||||
const mapStateToProps = createSelector(
|
|
||||||
challengeIdToNameMapSelector,
|
|
||||||
idToNameMap => ({
|
|
||||||
idToNameMap
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch =>
|
|
||||||
bindActionCreators({ fetchIdToNameMap }, dispatch);
|
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
completedMap: PropTypes.arrayOf(
|
completedMap: PropTypes.arrayOf(
|
||||||
@ -40,7 +24,6 @@ const propTypes = {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
fetchIdToNameMap: PropTypes.func.isRequired,
|
|
||||||
idToNameMap: PropTypes.objectOf(
|
idToNameMap: PropTypes.objectOf(
|
||||||
PropTypes.shape({
|
PropTypes.shape({
|
||||||
challengePath: PropTypes.string,
|
challengePath: PropTypes.string,
|
||||||
@ -50,7 +33,7 @@ const propTypes = {
|
|||||||
username: PropTypes.string
|
username: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
class Timeline extends Component {
|
class TimelineInner extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
@ -64,21 +47,14 @@ class Timeline extends Component {
|
|||||||
this.viewSolution = this.viewSolution.bind(this);
|
this.viewSolution = this.viewSolution.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
if (isEmpty(this.props.idToNameMap)) {
|
|
||||||
return this.props.fetchIdToNameMap();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderCompletion(completed) {
|
renderCompletion(completed) {
|
||||||
const { idToNameMap } = this.props;
|
const { idToNameMap } = this.props;
|
||||||
const { id, completedDate } = completed;
|
const { id, completedDate } = completed;
|
||||||
const { challengeTitle, challengePath } = idToNameMap[id];
|
const { challengeTitle, challengePath } = idToNameMap.get(id);
|
||||||
return (
|
return (
|
||||||
<tr key={id}>
|
<tr key={id}>
|
||||||
<td>
|
<td>
|
||||||
<Link to={`/learn/${challengePath}`}>{challengeTitle}</Link>
|
<Link to={challengePath}>{challengeTitle}</Link>
|
||||||
</td>
|
</td>
|
||||||
<td className='text-center'>
|
<td className='text-center'>
|
||||||
<time dateTime={format(completedDate, 'YYYY-MM-DDTHH:MM:SSZ')}>
|
<time dateTime={format(completedDate, 'YYYY-MM-DDTHH:MM:SSZ')}>
|
||||||
@ -108,9 +84,6 @@ class Timeline extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const { completedMap, idToNameMap, username } = this.props;
|
const { completedMap, idToNameMap, username } = this.props;
|
||||||
const { solutionToView: id, solutionOpen } = this.state;
|
const { solutionToView: id, solutionOpen } = this.state;
|
||||||
if (isEmpty(idToNameMap)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<FullWidthRow>
|
<FullWidthRow>
|
||||||
<h2 className='text-center'>Timeline</h2>
|
<h2 className='text-center'>Timeline</h2>
|
||||||
@ -132,7 +105,8 @@ class Timeline extends Component {
|
|||||||
{reverse(
|
{reverse(
|
||||||
sortBy(completedMap, ['completedDate']).filter(challenge => {
|
sortBy(completedMap, ['completedDate']).filter(challenge => {
|
||||||
return (
|
return (
|
||||||
challenge.challengeType !== 7 && idToNameMap[challenge.id]
|
challenge.challengeType !== challengeTypes.step &&
|
||||||
|
idToNameMap.has(challenge.id)
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
).map(this.renderCompletion)}
|
).map(this.renderCompletion)}
|
||||||
@ -147,7 +121,9 @@ class Timeline extends Component {
|
|||||||
>
|
>
|
||||||
<Modal.Header closeButton={true}>
|
<Modal.Header closeButton={true}>
|
||||||
<Modal.Title id='contained-modal-title'>
|
<Modal.Title id='contained-modal-title'>
|
||||||
{`${username}'s Solution to ${idToNameMap[id].challengeTitle}`}
|
{`${username}'s Solution to ${
|
||||||
|
idToNameMap.get(id).challengeTitle
|
||||||
|
}`}
|
||||||
</Modal.Title>
|
</Modal.Title>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
@ -168,10 +144,38 @@ class Timeline extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Timeline.displayName = 'Timeline';
|
TimelineInner.propTypes = propTypes;
|
||||||
Timeline.propTypes = propTypes;
|
|
||||||
|
|
||||||
export default connect(
|
function useIdToNameMap() {
|
||||||
mapStateToProps,
|
const {
|
||||||
mapDispatchToProps
|
allChallengeNode: { edges }
|
||||||
)(Timeline);
|
} = 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 { reducer as reduxFormReducer } from 'redux-form';
|
||||||
|
|
||||||
import { createTypes } from '../../../../utils/stateManagement';
|
import { createTypes } from '../../../../utils/stateManagement';
|
||||||
import { createAsyncTypes } from '../../../utils/createTypes';
|
|
||||||
|
|
||||||
import { createPoly } from '../utils/polyvinyl';
|
import { createPoly } from '../utils/polyvinyl';
|
||||||
import challengeModalEpic from './challenge-modal-epic';
|
import challengeModalEpic from './challenge-modal-epic';
|
||||||
@ -11,7 +10,6 @@ import codeLockEpic from './code-lock-epic';
|
|||||||
import createQuestionEpic from './create-question-epic';
|
import createQuestionEpic from './create-question-epic';
|
||||||
import codeStorageEpic from './code-storage-epic';
|
import codeStorageEpic from './code-storage-epic';
|
||||||
|
|
||||||
import { createIdToNameMapSaga } from './id-to-name-map-saga';
|
|
||||||
import { createExecuteChallengeSaga } from './execute-challenge-saga';
|
import { createExecuteChallengeSaga } from './execute-challenge-saga';
|
||||||
import { createCurrentChallengeSaga } from './current-challenge-saga';
|
import { createCurrentChallengeSaga } from './current-challenge-saga';
|
||||||
import { challengeTypes } from '../../../../utils/challengeTypes';
|
import { challengeTypes } from '../../../../utils/challengeTypes';
|
||||||
@ -21,7 +19,6 @@ export const backendNS = 'backendChallenge';
|
|||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
challengeFiles: {},
|
challengeFiles: {},
|
||||||
challengeIdToNameMap: {},
|
|
||||||
challengeMeta: {
|
challengeMeta: {
|
||||||
id: '',
|
id: '',
|
||||||
nextChallengePath: '/',
|
nextChallengePath: '/',
|
||||||
@ -78,9 +75,7 @@ export const types = createTypes(
|
|||||||
'resetChallenge',
|
'resetChallenge',
|
||||||
'submitChallenge',
|
'submitChallenge',
|
||||||
|
|
||||||
'moveToTab',
|
'moveToTab'
|
||||||
|
|
||||||
...createAsyncTypes('fetchIdToNameMap')
|
|
||||||
],
|
],
|
||||||
ns
|
ns
|
||||||
);
|
);
|
||||||
@ -94,7 +89,6 @@ export const epics = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const sagas = [
|
export const sagas = [
|
||||||
...createIdToNameMapSaga(types),
|
|
||||||
...createExecuteChallengeSaga(types),
|
...createExecuteChallengeSaga(types),
|
||||||
...createCurrentChallengeSaga(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 createQuestion = createAction(types.createQuestion);
|
||||||
export const initTests = createAction(types.initTests);
|
export const initTests = createAction(types.initTests);
|
||||||
export const updateTests = createAction(types.updateTests);
|
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 currentTabSelector = state => state[ns].currentTab;
|
||||||
export const challengeFilesSelector = state => state[ns].challengeFiles;
|
export const challengeFilesSelector = state => state[ns].challengeFiles;
|
||||||
export const challengeIdToNameMapSelector = state =>
|
|
||||||
state[ns].challengeIdToNameMap;
|
|
||||||
export const challengeMetaSelector = state => state[ns].challengeMeta;
|
export const challengeMetaSelector = state => state[ns].challengeMeta;
|
||||||
export const challengeTestsSelector = state => state[ns].challengeTests;
|
export const challengeTestsSelector = state => state[ns].challengeTests;
|
||||||
export const consoleOutputSelector = state => state[ns].consoleOut;
|
export const consoleOutputSelector = state => state[ns].consoleOut;
|
||||||
@ -230,10 +216,6 @@ const MAX_LOGS_SIZE = 64 * 1024;
|
|||||||
|
|
||||||
export const reducer = handleActions(
|
export const reducer = handleActions(
|
||||||
{
|
{
|
||||||
[types.fetchIdToNameMapComplete]: (state, { payload }) => ({
|
|
||||||
...state,
|
|
||||||
challengeIdToNameMap: payload
|
|
||||||
}),
|
|
||||||
[types.createFiles]: (state, { payload }) => ({
|
[types.createFiles]: (state, { payload }) => ({
|
||||||
...state,
|
...state,
|
||||||
challengeFiles: payload
|
challengeFiles: payload
|
||||||
|
@ -25,10 +25,6 @@ export function getSessionUser() {
|
|||||||
return get('/user/get-session-user');
|
return get('/user/get-session-user');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getIdToNameMap() {
|
|
||||||
return get('/api/challenges/get-id-to-name');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getUserProfile(username) {
|
export function getUserProfile(username) {
|
||||||
return get(`/api/users/get-public-profile?username=${username}`);
|
return get(`/api/users/get-public-profile?username=${username}`);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user