Skip to content

Commit

Permalink
Quiz item message on model solution and code block fix (#1221)
Browse files Browse the repository at this point in the history
* Fix the code block

* Implement message on model solution to quiz item
  • Loading branch information
nygrenh authored Dec 19, 2023
1 parent 35ffa6b commit c0b4843
Show file tree
Hide file tree
Showing 21 changed files with 209 additions and 26 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import "highlight.js/styles/atom-one-dark.css"
import { css } from "@emotion/css"
import hljs from "highlight.js"
import { useEffect, useRef } from "react"
import { useEffect, useMemo, useRef } from "react"

import { sanitizeCourseMaterialHtml } from "../../../../../utils/sanitizeCourseMaterialHtml"

Expand All @@ -17,14 +17,21 @@ const SyntaxHighlightedContainer: React.FC<SyntaxHighlightedContainerProps> = ({
}
hljs.highlightElement(ref.current)
}, [ref])

// The content coming from gutenberg contains <br> tags which do not work when we higlight the code with hljs
// So we'll replace the br tags with newlines
const replacedContent = useMemo(() => {
return content?.replace(/<br\s*\\?>/g, "\n") ?? ""
}, [content])

return (
<code
className={css`
background-color: #1a2333;
border-radius: 4px;
`}
ref={ref}
dangerouslySetInnerHTML={{ __html: sanitizeCourseMaterialHtml(content ?? "") }}
dangerouslySetInnerHTML={{ __html: sanitizeCourseMaterialHtml(replacedContent) }}
/>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import dynamic from "next/dynamic"

import { BlockRendererProps } from "../../.."
import { CodeAttributes } from "../../../../../../types/GutenbergBlockAttributes"
import BreakFromCentered from "../../../../../shared-module/components/Centering/BreakFromCentered"
import Spinner from "../../../../../shared-module/components/Spinner"
import { monospaceFont } from "../../../../../shared-module/styles"
import withErrorBoundary from "../../../../../shared-module/utils/withErrorBoundary"
Expand All @@ -20,18 +21,22 @@ const CodeBlock: React.FC<React.PropsWithChildren<BlockRendererProps<CodeAttribu
}) => {
const { anchor, content, fontSize } = data.attributes
return (
<pre
className={css`
${fontSize && `font-size: ${fontSizeMapper(fontSize)};`}
font-family: ${monospaceFont} !important;
line-height: 1.75rem;
white-space: pre-wrap;
overflow-wrap: break-word;
`}
{...(anchor && { id: anchor })}
>
<SyntaxHighlightedContainer content={content} />
</pre>
<BreakFromCentered sidebar={false}>
<pre
className={css`
max-width: 1000px;
margin: 0 auto;
${fontSize && `font-size: ${fontSizeMapper(fontSize)};`}
font-family: ${monospaceFont} !important;
line-height: 1.75rem;
white-space: pre-wrap;
overflow-wrap: break-word;
`}
{...(anchor && { id: anchor })}
>
<SyntaxHighlightedContainer content={content} />
</pre>
</BreakFromCentered>
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,18 @@ const MultipleChoiceEditor: React.FC<MultipleChoiceEditorProps> = ({ quizItemId
}}
label={t("failure-message")}
/>
<ParsedTextField
value={selected.messageOnModelSolution ?? ""}
onChange={(newValue) => {
updateState((draft) => {
if (!draft) {
return
}
draft.messageOnModelSolution = newValue
})
}}
label={t("label-message-on-model-solution")}
/>
</AdvancedOptionsContainer>
</details>
</Accordion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import TextField from "../../../../../shared-module/components/InputFields/TextF
import { primaryFont } from "../../../../../shared-module/styles"
import findQuizItem from "../../utils/general"
import EditorCard from "../common/EditorCard"
import ParsedTextField from "../common/ParsedTextField"

interface ClosedEndedQuestionEditorProps {
quizItemId: string
Expand Down Expand Up @@ -329,6 +330,18 @@ const ClosedEndedQuestionEditor: React.FC<ClosedEndedQuestionEditorProps> = ({ q
<RegexTestTableContainer>
<RegexTestTable quizItem={selected} testStrings={testStrings} />
</RegexTestTableContainer>
<ParsedTextField
value={selected.messageOnModelSolution ?? ""}
onChange={(newValue) => {
updateState((draft) => {
if (!draft) {
return
}
draft.messageOnModelSolution = newValue
})
}}
label={t("label-message-on-model-solution")}
/>
</details>
</Accordion>
</EditorCard>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,18 @@ const MultipleChoiceEditor: React.FC<MultipleChoiceEditorProps> = ({ quizItemId
}}
label={t("failure-message")}
/>
<ParsedTextField
value={selected.messageOnModelSolution ?? ""}
onChange={(newValue) => {
updateState((draft) => {
if (!draft) {
return
}
draft.messageOnModelSolution = newValue
})
}}
label={t("label-message-on-model-solution")}
/>
</AdvancedOptionsContainer>
</details>
</Accordion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,18 @@ const MultipleChoiceEditor: React.FC<MultipleChoiceEditorProps> = ({ quizItemId
}}
label={t("failure-message")}
/>
<ParsedTextField
value={selected.messageOnModelSolution ?? ""}
onChange={(newValue) => {
updateState((draft) => {
if (!draft) {
return
}
draft.messageOnModelSolution = newValue
})
}}
label={t("label-message-on-model-solution")}
/>
</AdvancedOptionsContainer>
</details>
</Accordion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ const createEmptyQuizItem = (type: QuizItemType) => {
body: null,
n: 2,
options: [],
messageOnModelSolution: null,
} as PrivateSpecQuizItemChooseN
case "closed-ended-question":
return {
Expand Down Expand Up @@ -127,6 +128,7 @@ const createEmptyQuizItem = (type: QuizItemType) => {
title: null,
fogOfWar: false,
options: [],
messageOnModelSolution: null,
} as PrivateSpecQuizItemMultiplechoice
case "multiple-choice-dropdown":
return {
Expand All @@ -137,13 +139,13 @@ const createEmptyQuizItem = (type: QuizItemType) => {
direction: "row",
failureMessage: null,
multipleChoiceMultipleOptionsGradingPolicy: "default",

order: 0,
sharedOptionFeedbackMessage: null,
shuffleOptions: false,
successMessage: null,
title: null,
options: [],
messageOnModelSolution: null,
} as PrivateSpecQuizItemMultiplechoiceDropdown
case "scale":
return {
Expand All @@ -168,6 +170,7 @@ const createEmptyQuizItem = (type: QuizItemType) => {
order: 0,
successMessage: null,
timelineItems: [],
messageOnModelSolution: null,
} as PrivateSpecQuizItemTimeline
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import {
UserItemAnswerTimeline,
} from "../../../../types/quizTypes/answer"
import { ItemAnswerFeedback } from "../../../../types/quizTypes/grading"
import { ModelSolutionQuiz } from "../../../../types/quizTypes/modelSolutionSpec"
import {
ModelSolutionQuiz,
ModelSolutionQuizItem,
} from "../../../../types/quizTypes/modelSolutionSpec"
import { QuizItemType } from "../../../../types/quizTypes/privateSpec"
import {
PublicSpecQuiz,
Expand Down Expand Up @@ -114,7 +117,10 @@ const FlexItem = styled.div`
flex: 1;
`

const SubmissionFeedback: React.FC<{ itemFeedback: ItemAnswerFeedback }> = ({ itemFeedback }) => {
const SubmissionFeedback: React.FC<{
itemFeedback: ItemAnswerFeedback
itemModelSolution: ModelSolutionQuizItem | null
}> = ({ itemFeedback, itemModelSolution }) => {
const { t } = useTranslation()

let backgroundColor = "#fffaf1"
Expand Down Expand Up @@ -147,6 +153,19 @@ const SubmissionFeedback: React.FC<{ itemFeedback: ItemAnswerFeedback }> = ({ it

const customItemFeedback = useMemo(() => {
const customItemFeedback = itemFeedback.quiz_item_feedback?.trim()
// If feedback on model solution is defined, this feedback takes precedence as the user is allowed to see the model solution and the teacher wants to show a custom message on the model solution
let messageOnModelSolution = itemModelSolution?.messageOnModelSolution ?? null
if (messageOnModelSolution !== null && messageOnModelSolution.trim() !== "") {
messageOnModelSolution = messageOnModelSolution.trim()
if (
!messageOnModelSolution?.endsWith(".") &&
!messageOnModelSolution?.endsWith("!") &&
!messageOnModelSolution?.endsWith("?")
) {
return messageOnModelSolution + "."
}
return messageOnModelSolution
}
if (
customItemFeedback === "" ||
customItemFeedback === null ||
Expand Down Expand Up @@ -225,7 +244,10 @@ const Submission: React.FC<React.PropsWithChildren<SubmissionProps>> = ({
)[0]
const feedback = itemAnswerFeedback &&
componentDescriptor.shouldDisplayCorrectnessMessageAfterAnswer && (
<SubmissionFeedback itemFeedback={itemAnswerFeedback} />
<SubmissionFeedback
itemFeedback={itemAnswerFeedback}
itemModelSolution={itemModelSolution}
/>
)
const missingQuizItemAnswer = !quizItemAnswer && (
<div
Expand Down
9 changes: 8 additions & 1 deletion services/quizzes/src/grading/assessment/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,14 @@ const assessAnswers = (quizAnswer: UserAnswer, quiz: PrivateSpecQuiz): QuizItemA
return quizAnswer.itemAnswers.map((itemAnswer) => {
const quizItem = quiz.items.find((quizItem) => quizItem.id === itemAnswer.quizItemId)
if (!quizItem) {
throw new Error("Item was not defined: " + JSON.stringify(quizAnswer))
const allAvailableIds = quiz.items.map((item) => item.id)
const allAnsweredIds = quizAnswer.itemAnswers.map((item) => item.quizItemId)
throw new Error(
"Answer included an answer to an item that was not in the quiz. Answered item ids: " +
allAnsweredIds.join(", ") +
". Available ids: " +
allAvailableIds.join(", "),
)
}
switch (itemAnswer.type) {
case "multiple-choice":
Expand Down
27 changes: 22 additions & 5 deletions services/quizzes/src/pages/iframe.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,21 +79,38 @@ const IFrame: React.FC<React.PropsWithChildren<unknown>> = () => {
"Set-state message data is invalid for the specified answer-exercise view type",
)
}
let public_spec = messageData.data.public_spec
let publicSpec = messageData.data.public_spec
let quiz_answer = messageData.data.previous_submission

if (isOldQuiz(messageData.data.previous_submission as OldQuizAnswer)) {
quiz_answer = migrateQuizAnswer(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(messageData.data.previous_submission as any)?.private_spec as OldQuizAnswer,
public_spec as PublicSpecQuiz,
publicSpec as PublicSpecQuiz,
)
}
if (isOldQuiz(public_spec as OldPublicQuiz)) {
public_spec = migratePublicSpecQuiz(public_spec as OldPublicQuiz)
if (isOldQuiz(publicSpec as OldPublicQuiz)) {
publicSpec = migratePublicSpecQuiz(publicSpec as OldPublicQuiz)
}
// An exercise might be edited after the previous submission and some item answers in the previous submission might be for a quiz item that has been removed from the exercise.
// We'll filter out those answers here so that we don't submit answers to non-existing quiz items.
if (quiz_answer) {
console.log("quiz_answer", quiz_answer)
console.log("publicSpec", publicSpec)
console.log("wat")
quiz_answer = {
...(quiz_answer as UserAnswer),
itemAnswers: (quiz_answer as UserAnswer).itemAnswers.filter((itemAnswer) =>
(publicSpec as PublicSpecQuiz).items.some(
(quizItem) => quizItem.id === itemAnswer.quizItemId,
),
),
} satisfies UserAnswer
}
console.log("quiz_answer after filtering", quiz_answer)
setState({
viewType: messageData.view_type,
publicSpec: public_spec as PublicSpecQuiz,
publicSpec: publicSpec as PublicSpecQuiz,
userInformation: messageData.user_information,
previousSubmission: quiz_answer as UserAnswer | null,
})
Expand Down
9 changes: 9 additions & 0 deletions services/quizzes/src/util/migration/modelSolutionSpecQuiz.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const migrateModelSolutionSpecQuizItem = (
minWords: quizItem.minWords,
order: quizItem.order,
title: quizItem.title,
messageOnModelSolution: null,
} satisfies ModelSolutionQuizItemEssay
case "multiple-choice":
return {
Expand All @@ -52,6 +53,7 @@ const migrateModelSolutionSpecQuizItem = (
sharedOptionFeedbackMessage: quizItem.sharedOptionFeedbackMessage,
options: quizItem.options,
shuffleOptions: quizItem.shuffleOptions,
messageOnModelSolution: null,
} satisfies ModelSolutionQuizItemMultiplechoice
case "scale":
return {
Expand All @@ -66,6 +68,7 @@ const migrateModelSolutionSpecQuizItem = (
minLabel: quizItem.minLabel ? quizItem.minLabel : "?",
maxValue: quizItem.maxValue,
minValue: quizItem.minValue,
messageOnModelSolution: null,
} satisfies ModelSolutionQuizItemScale
case "checkbox":
return {
Expand All @@ -76,6 +79,7 @@ const migrateModelSolutionSpecQuizItem = (
failureMessage: quizItem.failureMessage,
successMessage: quizItem.successMessage,
title: quizItem.title,
messageOnModelSolution: null,
} satisfies ModelSolutionQuizItemCheckbox
case "open":
return {
Expand All @@ -87,6 +91,7 @@ const migrateModelSolutionSpecQuizItem = (
formatRegex: quizItem.formatRegex,
successMessage: quizItem.successMessage,
failureMessage: quizItem.failureMessage,
messageOnModelSolution: null,
} satisfies ModelSolutionQuizItemClosedEndedQuestion
case "matrix":
return {
Expand All @@ -96,6 +101,7 @@ const migrateModelSolutionSpecQuizItem = (
failureMessage: quizItem.failureMessage,
optionCells: quizItem.optionCells,
successMessage: quizItem.successMessage,
messageOnModelSolution: null,
} satisfies ModelSolutionQuizItemMatrix
case "timeline":
return {
Expand All @@ -105,6 +111,7 @@ const migrateModelSolutionSpecQuizItem = (
failureMessage: quizItem.failureMessage,
successMessage: quizItem.successMessage,
timelineItems: quizItem.timelineItems,
messageOnModelSolution: null,
} satisfies ModelSolutionQuizItemTimeline
case "clickable-multiple-choice":
return {
Expand All @@ -117,6 +124,7 @@ const migrateModelSolutionSpecQuizItem = (
successMessage: quizItem.successMessage,
options: quizItem.options,
n: CHOOSE_N_DEFAULT_VALUE,
messageOnModelSolution: null,
} satisfies ModelSolutionQuizItemChooseN
case "multiple-choice-dropdown":
return {
Expand All @@ -128,6 +136,7 @@ const migrateModelSolutionSpecQuizItem = (
failureMessage: quizItem.failureMessage,
successMessage: quizItem.successMessage,
options: quizItem.options,
messageOnModelSolution: null,
} satisfies ModelSolutionQuizItemMultiplechoiceDropdown
}
}
Expand Down
Loading

0 comments on commit c0b4843

Please sign in to comment.