Skip to content

Commit

Permalink
enhance: add manipulation functionality (creation and editing) for ca…
Browse files Browse the repository at this point in the history
…se-study elements (#4481)
  • Loading branch information
sjschlapbach authored Jan 28, 2025
1 parent 9ca7e68 commit 9c65728
Show file tree
Hide file tree
Showing 35 changed files with 2,372 additions and 338 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
GetSingleQuestionDocument,
GetUserQuestionsDocument,
GetUserTagsDocument,
ManipulateCaseStudyQuestionDocument,
ManipulateChoicesQuestionDocument,
ManipulateContentElementDocument,
ManipulateFlashcardElementDocument,
Expand All @@ -29,6 +30,7 @@ import ElementTypeMonitor from './ElementTypeMonitor'
import InstanceUpdateSwitch from './InstanceUpdateSwitch'
import StudentElementPreview from './StudentElementPreview'
import {
prepareCaseStudyArgs,
prepareChoicesArgs,
prepareContentArgs,
prepareFlashcardArgs,
Expand Down Expand Up @@ -119,6 +121,9 @@ function ElementEditModal({
const [manipulateSelectionQuestion] = useMutation(
ManipulateSelectionQuestionDocument
)
const [manipulateCaseStudyQuestion] = useMutation(
ManipulateCaseStudyQuestionDocument
)
const [updateElementInstances] = useMutation(UpdateElementInstancesDocument)

const initialValues = useElementFormInitialValues({
Expand Down Expand Up @@ -224,6 +229,7 @@ function ElementEditModal({

break
}

case ElementType.Numerical: {
const args = prepareNumericalArgs({
elementId,
Expand All @@ -247,6 +253,7 @@ function ElementEditModal({

break
}

case ElementType.FreeText: {
const args = prepareFreeTextArgs({
elementId,
Expand All @@ -270,6 +277,7 @@ function ElementEditModal({

break
}

case ElementType.Selection: {
const args = prepareSelectionArgs({
elementId,
Expand All @@ -294,6 +302,30 @@ function ElementEditModal({
break
}

case ElementType.CaseStudy: {
const args = prepareCaseStudyArgs({
elementId,
isDuplication,
values,
})

const result = await manipulateCaseStudyQuestion({
variables: args,
refetchQueries: [
{ query: GetUserQuestionsDocument },
{ query: GetUserTagsDocument },
],
})

const data = result.data?.manipulateCaseStudyQuestion
if (data?.__typename !== 'CaseStudyElement' || !data.id) {
setFailureToast(true)
return
}

break
}

default:
break
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
ElementFormTypesCaseStudy,
ElementFormTypesChoices,
ElementFormTypesContent,
ElementFormTypesFlashcard,
Expand Down Expand Up @@ -85,6 +86,7 @@ export function prepareChoicesArgs({
}
}),
},

tags: values.tags,
}
}
Expand Down Expand Up @@ -153,6 +155,7 @@ export function prepareNumericalArgs({
})
: undefined,
},

tags: values.tags,
}
}
Expand Down Expand Up @@ -191,6 +194,7 @@ export function prepareFreeTextArgs({
},
solutions: values.options.solutions,
},

tags: values.tags,
}
}
Expand Down Expand Up @@ -222,6 +226,65 @@ export function prepareSelectionArgs({
numberOfInputs: parseInt(values.options.numberOfInputs),
correctAnswers: values.options.correctAnswers,
},

tags: values.tags,
}
}

interface PrepareCaseStudyArgsProps {
elementId?: number
isDuplication: boolean
values: ElementFormTypesCaseStudy
}
export function prepareCaseStudyArgs({
elementId,
isDuplication,
values,
}: PrepareCaseStudyArgsProps) {
return {
id: isDuplication ? undefined : elementId,
name: values.name,
status: values.status,
content: values.content,
explanation:
!values.explanation?.match(/^(<br>(\n)*)$/g) && values.explanation !== ''
? values.explanation
: null,
pointsMultiplier: parseInt(values.pointsMultiplier),

options: {
hasSampleSolution: values.options.hasSampleSolution,
answerCollection: parseInt(values.options.answerCollection),
collectionItemIds: values.options.selectedItems,

criteria: values.options.criteria.map((criterion, index) => ({
id: criterion.id,
name: criterion.name,
order: index,
min: parseFloat(criterion.min),
max: parseFloat(criterion.max),
step: parseFloat(criterion.step),
unit:
criterion.unit && criterion.unit !== '' ? criterion.unit : undefined,
})),

cases: values.options.cases.map((c, index) => ({
title: c.title,
description: c.description,
order: index,
solutions: Object.entries(c.solutions ?? {}).map(([key, value]) => ({
itemId: parseInt(key.split('-')[1]),
criteriaSolutions: Object.entries(value).map(
([criterionId, criterionValue]) => ({
criterionId,
min: parseFloat(criterionValue.min),
max: parseFloat(criterionValue.max),
})
),
})),
})),
},

tags: values.tags,
}
}
31 changes: 21 additions & 10 deletions apps/frontend-manage/src/components/questions/manipulation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,25 @@ export interface ElementFormTypesSelection extends SharedQuestionFormProps {
}
}

// key of top level record is `itemId-${item.id}`, key of nested record is criterion id
export type ElementFormTypesCaseStudySolution = Record<
string,
{ min: string; max: string }
>
export type ElementFormTypesCaseStudySolutions = Record<
string,
ElementFormTypesCaseStudySolution
>

export type ElementFormTypesCaseStudyCriterion = {
id: string // short id
name: string
min: string
max: string
step: string
unit?: string | null
}

export interface ElementFormTypesCaseStudy extends SharedQuestionFormProps {
type: ElementType.CaseStudy
explanation?: string | null
Expand All @@ -85,17 +104,9 @@ export interface ElementFormTypesCaseStudy extends SharedQuestionFormProps {
cases: {
title: string
description: string
// key of top level record is `itemId-${item.id}`, key of nested record is criterion id
solutions?: Record<string, Record<string, { min: string; max: string }>>
}[]
criteria: {
id: string // short id
name: string
min: string
max: string
step: string
unit?: string | null
solutions?: ElementFormTypesCaseStudySolutions
}[]
criteria: ElementFormTypesCaseStudyCriterion[]
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import { nanoid } from 'nanoid'
import { useMemo } from 'react'
import { sort } from 'remeda'
import { ElementEditMode } from './ElementEditModal'
import { ElementFormTypes } from './types'
import {
ElementFormTypes,
ElementFormTypesCaseStudySolution,
ElementFormTypesCaseStudySolutions,
} from './types'

interface UseElementFormInitialValuesProps {
mode: ElementEditMode
Expand Down Expand Up @@ -139,6 +143,54 @@ function useElementFormInitialValues({
correctAnswers: options.answerCollectionSolutionIds ?? undefined,
},
}
} else if (question.__typename === 'CaseStudyElement') {
const options = question.options

return {
...sharedAttributes,
type: ElementType.CaseStudy,
options: {
hasSampleSolution: options.hasSampleSolution ?? false,
answerCollection: options.answerCollection
? String(options.answerCollection.id)
: '',
selectedItems: options.collectionItemIds ?? [],
criteria:
options.criteria?.map((criterion) => ({
...criterion,
min: String(criterion.min),
max: String(criterion.max),
step: String(criterion.step),
})) ?? [],
cases:
options.cases?.map((caseItem) => ({
title: caseItem.title,
description: caseItem.description,
solutions: options.hasSampleSolution
? caseItem.solutions!.reduce<ElementFormTypesCaseStudySolutions>(
(acc, solution) => {
const criteriaSolutions =
solution.criteriaSolutions.reduce<ElementFormTypesCaseStudySolution>(
(acc, sol) => {
acc[sol.criterionId] = {
min: String(sol.min),
max: String(sol.max),
}

return acc
},
{}
)

acc[`itemId-${solution.itemId}`] = criteriaSolutions
return acc
},
{}
)
: undefined,
})) ?? [],
},
}
} else if (question.__typename === 'FlashcardElement') {
return {
...sharedAttributes,
Expand Down
12 changes: 0 additions & 12 deletions cypress/cypress/e2e/K-resources-workflow.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,9 +373,6 @@ describe('Create, edit and share answer collections', function () {
).click()

cy.get('[data-cy="select-answer-collection"]').should('not.exist')
cy.findByText(
'To create selection questions, you need access to at least one answer collection! You can either create one yourself under the "Resources" tab or import an existing collection from other users there.'
)
})

it("Verify that the private answer collection cannot be removed by user 'pro1' as it is used in a question", function () {
Expand Down Expand Up @@ -548,9 +545,6 @@ describe('Create, edit and share answer collections', function () {
`[data-cy="select-question-type-${messages.shared.SELECTION.typeLabel}"]`
).click()
cy.get('[data-cy="select-answer-collection"]').should('not.exist')
cy.findByText(
'To create selection questions, you need access to at least one answer collection! You can either create one yourself under the "Resources" tab or import an existing collection from other users there.'
)
})

it('Grant access to restricted answer collection (for user pro1)', function () {
Expand Down Expand Up @@ -613,9 +607,6 @@ describe('Create, edit and share answer collections', function () {
`[data-cy="select-question-type-${messages.shared.SELECTION.typeLabel}"]`
).click()
cy.get('[data-cy="select-answer-collection"]').should('not.exist')
cy.findByText(
'To create selection questions, you need access to at least one answer collection! You can either create one yourself under the "Resources" tab or import an existing collection from other users there.'
)
})

it('Verify that restricted answer collection can be used in selection question by user pro1 and create question', function () {
Expand Down Expand Up @@ -866,9 +857,6 @@ describe('Create, edit and share answer collections', function () {
`[data-cy="select-question-type-${messages.shared.SELECTION.typeLabel}"]`
).click()
cy.get('[data-cy="select-answer-collection"]').should('not.exist')
cy.findByText(
'To create selection questions, you need access to at least one answer collection! You can either create one yourself under the "Resources" tab or import an existing collection from other users there.'
)
})

it('Import (and copy) the public answer collection (for user pro2)', function () {
Expand Down
3 changes: 3 additions & 0 deletions packages/graphql/src/graphql/ops/MDeleteQuestion.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ mutation DeleteQuestion($id: Int!) {
... on SelectionElement {
id
}
... on CaseStudyElement {
id
}
... on FlashcardElement {
id
}
Expand Down
Loading

0 comments on commit 9c65728

Please sign in to comment.