feat(client, challenge-parser): add explanation to challenges (#56472)

This commit is contained in:
Tom
2024-10-04 12:59:50 -05:00
committed by GitHub
parent 32cf5e4616
commit 75401e13ee
13 changed files with 255 additions and 5 deletions

View File

@@ -261,6 +261,7 @@ exports.createSchemaCustomization = ({ actions }) => {
type Challenge {
blockType: String
challengeFiles: [FileContents]
explanation: String
notes: String
url: String
assignments: [String]

View File

@@ -388,6 +388,7 @@
"assignment-not-complete": "Please complete the assignments",
"assignments": "Assignments",
"question": "Question",
"explanation": "Explanation",
"solution-link": "Solution Link",
"source-code-link": "Source Code Link",
"ms-link": "Microsoft Link",

View File

@@ -174,6 +174,7 @@ export type ChallengeNode = {
demoType: 'onClick' | 'onLoad' | null;
description: string;
challengeFiles: ChallengeFiles;
explanation: string;
fields: Fields;
fillInTheBlank: FillInTheBlank;
forumTopicId: number;

View File

@@ -0,0 +1,3 @@
.explanation {
cursor: pointer;
}

View File

@@ -0,0 +1,29 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import Spacer from '../../../components/helpers/spacer';
import PrismFormatted from './prism-formatted';
interface ChallengeExplanationProps {
explanation: string;
}
function ChallengeExplanation({
explanation
}: ChallengeExplanationProps): JSX.Element {
const { t } = useTranslation();
return (
<>
<details className='explanation'>
<summary>{t('learn.explanation')}</summary>
<Spacer size='medium' />
<PrismFormatted className={'line-numbers'} text={explanation} />
</details>
<Spacer size='medium' />
</>
);
}
ChallengeExplanation.displayName = 'ChallengeExplanation';
export default ChallengeExplanation;

View File

@@ -18,6 +18,7 @@ import LearnLayout from '../../../components/layouts/learn';
import { ChallengeNode, ChallengeMeta, Test } from '../../../redux/prop-types';
import Hotkeys from '../components/hotkeys';
import ChallengeTitle from '../components/challenge-title';
import ChallegeExplanation from '../components/challenge-explanation';
import CompletionModal from '../components/completion-modal';
import HelpModal from '../components/help-modal';
import FillInTheBlanks from '../components/fill-in-the-blanks';
@@ -60,7 +61,6 @@ const mapDispatchToProps = (dispatch: Dispatch) =>
interface ShowFillInTheBlankProps {
challengeMounted: (arg0: string) => void;
data: { challengeNode: ChallengeNode };
description: string;
isChallengeCompleted: boolean;
initTests: (xs: Test[]) => void;
openCompletionModal: () => void;
@@ -250,6 +250,7 @@ class ShowFillInTheBlank extends Component<
title,
description,
instructions,
explanation,
superBlock,
block,
translationPending,
@@ -328,7 +329,13 @@ class ShowFillInTheBlank extends Component<
handleInputChange={this.handleInputChange}
/>
</ObserveKeys>
<Spacer size='medium' />
{explanation ? (
<ChallegeExplanation explanation={explanation} />
) : (
<Spacer size='medium' />
)}
<Button
block={true}
variant='primary'
@@ -368,6 +375,7 @@ export const query = graphql`
title
description
instructions
explanation
challengeType
helpCategory
superBlock

View File

@@ -23,6 +23,7 @@ import HelpModal from '../components/help-modal';
import Scene from '../components/scene/scene';
import PrismFormatted from '../components/prism-formatted';
import ChallengeTitle from '../components/challenge-title';
import ChallegeExplanation from '../components/challenge-explanation';
import MultipleChoiceQuestions from '../components/multiple-choice-questions';
import Assignments from '../components/assignments';
import {
@@ -62,7 +63,6 @@ const mapDispatchToProps = (dispatch: Dispatch) =>
interface ShowOdinProps {
challengeMounted: (arg0: string) => void;
data: { challengeNode: ChallengeNode };
description: string;
initTests: (xs: Test[]) => void;
isChallengeCompleted: boolean;
openCompletionModal: () => void;
@@ -231,6 +231,7 @@ class ShowOdin extends Component<ShowOdinProps, ShowOdinState> {
title,
description,
instructions,
explanation,
superBlock,
block,
videoId,
@@ -336,7 +337,13 @@ class ShowOdin extends Component<ShowOdinProps, ShowOdinState> {
handleOptionChange={this.handleOptionChange}
/>
</ObserveKeys>
<Spacer size='medium' />
{explanation ? (
<ChallegeExplanation explanation={explanation} />
) : (
<Spacer size='medium' />
)}
<Button
block={true}
size='medium'
@@ -398,6 +405,7 @@ export const query = graphql`
title
description
instructions
explanation
challengeType
helpCategory
superBlock

View File

@@ -8,6 +8,14 @@
{
"id": "66607e5b317411dd5e8aae22",
"title": "Dialogue 1: I'm Tom"
},
{
"id": "66a909dc2e382527ead44c23",
"title": "Task 1"
},
{
"id": "66a90bbd872bca2a1b7e63a9",
"title": "Task 2"
}
],
"helpCategory": "English"

View File

@@ -0,0 +1,101 @@
---
id: 66a909dc2e382527ead44c23
title: Task 1
challengeType: 19
dashedName: task-1
---
<!-- (Audio) Bob: Hey Lisa, do you have a minute? -->
# --instructions--
Listen to the audio and answer the question below.
# --questions--
## --text--
What does Bob want to know from Lisa?
## --answers--
If Lisa can lend him some money.
### --feedback--
His question is not about borrowing money.
---
If Lisa can help him with his project.
### --feedback--
The question is not specifically about a project. It is more general.
---
If Lisa has time to talk.
---
If Lisa is feeling well.
### --feedback--
This question is not about Lisa's health.
## --video-solution--
3
# --explanation--
`Do you have a minute?` is a polite way to ask if someone has time to talk.
# --scene--
```json
{
"setup": {
"background": "company2-boardroom.png",
"characters": [
{
"character": "Bob",
"position": {
"x": 50,
"y": 15,
"z": 1.2
},
"opacity": 0
}
],
"audio": {
"filename": "B1_1-1.mp3",
"startTime": 1,
"startTimestamp": 0,
"finishTimestamp": 1.72
}
},
"commands": [
{
"character": "Bob",
"opacity": 1,
"startTime": 0
},
{
"character": "Bob",
"startTime": 1,
"finishTime": 2.72,
"dialogue": {
"text": "Hey Lisa, do you have a minute?",
"align": "center"
}
},
{
"character": "Bob",
"opacity": 0,
"startTime": 3.22
}
]
}
```

View File

@@ -0,0 +1,80 @@
---
id: 66a90bbd872bca2a1b7e63a9
title: Task 2
challengeType: 22
dashedName: task-2
---
<!-- (Audio) Bob: Hey Lisa, do you have a minute? -->
# --instructions--
Listen to the audio and complete the sentence below.
# --fillInTheBlank--
## --sentence--
`Hey Lisa, BLANK you have a minute?`
## --blanks--
`do`
### --feedback--
This is the auxiliary verb used to form the question.
# --explanation--
The auxiliary verb `do` is used to form questions in the `Present Simple` tense.
For example, in the question `Do you have a minute?`, `do` is used to ask if someone has time.
# --scene--
```json
{
"setup": {
"background": "company2-boardroom.png",
"characters": [
{
"character": "Bob",
"position": {
"x": 50,
"y": 15,
"z": 1.2
},
"opacity": 0
}
],
"audio": {
"filename": "B1_1-1.mp3",
"startTime": 1,
"startTimestamp": 0,
"finishTimestamp": 1.72
}
},
"commands": [
{
"character": "Bob",
"opacity": 1,
"startTime": 0
},
{
"character": "Bob",
"startTime": 1,
"finishTime": 2.72,
"dialogue": {
"text": "Hey Lisa, do you have a minute?",
"align": "center"
}
},
{
"character": "Bob",
"opacity": 0,
"startTime": 3.22
}
]
}
```

View File

@@ -143,6 +143,7 @@ const schema = Joi.object()
is: [
challengeTypes.step,
challengeTypes.video,
challengeTypes.multipleChoice,
challengeTypes.fillInTheBlank
],
then: Joi.string().allow(''),
@@ -150,6 +151,10 @@ const schema = Joi.object()
}),
disableLoopProtectTests: Joi.boolean().required(),
disableLoopProtectPreview: Joi.boolean().required(),
explanation: Joi.when('challengeType', {
is: [challengeTypes.multipleChoice, challengeTypes.fillInTheBlank],
then: Joi.string()
}),
challengeFiles: Joi.array().items(fileJoi),
guideUrl: Joi.string().uri({ scheme: 'https' }),
hasEditableBoundaries: Joi.boolean(),

View File

@@ -140,6 +140,7 @@ const schema = Joi.object()
is: [
challengeTypes.step,
challengeTypes.video,
challengeTypes.multipleChoice,
challengeTypes.fillInTheBlank
],
then: Joi.string().allow(''),
@@ -147,6 +148,10 @@ const schema = Joi.object()
}),
disableLoopProtectTests: Joi.boolean().required(),
disableLoopProtectPreview: Joi.boolean().required(),
explanation: Joi.when('challengeType', {
is: [challengeTypes.multipleChoice, challengeTypes.fillInTheBlank],
then: Joi.string()
}),
challengeFiles: Joi.array().items(fileJoi),
guideUrl: Joi.string().uri({ scheme: 'https' }),
hasEditableBoundaries: Joi.boolean(),

View File

@@ -53,7 +53,7 @@ const processor = unified()
.use(addScene)
.use(addQuizzes)
.use(addTests)
.use(addText, ['description', 'instructions', 'notes']);
.use(addText, ['description', 'instructions', 'notes', 'explanation']);
exports.parseMD = function parseMD(filename) {
return new Promise((resolve, reject) => {