feat(client/curriculum): add prerequisites to take exam (#50767)

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
Tom
2023-07-06 05:44:05 -05:00
committed by GitHub
parent 17ff5d2385
commit c7a3c6ce52
7 changed files with 77 additions and 9 deletions

View File

@@ -85,6 +85,10 @@ exports.createPages = function createPages({ graphql, actions, reporter }) {
hasEditableBoundaries
id
order
prerequisites {
id
title
}
required {
link
src
@@ -284,6 +288,7 @@ exports.createSchemaCustomization = ({ actions }) => {
notes: String
url: String
assignments: [String]
prerequisites: [PrerequisiteChallenge]
}
type FileContents {
fileKey: String
@@ -294,6 +299,10 @@ exports.createSchemaCustomization = ({ actions }) => {
tail: String
editableRegionBoundaries: [Int]
}
type PrerequisiteChallenge {
id: String
title: String
}
`;
createTypes(typeDefs);
};

View File

@@ -1011,6 +1011,7 @@
"Through hands-on exercises and projects, you will learn the fundamentals of C#, including variables, data types, control structures, and object-oriented programming principles.",
"By the end of this course, you will have gained the practical skills and knowledge needed to confidently leverage C# for building robust and scalable applications"
],
"note": "Note: You must complete the trophy challenge in each section below to qualify for the certification exam.",
"blocks": {
"write-your-first-code-using-c-sharp": {
"title": "Write Your First Code Using C#",

View File

@@ -402,7 +402,9 @@
"season-greetings-fcc": "Season's Greetings from the freeCodeCamp community 🎉",
"if-getting-value": "If you're getting a lot out of freeCodeCamp, now is a great time to donate to support our charity's mission.",
"building-a-university": "We're Building a Free Computer Science University Degree Program",
"if-help-university": "We've already made a ton of progress. Support our charity with the long road ahead."
"if-help-university": "We've already made a ton of progress. Support our charity with the long road ahead.",
"qualified-for-exam": "Congratulations, you have completed all the requirements to qualify for the exam.",
"not-qualified-for-exam": "You have not met the requirements to be eligible for the exam. To qualify, please complete the following challenges:"
},
"donate": {
"title": "Support our charity",

View File

@@ -71,6 +71,11 @@ export interface VideoLocaleIds {
portuguese?: string;
}
export interface PrerequisiteChallenge {
id: string;
title: string;
}
export type ChallengeWithCompletedNode = {
block: string;
challengeType: number;
@@ -114,6 +119,7 @@ export type ChallengeNode = {
type: string;
};
notes: string;
prerequisites: PrerequisiteChallenge[];
removeComments: boolean;
isLocked: boolean;
isPrivate: boolean;

View File

@@ -1,5 +1,5 @@
// Package Utilities
import { Grid, Col, Row, Button } from '@freecodecamp/react-bootstrap';
import { Alert, Grid, Col, Row, Button } from '@freecodecamp/react-bootstrap';
import { graphql } from 'gatsby';
import React, { Component, RefObject } from 'react';
import Helmet from 'react-helmet';
@@ -21,7 +21,6 @@ import Hotkeys from '../components/hotkeys';
import { startExam, stopExam } from '../../../redux/actions';
import {
completedChallengesSelector,
partiallyCompletedChallengesSelector,
isSignedInSelector,
examInProgressSelector
} from '../../../redux/selectors';
@@ -51,19 +50,16 @@ const mapStateToProps = createSelector(
completedChallengesSelector,
isChallengeCompletedSelector,
isSignedInSelector,
partiallyCompletedChallengesSelector,
examInProgressSelector,
(
completedChallenges: CompletedChallenge[],
isChallengeCompleted: boolean,
isSignedIn: boolean,
partiallyCompletedChallenges: CompletedChallenge[],
examInProgress: boolean
) => ({
completedChallenges,
isChallengeCompleted,
isSignedIn,
partiallyCompletedChallenges,
examInProgress
})
);
@@ -369,9 +365,9 @@ class ShowExam extends Component<ShowExamProps, ShowExamState> {
});
this.timerInterval = setInterval(() => {
this.setState({
examTimeInSeconds: this.state.examTimeInSeconds + 1
});
this.setState(state => ({
examTimeInSeconds: state.examTimeInSeconds + 1
}));
}, 1000);
this.setState(
@@ -462,6 +458,7 @@ class ShowExam extends Component<ShowExamProps, ShowExamState> {
description,
fields: { blockName },
instructions,
prerequisites,
superBlock,
title,
translationPending
@@ -469,6 +466,7 @@ class ShowExam extends Component<ShowExamProps, ShowExamState> {
}
},
examInProgress,
completedChallenges,
isChallengeCompleted,
openFinishExamModal,
pageContext: {
@@ -485,6 +483,13 @@ class ShowExam extends Component<ShowExamProps, ShowExamState> {
showResults
} = this.state;
const missingPrequisites = prerequisites.filter(
prerequisite =>
!completedChallenges.find(({ id }) => prerequisite.id === id)
);
const qualifiedForExam = missingPrequisites.length === 0;
const blockNameTitle = `${t(
`intro:${superBlock}.blocks.${block}.title`
)}: ${title}`;
@@ -613,6 +618,23 @@ class ShowExam extends Component<ShowExamProps, ShowExamState> {
{title}
</ChallengeTitle>
<Spacer size='medium' />
{qualifiedForExam ? (
<Alert id='qualified-for-exam' bsStyle='info'>
<p>{t('learn.qualified-for-exam')}</p>
</Alert>
) : (
<Alert id='not-qualified-for-exam' bsStyle='danger'>
<p>{t('learn.not-qualified-for-exam')}</p>
<Spacer size='small' />
<ul>
{missingPrequisites.map(({ title, id }) => (
<li key={id}>{title}</li>
))}
</ul>
</Alert>
)}
<PrismFormatted text={description} />
<Spacer size='medium' />
<PrismFormatted text={instructions} />
@@ -621,6 +643,7 @@ class ShowExam extends Component<ShowExamProps, ShowExamState> {
block={true}
bsStyle='primary'
data-cy='start-exam'
disabled={!qualifiedForExam}
onClick={this.runExam}
>
{t('buttons.click-start-exam')}
@@ -648,6 +671,7 @@ export const query = graphql`
query ExamChallenge($slug: String!) {
challengeNode(challenge: { fields: { slug: { eq: $slug } } }) {
challenge {
block
challengeType
description
fields {
@@ -656,6 +680,10 @@ export const query = graphql`
helpCategory
id
instructions
prerequisites {
id
title
}
superBlock
title
translationPending

View File

@@ -3,6 +3,19 @@ id: 647e22d18acb466c97ccbef8
title: Foundational C# with Microsoft Certification Exam
challengeType: 17
dashedName: foundational-c-sharp-with-microsoft-certification-exam
prerequisites:
- id: 647f85d407d29547b3bee1bb
title: Trophy - Write Your First Code Using C#
- id: 647f87dc07d29547b3bee1bf
title: Trophy - Create and Run Simple C# Console Applications
- id: 647f882207d29547b3bee1c0
title: Trophy - Add Logic to C# Console Applications
- id: 647f867a07d29547b3bee1bc
title: Trophy - Work with Variable Data in C# Console Applications
- id: 647f877f07d29547b3bee1be
title: Trophy - Create Methods in C# Console Applications
- id: 647f86ff07d29547b3bee1bd
title: Trophy - Debug C# Console Applications
---
# --description--

View File

@@ -21,6 +21,11 @@ const fileJoi = Joi.object().keys({
history: Joi.array().items(Joi.string().allow(''))
});
const prerequisitesJoi = Joi.object().keys({
id: Joi.objectId().required(),
title: Joi.string().required()
});
const schema = Joi.object()
.keys({
block: Joi.string().regex(slugRE).required(),
@@ -55,6 +60,10 @@ const schema = Joi.object()
isPrivate: Joi.bool(),
notes: Joi.string().allow(''),
order: Joi.number(),
prerequisites: Joi.when('challengeType', {
is: [challengeTypes.exam],
then: Joi.array().items(prerequisitesJoi)
}),
// video challenges only:
videoId: Joi.when('challengeType', {
is: challengeTypes.video,