Skip to content

Commit

Permalink
Longer questions in research consents & regradings by exercise id (#1254
Browse files Browse the repository at this point in the history
)

* Research consent question fixes

* Allow creating a regrading by exercise id

* System test fixes

* System test fixes
  • Loading branch information
nygrenh authored Mar 20, 2024
1 parent f0d8de0 commit df49a6d
Show file tree
Hide file tree
Showing 33 changed files with 353 additions and 127 deletions.
28 changes: 0 additions & 28 deletions services/cms/src/blocks/ResearchConsentCheckbox/index.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,35 @@ import ErrorBanner from "../../shared-module/components/ErrorBanner"
import CheckBox from "../../shared-module/components/InputFields/CheckBox"
import BlockPlaceholderWrapper from "../BlockPlaceholderWrapper"

import { CheckBoxAttributes } from "."
import { ResearchConsentQuestionAttributes } from "."

const ResearchConsentCheckBoxEditor: React.FC<
React.PropsWithChildren<BlockEditProps<CheckBoxAttributes>>
React.PropsWithChildren<BlockEditProps<ResearchConsentQuestionAttributes>>
> = ({ clientId, attributes, isSelected, setAttributes }) => {
const { content } = attributes
const { t } = useTranslation()

return (
<BlockPlaceholderWrapper
id={clientId}
title={t("title-research-form-checkbox")}
title={t("title-research-form-question")}
explanation={t("research-form-checkbox-description")}
>
<div
className={css`
display: flex;
flex-direction: rox;
align-items: baseline;
padding: 1rem;
padding: 1rem 0;
width: 100%;
display: flex;
`}
>
<CheckBox label={" "} checked={isSelected} />

<b>{t("label-question")}: </b>
<RichText
className={css`
width: 100%;
margin-left: 0.25rem;
`}
tagName="span"
label={t("title-research-form-question")}
value={content}
onChange={(value: string) => setAttributes({ content: value })}
/>
Expand Down
29 changes: 29 additions & 0 deletions services/cms/src/blocks/ResearchConsentQuestion/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/* eslint-disable i18next/no-literal-string */
import { BlockConfiguration } from "@wordpress/blocks"
import { formatLtr } from "@wordpress/icons"

import ResearchConsentCheckBoxEditor from "./ResearchConsentQuestionEditor"
import ResearchConsentCheckBoxSave from "./ResearchConsentQuestionSave"

export interface ResearchConsentQuestionAttributes {
content: string
}

const ResearchConsentQuestionConfiguration: BlockConfiguration<ResearchConsentQuestionAttributes> =
{
title: "Research Form Question",
description: "Used to add a new question to the research consent form",
category: "text",
attributes: {
content: {
type: "string",
source: "html",
selector: "span",
},
},
icon: formatLtr,
edit: ResearchConsentCheckBoxEditor,
save: ResearchConsentCheckBoxSave,
}

export default ResearchConsentQuestionConfiguration
4 changes: 2 additions & 2 deletions services/cms/src/blocks/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import LearningObjectives from "./LearningObjectives"
import Map from "./Map"
import PagesInChapter from "./PagesInChapter"
import PartnersBlock from "./Partners"
import ResearchConsentCheckBox from "./ResearchConsentCheckbox"
import ResearchFormQuestion from "./ResearchConsentQuestion"
import TableBox from "./TableBox"
import TopLevelPage from "./TopLevelPage"
import UnsupportedBlock from "./UnsupportedBlock"
Expand Down Expand Up @@ -91,7 +91,7 @@ export const blockTypeMapForTopLevelPages = [
] as Array<[string, BlockConfiguration<Record<string, any>>]>

export const blockTypeMapForResearchConsentForm = [
["moocfi/research-consent-checkbox", ResearchConsentCheckBox],
["moocfi/research-consent-question", ResearchFormQuestion],
] as Array<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[string, BlockConfiguration<Record<string, any>>]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const ResearchFormEditor: React.FC<React.PropsWithChildren<ResearchFormEditorPro
modifyBlocks((data.content ?? []) as BlockInstance[], [
...allowedResearchFormCoreBlocks,
// eslint-disable-next-line i18next/no-literal-string
"moocfi/research-consent-checkbox",
"moocfi/research-consent-question",
]) as BlockInstance[],
)
const courseId = useContext(CourseContext)?.courseId
Expand All @@ -52,7 +52,7 @@ const ResearchFormEditor: React.FC<React.PropsWithChildren<ResearchFormEditorPro
modifyBlocks((data.content ?? []) as BlockInstance[], [
...allowedResearchFormCoreBlocks,
// eslint-disable-next-line i18next/no-literal-string
"moocfi/research-consent-checkbox",
"moocfi/research-consent-question",
]) as BlockInstance[],
)

Expand Down
52 changes: 27 additions & 25 deletions services/cms/src/pages/courses/[id]/research-form-edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import dynamic from "next/dynamic"
import React, { useState } from "react"
import { useTranslation } from "react-i18next"

import { CheckBoxAttributes } from "../../../blocks/ResearchConsentCheckbox"
import { ResearchConsentQuestionAttributes } from "../../../blocks/ResearchConsentQuestion"
import CourseContext from "../../../contexts/CourseContext"
import {
fetchResearchFormWithCourseId,
upsertResearchForm,
upsertResearchFormQuestion,
upsertResearchFormQuestions,
} from "../../../services/backend/courses"
import {
NewResearchForm,
Expand Down Expand Up @@ -80,8 +80,24 @@ const ResearchForms: React.FC<React.PropsWithChildren<ResearchFormProps>> = ({ q
await getResearchForm.refetch()
}
const mutate = useToastMutation(
(form: NewResearchForm) => {
return upsertResearchForm(assertNotNullOrUndefined(courseId), form)
async (form: NewResearchForm) => {
if (!isBlockInstanceArray(form.content)) {
throw new Error("content is not block instance")
}
const researchForm = await upsertResearchForm(assertNotNullOrUndefined(courseId), form)
const questions: NewResearchFormQuestion[] = []
form.content.forEach((block) => {
if (isMoocfiCheckbox(block)) {
const newResearchQuestion: NewResearchFormQuestion = {
question_id: block.clientId,
course_id: researchForm.course_id,
research_consent_form_id: researchForm.id,
question: block.attributes.content,
}
questions.push(newResearchQuestion)
}
upsertResearchFormQuestions(researchForm.id, questions)
})
},
{
notify: true,
Expand All @@ -91,25 +107,9 @@ const ResearchForms: React.FC<React.PropsWithChildren<ResearchFormProps>> = ({ q
},
)
const handleSave = async (form: NewResearchForm): Promise<ResearchForm> => {
const researchForm = await mutate.mutateAsync(form)

if (!isBlockInstanceArray(form.content)) {
throw new Error("content is not block instance")
}
form.content.forEach((block) => {
if (isMoocfiCheckbox(block)) {
const newResearchQuestion: NewResearchFormQuestion = {
question_id: block.clientId,
course_id: researchForm.course_id,
research_consent_form_id: researchForm.id,
question: block.attributes.content,
}
upsertResearchFormQuestion(researchForm.id, newResearchQuestion)
}
})

await getResearchForm.refetch()
return researchForm
await mutate.mutateAsync(form)
const newData = await getResearchForm.refetch()
return newData.data as ResearchForm
}

return (
Expand Down Expand Up @@ -150,8 +150,10 @@ function isBlockInstanceArray(obj: unknown): obj is BlockInstance[] {
return true
}

function isMoocfiCheckbox(obj: BlockInstance): obj is BlockInstance<CheckBoxAttributes> {
return obj.name === "moocfi/research-consent-checkbox"
function isMoocfiCheckbox(
obj: BlockInstance,
): obj is BlockInstance<ResearchConsentQuestionAttributes> {
return obj.name === "moocfi/research-consent-question"
}

const exported = withErrorBoundary(withSignedIn(dontRenderUntilQueryParametersReady(ResearchForms)))
Expand Down
6 changes: 3 additions & 3 deletions services/cms/src/services/backend/courses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,12 @@ export const upsertResearchForm = async (
return validateResponse(response, isResearchForm)
}

export const upsertResearchFormQuestion = async (
export const upsertResearchFormQuestions = async (
courseId: string,
data: NewResearchFormQuestion,
data: NewResearchFormQuestion[],
): Promise<ResearchFormQuestion> => {
const response = await cmsClient.put(
`/courses/${courseId}/research-consent-form-question`,
`/courses/${courseId}/research-consent-form-questions`,
data,
{
responseType: "json",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ import LearningObjectiveBlock from "./moocfi/LearningObjectiveBlock"
import Map from "./moocfi/Map"
import PagesInChapterBlock from "./moocfi/PagesInChapterBlock"
import PartnersBlock from "./moocfi/PartnersBlock"
import ResearchFormCheckBoxBlock from "./moocfi/ResearchFormCheckBoxBlock"
import ResearchConsentQuestionBlock from "./moocfi/ResearchConsentQuestionBlock"
import TableBox from "./moocfi/TableBox"
import TopLevelPageBlock from "./moocfi/TopLevelPagesBlock/index"

Expand Down Expand Up @@ -154,7 +154,7 @@ export const blockToRendererMap: { [blockName: string]: any } = {
"moocfi/map": Map,
"moocfi/author": AuthorBlock,
"moocfi/author-inner-block": AuthorInnerBlock,
"moocfi/research-consent-checkbox": ResearchFormCheckBoxBlock,
"moocfi/research-consent-question": ResearchConsentQuestionBlock,
"moocfi/exercise-custom-view-block": ExerciseCustomViewBlock,
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE course_specific_consent_form_questions ALTER COLUMN question TYPE VARCHAR(255);
UPDATE course_specific_research_consent_forms SET content = REPLACE(content::text, 'moocfi/research-consent-question', 'moocfi/research-consent-checkbox')::jsonb;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ALTER TABLE course_specific_consent_form_questions ALTER COLUMN question TYPE VARCHAR(8192);
UPDATE course_specific_research_consent_forms SET content = REPLACE(content::text, 'moocfi/research-consent-checkbox', 'moocfi/research-consent-question')::jsonb;

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 45 additions & 0 deletions services/headless-lms/models/src/exercise_task_submissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -550,3 +550,48 @@ AND g.exercise_task_id IN (
.await?;
Ok(res)
}

pub async fn get_ids_by_exercise_id(
conn: &mut PgConnection,
exercise_id: Uuid,
) -> ModelResult<Vec<Uuid>> {
let res = sqlx::query!(
"
SELECT id
FROM exercise_task_submissions
WHERE exercise_slide_submission_id IN (
SELECT id
FROM exercise_slide_submissions
WHERE exercise_id = $1
)
",
&exercise_id
)
.fetch_all(conn)
.await?;
Ok(res.iter().map(|x| x.id).collect())
}

/// Similar to get_ids_by_exercise_id but returns the record with the highest created_at for a user_id
pub async fn get_latest_submission_ids_by_exercise_id(
conn: &mut PgConnection,
exercise_id: Uuid,
) -> ModelResult<Vec<Uuid>> {
let res = sqlx::query!(
"
SELECT id
FROM exercise_task_submissions
WHERE exercise_slide_submission_id IN (SELECT id
FROM (SELECT DISTINCT ON (user_id, exercise_id) *
FROM exercise_slide_submissions
WHERE exercise_id = $1
AND deleted_at IS NULL
ORDER BY user_id, exercise_id, created_at DESC) a )
AND deleted_at IS NULL
",
&exercise_id
)
.fetch_all(conn)
.await?;
Ok(res.iter().map(|x| x.id).collect())
}
25 changes: 25 additions & 0 deletions services/headless-lms/models/src/library/regrading.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,31 @@ async fn do_single_regrading(
models::exercise_slides::get_exercise_slide(&mut *conn, submission.exercise_slide_id)
.await?;
let exercise = models::exercises::get_by_id(&mut *conn, exercise_slide.exercise_id).await?;
if exercise.exam_id.is_some() {
info!("Submission being regraded is from an exam, making sure we only give points from the last submission.");
let exercise_slide_submission = models::exercise_slide_submissions::get_by_id(
&mut *conn,
submission.exercise_slide_submission_id,
)
.await?;
let latest_submission =
models::exercise_slide_submissions::get_users_latest_exercise_slide_submission(
&mut *conn,
submission.exercise_slide_id,
exercise_slide_submission.user_id,
)
.await?;
if exercise_slide_submission.id != latest_submission.id {
info!("Exam submission being regraded is not the latest submission, refusing to grade it.");
models::exercise_task_gradings::set_grading_progress(
&mut *conn,
regrading_submission.id,
GradingProgress::Failed,
)
.await?;
continue;
}
}
let not_ready_grading =
models::exercise_task_gradings::new_grading(&mut *conn, &exercise, &submission).await?;
models::exercise_task_regrading_submissions::set_grading_after_regrading(
Expand Down
Loading

0 comments on commit df49a6d

Please sign in to comment.