Files
freeCodeCamp/client/src/templates/Challenges/redux/completion-epic.js

226 lines
6.2 KiB
JavaScript
Raw Normal View History

import { navigate } from 'gatsby';
import { omit } from 'lodash-es';
import { ofType } from 'redux-observable';
import { of, empty } from 'rxjs';
2018-05-24 19:45:38 +01:00
import {
switchMap,
retry,
catchError,
concat,
2018-09-12 15:58:08 +03:00
filter,
finalize
2018-05-24 19:45:38 +01:00
} from 'rxjs/operators';
2018-04-06 14:51:52 +01:00
import { challengeTypes, submitTypes } from '../../../../utils/challenge-types';
import {
userSelector,
isSignedInSelector,
2019-01-03 01:03:43 +03:00
submitComplete,
updateComplete,
updateFailed,
usernameSelector
} from '../../../redux';
2018-04-06 14:51:52 +01:00
import { getVerifyCanClaimCert } from '../../../utils/ajax';
import postUpdate$ from '../utils/postUpdate$';
import { actionTypes } from './action-types';
import {
projectFormValuesSelector,
challengeMetaSelector,
challengeTestsSelector,
closeModal,
challengeFilesSelector,
updateSolutionFormValues
} from './';
2018-04-06 14:51:52 +01:00
function postChallenge(update, username) {
const saveChallenge = postUpdate$(update).pipe(
2018-05-24 19:45:38 +01:00
retry(3),
switchMap(({ points }) => {
const payloadWithClientProperties = {
...omit(update.payload, ['files']),
challengeFiles: update.payload.files ?? null
};
return of(
submitComplete({
username,
points,
...payloadWithClientProperties
}),
updateComplete()
);
}),
catchError(() => of(updateFailed(update)))
2018-05-24 19:45:38 +01:00
);
return saveChallenge;
}
2018-04-06 14:51:52 +01:00
2018-05-24 19:45:38 +01:00
function submitModern(type, state) {
const challengeType = state.challenge.challengeMeta.challengeType;
2018-05-24 19:45:38 +01:00
const tests = challengeTestsSelector(state);
if (
challengeType === 11 ||
(tests.length > 0 && tests.every(test => test.pass && !test.err))
) {
if (type === actionTypes.checkChallenge) {
2018-05-24 19:45:38 +01:00
return of({ type: 'this was a check challenge' });
}
2018-04-06 14:51:52 +01:00
if (type === actionTypes.submitChallenge) {
const { id, block } = challengeMetaSelector(state);
const challengeFiles = challengeFilesSelector(state);
2018-05-24 19:45:38 +01:00
const { username } = userSelector(state);
const challengeInfo = {
id,
challengeType
};
// Only send files to server, if it is a JS project or multiFile cert project
if (
block === 'javascript-algorithms-and-data-structures-projects' ||
challengeType === challengeTypes.multiFileCertProject
) {
challengeInfo.files = challengeFiles.reduce(
(acc, { fileKey, ...curr }) => [...acc, { ...curr, key: fileKey }],
[]
);
}
const update = {
endpoint: '/modern-challenge-completed',
payload: challengeInfo
};
return postChallenge(update, username);
2018-05-24 19:45:38 +01:00
}
}
return empty();
}
2018-04-06 14:51:52 +01:00
2018-05-24 19:45:38 +01:00
function submitProject(type, state) {
if (type === actionTypes.checkChallenge) {
2018-05-24 19:45:38 +01:00
return empty();
}
2018-04-06 14:51:52 +01:00
2018-09-27 14:19:03 +03:00
const { solution, githubLink } = projectFormValuesSelector(state);
2018-05-24 19:45:38 +01:00
const { id, challengeType } = challengeMetaSelector(state);
const { username } = userSelector(state);
const challengeInfo = { id, challengeType, solution };
if (challengeType === challengeTypes.backEndProject) {
challengeInfo.githubLink = githubLink;
}
const update = {
endpoint: '/project-completed',
payload: challengeInfo
};
return postChallenge(update, username).pipe(
concat(of(updateSolutionFormValues({})))
2018-05-24 19:45:38 +01:00
);
}
2018-04-06 14:51:52 +01:00
2018-05-24 19:45:38 +01:00
function submitBackendChallenge(type, state) {
const tests = challengeTestsSelector(state);
if (tests.length > 0 && tests.every(test => test.pass && !test.err)) {
if (type === actionTypes.submitChallenge) {
2018-05-24 19:45:38 +01:00
const { id } = challengeMetaSelector(state);
const { username } = userSelector(state);
2019-01-03 01:03:43 +03:00
const {
solution: { value: solution }
} = projectFormValuesSelector(state);
2018-05-24 19:45:38 +01:00
const challengeInfo = { id, solution };
const update = {
endpoint: '/backend-challenge-completed',
payload: challengeInfo
};
return postChallenge(update, username);
2018-05-24 19:45:38 +01:00
}
}
return empty();
}
2018-04-06 14:51:52 +01:00
2018-05-24 19:45:38 +01:00
const submitters = {
tests: submitModern,
backend: submitBackendChallenge,
'project.frontEnd': submitProject,
'project.backEnd': submitProject
};
2018-04-06 14:51:52 +01:00
export default function completionEpic(action$, state$) {
2018-04-06 14:51:52 +01:00
return action$.pipe(
ofType(actionTypes.submitChallenge),
2018-04-06 14:51:52 +01:00
switchMap(({ type }) => {
const state = state$.value;
2018-05-24 19:45:38 +01:00
const meta = challengeMetaSelector(state);
feat: enable new curriculum (#44183) * feat: use legacy flag chore: reorder challenges fix: linter revert: server change feat: unblock new editor fix: proper order fix: 0-based order fix: broke the order feat: move tribute certification to its own block feat: split the old projects block into 4 fix: put all blocks in order chore: add intro text refactor: use block, not blockName in query fix: project progress indicator * fix: reorder new challenges/certs * fix: reorder legacy challenges * fix: reintroduce legacy certs * feat: add showNewCurriculum flag to env * chore: forgot sample.env * feat: use feature flag for display * fix: rename meta + dirs to match new blocks * fix: add new blocks to help-category-map * fix: update completion-modal for new GQL schema * test: duplicate title/id errors -> warnings * fix: update completion-modal to new GQL schema Mk2 * chore: re-order metas (again) * fix: revert super-block-intro changes The intro needs to show both legacy and new content. We need to decide which pages are created, rather than than what a page shows when rendered. * feat: move upcoming curriculum into own superblock * fix: handle one certification with two superBlocks * fix: remove duplicated intros * fix: remove duplicate projects from /settings * fix: drop 'two' from Responsive Web Design Two * chore: rename slug suffix from two to v2 * feat: control display of new curriculum * feat: control project paths shown on /settings * fix: use new project order for /settings This does mean that /settings will change before the release, but I don't think it's serious. All the projects are there, just not in the legacy order. * fix: claim/show cert button * chore: remove isLegacy Since we have legacy superblocks, we don't currently need individual blocks to be legacy * test: fix utils.test * fix: verifyCanClaim needs certification If Shaun removes the cert claim cards, maybe we can remove this entirely * fix: add hasEditableBoundaries flags where needed * chore: remove isUpcomingChange * chore: v2 -> 22 Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
2021-12-20 10:36:31 -08:00
const { nextChallengePath, challengeType, superBlock, certification } =
meta;
2018-05-24 19:45:38 +01:00
const closeChallengeModal = of(closeModal('completion'));
let submitter = () => of({ type: 'no-user-signed-in' });
2018-05-24 19:45:38 +01:00
if (
!(challengeType in submitTypes) ||
!(submitTypes[challengeType] in submitters)
) {
throw new Error(
'Unable to find the correct submit function for challengeType ' +
challengeType
);
}
if (isSignedInSelector(state)) {
submitter = submitters[submitTypes[challengeType]];
}
const pathToNavigateTo = async () => {
return await findPathToNavigateTo(
feat: enable new curriculum (#44183) * feat: use legacy flag chore: reorder challenges fix: linter revert: server change feat: unblock new editor fix: proper order fix: 0-based order fix: broke the order feat: move tribute certification to its own block feat: split the old projects block into 4 fix: put all blocks in order chore: add intro text refactor: use block, not blockName in query fix: project progress indicator * fix: reorder new challenges/certs * fix: reorder legacy challenges * fix: reintroduce legacy certs * feat: add showNewCurriculum flag to env * chore: forgot sample.env * feat: use feature flag for display * fix: rename meta + dirs to match new blocks * fix: add new blocks to help-category-map * fix: update completion-modal for new GQL schema * test: duplicate title/id errors -> warnings * fix: update completion-modal to new GQL schema Mk2 * chore: re-order metas (again) * fix: revert super-block-intro changes The intro needs to show both legacy and new content. We need to decide which pages are created, rather than than what a page shows when rendered. * feat: move upcoming curriculum into own superblock * fix: handle one certification with two superBlocks * fix: remove duplicated intros * fix: remove duplicate projects from /settings * fix: drop 'two' from Responsive Web Design Two * chore: rename slug suffix from two to v2 * feat: control display of new curriculum * feat: control project paths shown on /settings * fix: use new project order for /settings This does mean that /settings will change before the release, but I don't think it's serious. All the projects are there, just not in the legacy order. * fix: claim/show cert button * chore: remove isLegacy Since we have legacy superblocks, we don't currently need individual blocks to be legacy * test: fix utils.test * fix: verifyCanClaim needs certification If Shaun removes the cert claim cards, maybe we can remove this entirely * fix: add hasEditableBoundaries flags where needed * chore: remove isUpcomingChange * chore: v2 -> 22 Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
2021-12-20 10:36:31 -08:00
certification,
nextChallengePath,
superBlock,
state,
challengeType
);
};
2018-05-24 19:45:38 +01:00
return submitter(type, state).pipe(
concat(closeChallengeModal),
filter(Boolean),
finalize(async () => navigate(await pathToNavigateTo()))
2018-05-24 19:45:38 +01:00
);
2018-04-06 14:51:52 +01:00
})
);
}
async function findPathToNavigateTo(
feat: enable new curriculum (#44183) * feat: use legacy flag chore: reorder challenges fix: linter revert: server change feat: unblock new editor fix: proper order fix: 0-based order fix: broke the order feat: move tribute certification to its own block feat: split the old projects block into 4 fix: put all blocks in order chore: add intro text refactor: use block, not blockName in query fix: project progress indicator * fix: reorder new challenges/certs * fix: reorder legacy challenges * fix: reintroduce legacy certs * feat: add showNewCurriculum flag to env * chore: forgot sample.env * feat: use feature flag for display * fix: rename meta + dirs to match new blocks * fix: add new blocks to help-category-map * fix: update completion-modal for new GQL schema * test: duplicate title/id errors -> warnings * fix: update completion-modal to new GQL schema Mk2 * chore: re-order metas (again) * fix: revert super-block-intro changes The intro needs to show both legacy and new content. We need to decide which pages are created, rather than than what a page shows when rendered. * feat: move upcoming curriculum into own superblock * fix: handle one certification with two superBlocks * fix: remove duplicated intros * fix: remove duplicate projects from /settings * fix: drop 'two' from Responsive Web Design Two * chore: rename slug suffix from two to v2 * feat: control display of new curriculum * feat: control project paths shown on /settings * fix: use new project order for /settings This does mean that /settings will change before the release, but I don't think it's serious. All the projects are there, just not in the legacy order. * fix: claim/show cert button * chore: remove isLegacy Since we have legacy superblocks, we don't currently need individual blocks to be legacy * test: fix utils.test * fix: verifyCanClaim needs certification If Shaun removes the cert claim cards, maybe we can remove this entirely * fix: add hasEditableBoundaries flags where needed * chore: remove isUpcomingChange * chore: v2 -> 22 Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
2021-12-20 10:36:31 -08:00
certification,
nextChallengePath,
superBlock,
state,
challengeType
) {
let canClaimCert = false;
const isProjectSubmission = [
challengeTypes.frontEndProject,
challengeTypes.backEndProject,
challengeTypes.pythonProject
].includes(challengeType);
if (isProjectSubmission) {
const username = usernameSelector(state);
try {
feat: enable new curriculum (#44183) * feat: use legacy flag chore: reorder challenges fix: linter revert: server change feat: unblock new editor fix: proper order fix: 0-based order fix: broke the order feat: move tribute certification to its own block feat: split the old projects block into 4 fix: put all blocks in order chore: add intro text refactor: use block, not blockName in query fix: project progress indicator * fix: reorder new challenges/certs * fix: reorder legacy challenges * fix: reintroduce legacy certs * feat: add showNewCurriculum flag to env * chore: forgot sample.env * feat: use feature flag for display * fix: rename meta + dirs to match new blocks * fix: add new blocks to help-category-map * fix: update completion-modal for new GQL schema * test: duplicate title/id errors -> warnings * fix: update completion-modal to new GQL schema Mk2 * chore: re-order metas (again) * fix: revert super-block-intro changes The intro needs to show both legacy and new content. We need to decide which pages are created, rather than than what a page shows when rendered. * feat: move upcoming curriculum into own superblock * fix: handle one certification with two superBlocks * fix: remove duplicated intros * fix: remove duplicate projects from /settings * fix: drop 'two' from Responsive Web Design Two * chore: rename slug suffix from two to v2 * feat: control display of new curriculum * feat: control project paths shown on /settings * fix: use new project order for /settings This does mean that /settings will change before the release, but I don't think it's serious. All the projects are there, just not in the legacy order. * fix: claim/show cert button * chore: remove isLegacy Since we have legacy superblocks, we don't currently need individual blocks to be legacy * test: fix utils.test * fix: verifyCanClaim needs certification If Shaun removes the cert claim cards, maybe we can remove this entirely * fix: add hasEditableBoundaries flags where needed * chore: remove isUpcomingChange * chore: v2 -> 22 Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
2021-12-20 10:36:31 -08:00
const response = await getVerifyCanClaimCert(username, certification);
if (response.status === 200) {
canClaimCert = response.data?.response?.message === 'can-claim-cert';
}
} catch (err) {
console.error('failed to verify if user can claim certificate', err);
}
}
let pathToNavigateTo;
if (nextChallengePath.includes(superBlock) && !canClaimCert) {
pathToNavigateTo = nextChallengePath;
} else if (canClaimCert) {
pathToNavigateTo = `/learn/${superBlock}/#claim-cert-block`;
} else {
pathToNavigateTo = `/learn/${superBlock}/#${superBlock}-projects`;
}
return pathToNavigateTo;
}