From d83b635e5e4003a88599695e5353670cf93163ac Mon Sep 17 00:00:00 2001 From: Can Date: Thu, 8 May 2025 13:24:12 +0200 Subject: [PATCH 1/7] add modal and corresponding button for quiz generation --- .../[courseId]/quiz/[quizId]/lecturer.tsx | 9 ++ components/GenerateQuizModal.tsx | 137 ++++++++++++++++++ components/quiz/QuizHeader.tsx | 16 +- 3 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 components/GenerateQuizModal.tsx diff --git a/app/courses/[courseId]/quiz/[quizId]/lecturer.tsx b/app/courses/[courseId]/quiz/[quizId]/lecturer.tsx index 6a3836c1..9ddf9a13 100644 --- a/app/courses/[courseId]/quiz/[quizId]/lecturer.tsx +++ b/app/courses/[courseId]/quiz/[quizId]/lecturer.tsx @@ -3,6 +3,7 @@ import { lecturerEditQuizQuery } from "@/__generated__/lecturerEditQuizQuery.gra import { ErrorContext, ES2022Error } from "@/components/ErrorContext"; import { PageError } from "@/components/PageError"; import { QuizModal } from "@/components/QuizModal"; +import { GenerateQuizModal } from "@/components/GenerateQuizModal"; import { AddQuestionButton } from "@/components/quiz/AddQuestionButton"; import QuestionPreview from "@/components/quiz/QuestionPreview"; import QuizHeader from "@/components/quiz/QuizHeader"; @@ -72,6 +73,7 @@ export default function LecturerQuiz() { }, [courseId, loadQuery, queryReference]); const [isEditSetModalOpen, setEditSetModalOpen] = useState(false); + const [isGenerateSetModalOpen, setGenerateSetModalOpen] = useState(false); const content = contentsByIds[0]; const quiz = content.quiz; @@ -89,6 +91,7 @@ export default function LecturerQuiz() { setEditSetModalOpen(true)} + openGenerateQuizModal={() => setGenerateSetModalOpen(true)} content={content} /> @@ -117,6 +120,12 @@ export default function LecturerQuiz() { _existingQuiz={quiz} chapterId={content.metadata.chapterId} /> + + setGenerateSetModalOpen(false)} + isOpen={isGenerateSetModalOpen} + chapterId={content.metadata.chapterId} + /> ); diff --git a/components/GenerateQuizModal.tsx b/components/GenerateQuizModal.tsx new file mode 100644 index 00000000..8a3a66f8 --- /dev/null +++ b/components/GenerateQuizModal.tsx @@ -0,0 +1,137 @@ +"use client"; +import { QuizModalEditMutation } from "@/__generated__/QuizModalEditMutation.graphql"; +import { QuizModalFragment$key } from "@/__generated__/QuizModalFragment.graphql"; +import { + CreateQuizInput, + QuestionPoolingMode, + QuizModalMutation, +} from "@/__generated__/QuizModalMutation.graphql"; +import { Form, FormSection } from "@/components/Form"; +import { LoadingButton } from "@mui/lab"; +import { + Alert, + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + MenuItem, + Select, + Tab, + Tabs, + TextField, + Typography, +} from "@mui/material"; +import { useState } from "react"; +import { graphql, useFragment, useMutation } from "react-relay"; +import { + AssessmentMetadataFormSection, + AssessmentMetadataPayload, +} from "./AssessmentMetadataFormSection"; +import { + ContentMetadataFormSection, + ContentMetadataPayload, +} from "./ContentMetadataFormSection"; + +interface TabPanelProps { + children?: React.ReactNode; + index: number; + value: number; +} + +function TabPanel(props: TabPanelProps) { + const { children, value, index, ...other } = props; + + return ( + + ); +} + +export function GenerateQuizModal({ + onClose: _onClose, + chapterId, + isOpen, +}: { + onClose: () => void; + isOpen: boolean; + chapterId: string; +}) { + const [tabIndex, setTabIndex] = useState(0); + + const [input, setInput] = useState(); + + const [error, setError] = useState(null); + + function handleSubmit() { + console.log("start query"); + } + + function handleNext() { + if (tabIndex != 2) { + setTabIndex(tabIndex + 1); + } else { + handleSubmit(); + } + } + + return ( + + Generate Quiz + + {error?.source.errors.map((err: any, i: number) => ( + setError(null)}> + {err.message} + + ))} +
+ + setTabIndex(newIndex)} + aria-label="basic tabs example" + > + + + + + + + + + +
+
+ + + + + + + + + +
+ ); +} diff --git a/components/quiz/QuizHeader.tsx b/components/quiz/QuizHeader.tsx index a9f6a233..531cbbc2 100644 --- a/components/quiz/QuizHeader.tsx +++ b/components/quiz/QuizHeader.tsx @@ -1,7 +1,7 @@ import { QuizHeaderDeleteQuizMutation } from "@/__generated__/QuizHeaderDeleteQuizMutation.graphql"; import { QuizHeaderFragment$key } from "@/__generated__/QuizHeaderFragment.graphql"; import { updaterSetDelete } from "@/src/relay-helpers/common"; -import { Delete, Edit } from "@mui/icons-material"; +import { Delete, Edit, AutoAwesome } from "@mui/icons-material"; import { Button, CircularProgress } from "@mui/material"; import { useParams, useRouter } from "next/navigation"; import { useCallback } from "react"; @@ -32,9 +32,14 @@ const metadataFragment = graphql` interface Props { content: QuizHeaderFragment$key; openEditQuizModal: () => void; + openGenerateQuizModal: () => void; } -const QuizHeader = ({ content, openEditQuizModal }: Props) => { +const QuizHeader = ({ + content, + openEditQuizModal, + openGenerateQuizModal, +}: Props) => { const { courseId, quizId } = useParams(); const router = useRouter(); @@ -63,6 +68,13 @@ const QuizHeader = ({ content, openEditQuizModal }: Props) => { title={metadata.name} action={
+ - diff --git a/components/quiz/CapabilitiesTabPanel.tsx b/components/quiz/CapabilitiesTabPanel.tsx new file mode 100644 index 00000000..0f04f702 --- /dev/null +++ b/components/quiz/CapabilitiesTabPanel.tsx @@ -0,0 +1,166 @@ +import { + Button, + Checkbox, + FormControl, + IconButton, + InputLabel, + ListItemText, + MenuItem, + Select, + TextField, +} from "@mui/material"; +import { Form, FormSection } from "../Form"; +import { Add, Delete } from "@mui/icons-material"; +import { useCallback, useEffect, useState } from "react"; +import { SkillType } from "@/__generated__/QuizModalEditMutation.graphql"; +import { CapabilityInput } from "../GenerateQuizModal"; + +export type EducationalObjective = Exclude; + +const skillTypeLabel: Record = { + ANALYZE: "Analyze", + APPLY: "Apply", + REMEMBER: "Remember", + UNDERSTAND: "Understand", + "%future added value": "Unknown", +}; + +export function CapabilitiesTabPanel({ + onChange, + capabilities, +}: { + onChange: (capabilities: CapabilityInput) => void; + capabilities: CapabilityInput; +}) { + const [skillTypes, setSkillTypes] = useState( + capabilities.objectives + ); + const [relationship, setRelationship] = useState( + capabilities.relationship + ); + const [keywords, setKeywords] = useState(capabilities.keywords); + + const updateKeywordAt = useCallback( + (index: number, keywordText: string) => { + setKeywords((oldValue) => + oldValue.map((item, i) => (index === i ? keywordText : item)) + ); + }, + [setKeywords] + ); + + const deleteQuestionAnswerAt = useCallback( + (index: number) => { + setKeywords((oldValue) => oldValue.filter((_, i) => i !== index)); + }, + [setKeywords] + ); + + const addEmptyKeyword = useCallback( + () => setKeywords((oldValue) => [...oldValue, ""]), + [setKeywords] + ); + + useEffect(() => { + onChange({ + objectives: skillTypes, + keywords: keywords, + relationship: relationship, + }); + }, [skillTypes, keywords, relationship, onChange]); + + return ( +
+ + + + Objectives * + + + + + + + +
+ +
+ {keywords.map((keyword, i) => ( +
+ updateKeywordAt(i, e.target.value)} + /> + {i !== 0 && ( + deleteQuestionAnswerAt(i)} + > + + + )} +
+ ))} +
+ + + + Relationship * + + + +
+ ); +} diff --git a/components/quiz/LectureMaterialsTabPanel.tsx b/components/quiz/LectureMaterialsTabPanel.tsx new file mode 100644 index 00000000..e67cb9b2 --- /dev/null +++ b/components/quiz/LectureMaterialsTabPanel.tsx @@ -0,0 +1,106 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; +import { Form, FormSection } from "../Form"; +import { + Alert, + AlertTitle, + Button, + IconButton, + ListItemText, + MenuItem, + Select, +} from "@mui/material"; +import { Add, Delete } from "@mui/icons-material"; +import { GenerateQuizModalMediaQuery$data } from "@/__generated__/GenerateQuizModalMediaQuery.graphql"; + +export function LectureMaterialsTabPanel({ + mediaRecords, + materialIds, + onChange, +}: { + mediaRecords: GenerateQuizModalMediaQuery$data["mediaRecordsForCourses"][0]; + materialIds: string[]; + onChange: (materialIds: string[]) => void; +}) { + const [selectedMediaIds, setSelectedMediaIds] = + useState(materialIds); + const selectableMedia = useMemo(() => { + return mediaRecords.filter((media) => !selectedMediaIds.includes(media.id)); + }, [mediaRecords, selectedMediaIds]); + + const noMediaToPick = useMemo(() => { + mediaRecords.filter((item) => !item.id || !item.name || !item.type); + return mediaRecords.length === 0; + }, [mediaRecords]); + + const addMaterial = useCallback( + () => setSelectedMediaIds((oldValue) => [...oldValue, ""]), + [setSelectedMediaIds] + ); + + const updateMaterialAt = useCallback( + (index: number, materialID: string) => { + setSelectedMediaIds((oldValue) => + oldValue.map((item, i) => (index === i ? materialID : item)) + ); + }, + [setSelectedMediaIds] + ); + + const deleteMaterialAt = useCallback( + (index: number) => { + setSelectedMediaIds((oldValue) => oldValue.filter((_, i) => i !== index)); + }, + [setSelectedMediaIds] + ); + + useEffect(() => { + onChange(selectedMediaIds); + }); + + return ( +
+ +
+ +
+ {noMediaToPick ? ( + selectedMediaIds.map((media, i) => ( +
+ + {i !== 0 && ( + deleteMaterialAt(i)}> + + + )} +
+ )) + ) : ( + + No Media Available + Please upload Media like Lecture Recordings or Slides in this Course + before generating questions based on these + + )} +
+
+ ); +} diff --git a/components/quiz/QuestionsTabPanel.tsx b/components/quiz/QuestionsTabPanel.tsx new file mode 100644 index 00000000..33b90ab2 --- /dev/null +++ b/components/quiz/QuestionsTabPanel.tsx @@ -0,0 +1,75 @@ +import { FormControl, TextField } from "@mui/material"; +import { Form, FormSection } from "../Form"; +import { useEffect, useState } from "react"; + +type GenerateQuestionsInput = { + multipleChoiceAmount: number; + clozeAmount: number; + associationAmount: number; +}; + +export function QuestionsTabPanel({ + questionAmounts, + onChange, +}: { + questionAmounts: GenerateQuestionsInput; + onChange: (amounts: GenerateQuestionsInput) => void; +}) { + const [input, setInput] = useState(questionAmounts); + + useEffect(() => { + onChange(input); + }); + + return ( +
+ + + + setInput({ ...input, multipleChoiceAmount: Number(value) }) + } + type="number" + label="Amount to Generate" + required + sx={{ width: "225px" }} + /> + + + + + + + setInput({ ...input, clozeAmount: Number(value) }) + } + type="number" + label="Amount to Generate" + required + sx={{ width: "225px" }} + /> + + + + + + + setInput({ ...input, associationAmount: Number(value) }) + } + type="number" + label="Amount to Generate" + required + sx={{ width: "225px" }} + /> + + +
+ ); +} From 89e169b7e4c54b141f1e589ea088db3160bf0502 Mon Sep 17 00:00:00 2001 From: Can Date: Tue, 27 May 2025 17:10:44 +0200 Subject: [PATCH 4/7] fixed mediaRecords query bug --- components/GenerateQuizModal.tsx | 36 ++++++++++++-------- components/quiz/LectureMaterialsTabPanel.tsx | 31 ++++++++++------- 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/components/GenerateQuizModal.tsx b/components/GenerateQuizModal.tsx index 08454b03..b00ad96f 100644 --- a/components/GenerateQuizModal.tsx +++ b/components/GenerateQuizModal.tsx @@ -93,7 +93,8 @@ export function GenerateQuizModal({ capabilities.relationship !== "" && capabilities.keywords.every((kw) => kw.trim() !== ""); - const validMaterials = materialIds.length !== 0; + const validMaterials = + materialIds.length !== 0 && materialIds.every((m) => m !== ""); const validQuestionAmount = Object.values(questionAmount).some( (amount) => amount !== 0 ); @@ -106,24 +107,25 @@ export function GenerateQuizModal({ const data = useLazyLoadQuery( graphql` - query GenerateQuizModalMediaQuery($courseId: UUID!) { - mediaRecordsForCourses(courseIds: [$courseId]) { - ... on MediaRecord { - __typename - id - name - type - } + query GenerateQuizModalMediaQuery { + mediaRecords { + id + name + type + courseIds } } `, { courseId } ); - const mediaRecords = data.mediaRecordsForCourses.flat(); + const mediaRecords = data.mediaRecords.filter((item) => { + return item.courseIds.includes(courseId); + }); function handleSubmit() { console.log("start query"); + _onClose(); } function handleNext() { @@ -136,7 +138,7 @@ export function GenerateQuizModal({ } return ( - + Generate Quiz {error?.source.errors.map((err: any, i: number) => ( @@ -177,15 +179,21 @@ export function GenerateQuizModal({ - + - diff --git a/components/quiz/LectureMaterialsTabPanel.tsx b/components/quiz/LectureMaterialsTabPanel.tsx index e67cb9b2..8da14ecd 100644 --- a/components/quiz/LectureMaterialsTabPanel.tsx +++ b/components/quiz/LectureMaterialsTabPanel.tsx @@ -5,7 +5,6 @@ import { AlertTitle, Button, IconButton, - ListItemText, MenuItem, Select, } from "@mui/material"; @@ -17,19 +16,18 @@ export function LectureMaterialsTabPanel({ materialIds, onChange, }: { - mediaRecords: GenerateQuizModalMediaQuery$data["mediaRecordsForCourses"][0]; + mediaRecords: GenerateQuizModalMediaQuery$data["mediaRecords"]; materialIds: string[]; onChange: (materialIds: string[]) => void; }) { const [selectedMediaIds, setSelectedMediaIds] = useState(materialIds); - const selectableMedia = useMemo(() => { - return mediaRecords.filter((media) => !selectedMediaIds.includes(media.id)); - }, [mediaRecords, selectedMediaIds]); const noMediaToPick = useMemo(() => { - mediaRecords.filter((item) => !item.id || !item.name || !item.type); - return mediaRecords.length === 0; + const test = mediaRecords.filter((item) => { + return !!item.id && !!item.name; + }); + return test.length === 0; }, [mediaRecords]); const addMaterial = useCallback( @@ -69,20 +67,27 @@ export function LectureMaterialsTabPanel({ Add Material
- {noMediaToPick ? ( + {!noMediaToPick ? ( selectedMediaIds.map((media, i) => ( -
+
From 71a01b8d1f6b87fc6ebc44985d42bc073089b0d7 Mon Sep 17 00:00:00 2001 From: Can Date: Mon, 2 Jun 2025 16:31:53 +0200 Subject: [PATCH 5/7] Added graphql mutation to generate questions --- .../[courseId]/quiz/[quizId]/lecturer.tsx | 1 + components/GenerateQuizModal.tsx | 73 +- src/schema.graphql | 914 +++++++++++++++--- 3 files changed, 853 insertions(+), 135 deletions(-) diff --git a/app/courses/[courseId]/quiz/[quizId]/lecturer.tsx b/app/courses/[courseId]/quiz/[quizId]/lecturer.tsx index ed39c400..9277c3bd 100644 --- a/app/courses/[courseId]/quiz/[quizId]/lecturer.tsx +++ b/app/courses/[courseId]/quiz/[quizId]/lecturer.tsx @@ -125,6 +125,7 @@ export default function LecturerQuiz() { onClose={() => setGenerateSetModalOpen(false)} isOpen={isGenerateSetModalOpen} courseId={courseId} + quizId={quizId} /> diff --git a/components/GenerateQuizModal.tsx b/components/GenerateQuizModal.tsx index b00ad96f..1d75c49f 100644 --- a/components/GenerateQuizModal.tsx +++ b/components/GenerateQuizModal.tsx @@ -12,7 +12,7 @@ import { Typography, } from "@mui/material"; import { useMemo, useState } from "react"; -import { graphql, useLazyLoadQuery } from "react-relay"; +import { graphql, useLazyLoadQuery, useMutation } from "react-relay"; import { CapabilitiesTabPanel, EducationalObjective, @@ -20,6 +20,10 @@ import { import { LectureMaterialsTabPanel } from "./quiz/LectureMaterialsTabPanel"; import { GenerateQuizModalMediaQuery } from "@/__generated__/GenerateQuizModalMediaQuery.graphql"; import { QuestionsTabPanel } from "./quiz/QuestionsTabPanel"; +import { + AiGenQuestionContext, + GenerateQuizModalMutation, +} from "@/__generated__/GenerateQuizModalMutation.graphql"; interface TabPanelProps { children?: React.ReactNode; @@ -59,14 +63,22 @@ const defaultCapability = { relationship: "", }; +const defaultQuestionAmount = { + multipleChoiceAmount: 0, + clozeAmount: 0, + associationAmount: 0, +}; + export function GenerateQuizModal({ onClose: _onClose, courseId, isOpen, + quizId, }: { onClose: () => void; isOpen: boolean; courseId: string; + quizId: string; }) { const [tabIndex, setTabIndex] = useState(0); @@ -79,11 +91,7 @@ export function GenerateQuizModal({ multipleChoiceAmount: number; clozeAmount: number; associationAmount: number; - }>({ - multipleChoiceAmount: 0, - clozeAmount: 0, - associationAmount: 0, - }); + }>(defaultQuestionAmount); const [error, setError] = useState(null); @@ -123,8 +131,59 @@ export function GenerateQuizModal({ return item.courseIds.includes(courseId); }); + const [generate] = useMutation(graphql` + mutation GenerateQuizModalMutation( + $context: AiGenQuestionContext! + $assessmentId: UUID! + ) { + mutateQuiz(assessmentId: $assessmentId) { + aiGenerateQuestionAsync(context: $context) { + assessmentId + } + } + } + `); + function handleSubmit() { - console.log("start query"); + const sumOfQuesitonAmount = Object.values(questionAmount).reduce( + (acc, val) => acc + val, + 0 + ); + const context: AiGenQuestionContext = { + description: + "Use the following keywords as context to generate the questions:\n" + + capabilities.keywords.join(", "), + allowMultipleCorrectAnswers: false, + maxAnswersPerQuestion: 5, + maxExactQuestions: 0, + maxFreeTextQuestions: 0, + maxMultipleChoiceQuestions: questionAmount.multipleChoiceAmount, + maxNumericQuestions: 0, + maxQuestions: sumOfQuesitonAmount, + mediaRecordIds: materialIds, + minQuestions: sumOfQuesitonAmount, + quizId: quizId, + }; + generate({ + variables: { context, assessmentId: quizId }, + onError: setError, + + onCompleted() { + alert( + "Generation of questions was started successfully!" + + "\n Please come back later to review the generated questions!" + ); + closeModal(); + }, + }); + } + + function closeModal() { + setCapabilities(defaultCapability); + setMaterialIds([]); + setQuestionAmount(defaultQuestionAmount); + setTabIndex(0); + setError(null); _onClose(); } diff --git a/src/schema.graphql b/src/schema.graphql index c2d5f239..067d58ca 100644 --- a/src/schema.graphql +++ b/src/schema.graphql @@ -35,6 +35,7 @@ directive @Size(min: Int = 0, max: Int = 2147483647, message: String = "graphql. directive @ContainerSize(min: Int = 0, max: Int = 2147483647, message: String = "graphql.validation.ContainerSize.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION +# # The @OnDemand directive is used to mark fields that are only internally resolved when requested. # Implementation Note: This will cause the code generator to omit the field from the generated DTOs. directive @OnDemand on FIELD_DEFINITION @@ -42,6 +43,18 @@ directive @OnDemand on FIELD_DEFINITION # Indicates an Input Object is a OneOf Input Object. directive @oneOf on INPUT_OBJECT +# This directive allows results to be deferred during execution +directive @defer( + # Deferred behaviour is controlled by this argument + if: Boolean! = true + + # A unique label that represents the fragment being deferred + label: String +) on FRAGMENT_SPREAD | INLINE_FRAGMENT + +# This directive disables error propagation when a non nullable field returns null for the given operation. +directive @experimental_disableErrorPropagation on QUERY | MUTATION | SUBSCRIPTION + directive @resolveTo(requiredSelectionSet: String, sourceName: String!, sourceTypeName: String!, sourceFieldName: String!, sourceSelectionSet: String, sourceArgs: ResolveToSourceArgs, keyField: String, keysArg: String, pubsubTopic: String, filterBy: String, additionalArgs: ResolveToSourceArgs, result: String, resultType: String) on FIELD_DEFINITION type AiEntityProcessingProgress { @@ -57,25 +70,78 @@ enum AiEntityProcessingState { DONE } +input AiGenQuestionContext { + # + # The question is used to fine tune the prompt context + description: String! + + # + # the media records that are used to give knowledge to the AI about the topic of the question. + mediaRecordIds: [UUID]! + + # + # The id of the quiz to which the question will be added. + quizId: UUID! + + # + # The maximum number of questions that can be generated. + maxQuestions: Int! = 5 + + # + # The minimum number of questions that must be generated. + minQuestions: Int! = 5 + + # + # The maximum number of answers per question if is a multiple choice question. + maxAnswersPerQuestion: Int! = 5 + + # + # The maximum number of multiple choice questions that can be generated. + maxMultipleChoiceQuestions: Int! = 5 + + # + # The maximum number of free text questions that can be generated. + maxFreeTextQuestions: Int! = 5 + + # + # The maximum number of numeric questions that can be generated. + maxNumericQuestions: Int! = 5 + + # + # The maximum number of exact answer questions that can be generated. + maxExactQuestions: Int! = 5 + + # + # Whether multiple choice questions with multiple correct answers are allowed. + allowMultipleCorrectAnswers: Boolean! = false +} + interface Assessment { + # # Assessment metadata assessmentMetadata: AssessmentMetadata! + # # ID of the content id: UUID! + # # Metadata of the content metadata: ContentMetadata! + # # Progress data of the content for the current user. userProgressData: UserProgressData! + # # Progress data of the specified user. progressDataForUser(userId: UUID!): UserProgressData! + # # the items that belong to the Assessment items: [Item!]! + # # For the current user, returns true if this content could be worked on by the user (i.e. it is not locked), false # if content is not available to be worked on (e.g. because previous stage has not been completed) isAvailableToBeWorkedOn: Boolean! @@ -86,12 +152,15 @@ type AssessmentContentReference { } type AssessmentMetadata { + # # Number of skill points a student receives for completing this content skillPoints: Int! + # # Type of the assessment skillTypes: [SkillType!]! + # # The initial learning interval for the assessment in days. # This is the interval that is applied after the assessment is completed the first time. # Following intervals are calculated based on the previous interval and the user's performance. @@ -101,12 +170,15 @@ type AssessmentMetadata { } input AssessmentMetadataInput { + # # Number of skill points a student receives for completing this content skillPoints: Int! + # # Type of the assessment skillTypes: [SkillType!]! + # # The initial learning interval for the assessment in days. # This is the interval that is applied after the assessment is completed the first time. # Following intervals are calculated based on the previous interval and the user's performance. @@ -116,9 +188,11 @@ input AssessmentMetadataInput { } type AssessmentSemanticSearchResult implements SemanticSearchResult { + # # The similarity score of the search result. score: Float! + # # ID of the assessment this search result is referencing. assessmentId: UUID! @@ -127,48 +201,62 @@ type AssessmentSemanticSearchResult implements SemanticSearchResult { } input AssociationInput { + # # id of the corresponding item itemId: UUID + # # Text of the left side of the association, in SlateJS JSON format. left: String! + # # Text of the right side of the association, in SlateJS JSON format. right: String! + # # Feedback for the association when the user selects a wrong answer, in SlateJS JSON format. feedback: JSON } +# # Association question, i.e., a question where the user has to assign the correct right side to each left side. type AssociationQuestion implements Question { + # # Text to display above the association question, in SlateJS JSON format. text: JSON! + # # List of correct associations. correctAssociations: [SingleAssociation!]! + # # Computed list of all the left sides of the associations, shuffled. leftSide: [String!]! + # # Computed list of all the right sides of the associations, shuffled. rightSide: [String!]! + # # Unique identifier of the question and the id of the corresponding item itemId: UUID! + # # Number of the question, i.e., the position of the question in the list of questions. # Only relevant if questionPoolingMode is ORDERED. number: Int! + # # Type of the question. type: QuestionType! + # # Optional hint for the question, in SlateJS JSON format. hint: JSON item: Item! } +# # Level of Blooms Taxonomy enum BloomLevel { REMEMBER @@ -179,34 +267,44 @@ enum BloomLevel { CREATE } +# # A chapter is a part of a course. type Chapter { + # # UUID of the chapter, generated automatically id: UUID! + # # Title of the chapter, maximum length is 255 characters. title: String! + # # Description of the chapter, maximum length is 3000 characters. description: String! + # # Number of the chapter, determines the order of the chapters. number: Int! + # # Start date of the chapter, ISO 8601 format. startDate: DateTime! + # # End date of the chapter, ISO 8601 format. endDate: DateTime! + # # Suggested Start date to start the chapter, ISO 8601 format. # Must be after Start Date and before the End dates. suggestedStartDate: DateTime + # # Suggested End date of the chapter, ISO 8601 format. # Must be after the Start Dates and before the End dates. suggestedEndDate: DateTime + # # The course the chapter belongs to. course: Course! @@ -241,6 +339,7 @@ input ChapterFilter { not: ChapterFilter } +# # Return type of the chapters query, contains a list of chapters and pagination info. type ChapterPayload { elements: [Chapter!]! @@ -248,9 +347,11 @@ type ChapterPayload { } type ClozeBlankElement { + # # The correct answer for the blank. correctAnswer: String! + # # Feedback for the blank when the user selects a wrong answer, in SlateJS JSON format. feedback: JSON } @@ -258,15 +359,19 @@ type ClozeBlankElement { union ClozeElement = ClozeTextElement | ClozeBlankElement input ClozeElementInput { + # # Type of the element. type: ClozeElementType! + # # Text of the element. Only used for TEXT type. text: JSON + # # The correct answer for the blank. Only used for BLANK type. correctAnswer: String + # # Feedback for the blank when the user selects a wrong answer, in SlateJS JSON format. Only used for BLANK type. feedback: JSON } @@ -277,86 +382,110 @@ enum ClozeElementType { } type ClozeQuestion implements Question { + # # The elements of the cloze question. clozeElements: [ClozeElement!]! + # # Addtional wrong answers for the blanks. additionalWrongAnswers: [String!]! + # # All selectable answers for the blanks (computed). This contains the correct answers as well as wrong answers. allBlanks: [String!]! + # # Whether the blanks must be answered in free text or by selecting the correct answer from a list. showBlanksList: Boolean! + # # Unique identifier of the question and the id of the corresponding item itemId: UUID! + # # Number of the question, i.e., the position of the question in the list of questions. # Only relevant if questionPoolingMode is ORDERED. number: Int! + # # Type of the question. type: QuestionType! + # # Optional hint for the question, in SlateJS JSON format. hint: JSON item: Item! } type ClozeTextElement { + # # Text of the element, in SlateJS JSON format. text: JSON! } type CompositeProgressInformation { + # # percentage of completedContents/totalContents progress: Float! + # # absolut number of completed content completedContents: Int! + # # absolut number of total content totalContents: Int! } interface Content { + # # ID of the content id: UUID! + # # Metadata of the content metadata: ContentMetadata! + # # Progress data of the content for the current user. userProgressData: UserProgressData! + # # Progress data of the specified user. progressDataForUser(userId: UUID!): UserProgressData! + # # For the current user, returns true if this content could be worked on by the user (i.e. it is not locked), false # if content is not available to be worked on (e.g. because previous stage has not been completed) isAvailableToBeWorkedOn: Boolean! } type ContentMetadata { + # # Name of the content name: String! + # # Content type type: ContentType! + # # Suggested date when the content should be done suggestedDate: DateTime! + # # Number of reward points a student receives for completing this content rewardPoints: Int! + # # ID of the chapter this content is associated with chapterId: UUID! + # # ID of the course this content is associated with courseId: UUID! + # # TagNames this content is tagged with tagNames: [String!]! @@ -368,33 +497,42 @@ type ContentMetadata { } type ContentMutation { + # # Identifier of Content contentId: UUID! + # # Update an existing Content updateMediaContent(input: UpdateMediaContentInput!): MediaContent! + # # Update an existing Assessment updateAssessment(input: UpdateAssessmentInput!): Assessment! + # # Delete an existing Content, throws an error if no Content with the given id exists deleteContent: UUID! + # # Add a tag to an existing content addTagToContent(tagName: String): Content! + # # Remove a tag from an existing content removeTagFromContent(tagName: String): Content! } type ContentPayload { + # # the contents elements: [Content!]! + # # pagination info pageInfo: PaginationInfo! } +# # Type of the content enum ContentType { MEDIA @@ -402,51 +540,64 @@ enum ContentType { QUIZ } +# # Courses are the main entity of the application. They are the top level of the # hierarchy and contain chapters. type Course { + # # UUID of the course. Generated automatically when creating a new course. id: UUID! + # # Title of the course. Maximal length is 255 characters, must not be blank. title: String! + # # Detailed description of the course. Maximal length is 3000 characters. description: String! + # # Start date of the course, ISO 8601 format. # Users can only access the course and work on course content after the start date. # Must be before the end date. startDate: DateTime! + # # End date of the course, ISO 8601 format. # Users can no longer access the course and work on course content after the end date. # Must be after the start date. endDate: DateTime! + # # Published state of the course. If the course is published, it is visible to users. published: Boolean! + # # The year in which the term starts. startYear: Int + # # The division of the academic calendar in which the term takes place. yearDivision: YearDivision + # # Chapters of the course. Can be filtered and sorted. # 🔒 User needs to be enrolled in the course to access this field. chapters( filter: ChapterFilter + # # The fields to sort by. The default sort order is by chapter number. # Throws an error if no field with the given name exists. sortBy: [String!]! = [] + # # The sort direction for each field. If not specified, defaults to ASC. sortDirection: [SortDirection!]! = [ASC] pagination: Pagination ): ChapterPayload! + # # Course Memberships of this course. Contains information about which users are members of the course and what # role they have in it. # 🔒 User needs to be at least an admin of the course to access this field. @@ -478,6 +629,7 @@ type Course { skills: [Skill!]! } +# # Input type for filtering courses. All fields are optional. # If multiple filters are specified, they are combined with AND (except for the or field). input CourseFilter { @@ -491,18 +643,23 @@ input CourseFilter { not: CourseFilter } +# # Represents a course membership object of a user. Each user can be a member of # set of courses and some users can also own courses type CourseMembership { + # # Id of the user. userId: UUID! + # # Id of the course the user is a member of. courseId: UUID! + # # The role of the user in the course. role: UserRoleInCourse! + # # Course of the Course Membership course: Course! @@ -510,18 +667,23 @@ type CourseMembership { user: PublicUserInfo } +# # Represents a course membership input object of a user. input CourseMembershipInput { + # # Id of the user. userId: UUID! + # # Id of the course the user is a member of. courseId: UUID! + # # The role of the user in the course. role: UserRoleInCourse! } +# # Return type for the course query. Contains the course and the pagination info. type CoursePayload { elements: [Course!]! @@ -529,41 +691,52 @@ type CoursePayload { } input CreateAssessmentInput { + # # Metadata for the new Content metadata: CreateContentMetadataInput! + # # Assessment metadata assessmentMetadata: AssessmentMetadataInput! + # # items of the new assessments items: [CreateItemInput!] } input CreateAssociationInput { + # # Text of the left side of the association, in SlateJS JSON format. left: String! + # # Text of the right side of the association, in SlateJS JSON format. right: String! + # # Feedback for the association when the user selects a wrong answer, in SlateJS JSON format. feedback: JSON } input CreateAssociationQuestionInput { + # # id of the corresponding item itemId: UUID! + # # Number of the question, used for ordering. # This can be omitted, in which case a number, one higher than the highest number of the existing questions, will be used. number: Int + # # Text of the question, in SlateJS JSON format. text: JSON! + # # List of associations. correctAssociations: [AssociationInput!]! + # # Optional hint for the question, in SlateJS JSON format. hint: JSON } @@ -575,69 +748,88 @@ input CreateAssociationQuestionInputWithoutItem { hint: JSON } +# # Input type for creating chapters. input CreateChapterInput { + # # Title of the chapter, maximum length is 255 characters, must not be blank. title: String! + # # Description of the chapter, maximum length is 3000 characters. description: String! + # # Number of the chapter, determines the order of the chapters, must be positive. number: Int! + # # Start date of the chapter, ISO 8601 format. # Must be before the end date. startDate: DateTime! + # # End date of the chapter, ISO 8601 format. # Must be after the start date. endDate: DateTime! + # # Suggested Start date to start the chapter, ISO 8601 format. # Must be after Start Date and before the End dates. suggestedStartDate: DateTime + # # Suggested End date of the chapter, ISO 8601 format. # Must be after the Start Dates and before the End dates. suggestedEndDate: DateTime + # # ID of the course the chapter belongs to. # Must be a UUID of an existing course. courseId: UUID! } input CreateClozeElementInput { + # # Type of the element. type: ClozeElementType! + # # Text of the element. Only used for TEXT type. text: JSON + # # The correct answer for the blank. Only used for BLANK type. correctAnswer: String + # # Feedback for the blank when the user selects a wrong answer, in SlateJS JSON format. Only used for BLANK type. feedback: JSON } input CreateClozeQuestionInput { + # # id of the corresponding item itemId: UUID! + # # Number of the question, used for ordering. # This can be omitted, in which case a number, one higher than the highest number of the existing questions, will be used. number: Int + # # List of cloze elements. clozeElements: [ClozeElementInput!]! + # # List of additional wrong answers. additionalWrongAnswers: [String!]! = [] + # # If true, the list of possible answers will be shown to the user. showBlanksList: Boolean! = true + # # Optional hint for the question, in SlateJS JSON format. hint: JSON } @@ -651,71 +843,92 @@ input CreateClozeQuestionInputWithoutItem { } input CreateContentMetadataInput { + # # Name of the content name: String! + # # Type of the content type: ContentType! + # # Suggested date when the content should be done suggestedDate: DateTime! + # # Number of reward points a student receives for completing this content rewardPoints: Int! + # # ID of the chapter this content is associated with chapterId: UUID! + # # TagNames this content is tagged with tagNames: [String!]! = [] } +# # Input type for creating a new course. See also on the course type for detailed field descriptions. input CreateCourseInput { + # # Title of the course, max 255 characters, must not be blank. title: String! + # # Description of the course, max 3000 characters. description: String! + # # Start date of the course, ISO 8601 format. # Must be before the end date. startDate: DateTime! + # # End date of the course, ISO 8601 format. # Must be after the start date. endDate: DateTime! + # # Published status of the course. published: Boolean! + # # The year in which the term starts. startYear: Int + # # The division of the academic calendar in which the term takes place. yearDivision: YearDivision } input CreateExactAnswerQuestionInput { + # # id of the corresponding item itemId: UUID + # # Number of the question, used for ordering. # This can be omitted, in which case a number, one higher than the highest number of the existing questions, will be used. number: Int + # # Text of the question, in SlateJS JSON format. text: JSON! + # # If the answer is case sensitive. If true, the answer is checked case sensitive. caseSensitive: Boolean! = false + # # A list of possible correct answers. correctAnswers: [String!]! + # # Feedback for the question when the user enters a wrong answer, in SlateJS JSON format. feedback: JSON + # # Optional hint for the question, in SlateJS JSON format. hint: JSON } @@ -730,9 +943,11 @@ input CreateExactAnswerQuestionInputWithoutItem { } input CreateFlashcardInput { + # # id of the item the flashcard belongs to itemId: UUID + # # List of sides of this flashcard. Must be at least two sides. sides: [FlashcardSideInput!]! } @@ -745,6 +960,7 @@ input CreateFlashcardInputWithoutItem { } input CreateFlashcardSetInput { + # # List of flashcards in this set. flashcards: [CreateFlashcardInput!]! } @@ -754,37 +970,47 @@ input CreateItemInput { associatedBloomLevels: [BloomLevel!]! } +# # Input for creating new media content. Media specific fields are stored in the Media Service. input CreateMediaContentInput { + # # Metadata for the new Content metadata: CreateContentMetadataInput! } input CreateMediaRecordInput { + # # Name of the media record. Cannot be blank, maximum length 255 characters. name: String! + # # Type of the media record. type: MediaType! + # # IDs of the MediaContents this media record is associated with contentIds: [UUID!]! } input CreateMultipleChoiceQuestionInput { + # # UUID of the question to update and the id of the corresponding item. itemId: UUID! + # # Number of the question, used for ordering. # This can be omitted, in which case a number, one higher than the highest number of the existing questions, will be used. number: Int + # # Text of the question, in SlateJS JSON format. text: JSON! + # # List of answers. answers: [MultipleChoiceAnswerInput!]! + # # Optional hint for the question, in SlateJS JSON format. hint: JSON } @@ -797,25 +1023,32 @@ input CreateMultipleChoiceQuestionInputWithoutItem { } input CreateNumericQuestionInput { + # # id of the corresponding item itemId: UUID! + # # Number of the question, used for ordering. # This can be omitted, in which case a number, one higher than the highest number of the existing questions, will be used. number: Int + # # Text of the question, in SlateJS JSON format. text: JSON! + # # The correct answer for the question. correctAnswer: Float! + # # The allowed deviation from the correct answer. tolerance: Float! + # # Feedback for the question when the user enters a wrong answer, in SlateJS JSON format. feedback: JSON + # # Optional hint for the question, in SlateJS JSON format. hint: JSON } @@ -830,47 +1063,57 @@ input CreateNumericQuestionInputWithoutItem { } input CreateQuizInput { + # # Threshold of the quiz, i.e., how many questions the user has to answer correctly to pass the quiz. - # + # # If this is greater than the number of questions, the behavior is the same # as if it was equal to the number of questions. requiredCorrectAnswers: Int! + # # Question pooling mode of the quiz. questionPoolingMode: QuestionPoolingMode! + # # Number of questions that are randomly selected from the list of questions. # Should only be set if questionPoolingMode is RANDOM. - # + # # If this is greater than the number of questions, the behavior is the same # as if it was equal to the number of questions. - # + # # If this is null or not set, the behavior is the same as if it was equal to the number of questions. numberOfRandomlySelectedQuestions: Int } input CreateSectionInput { + # # Chapter Section will belong to chapterId: UUID! + # # name given to Section name: String! } input CreateSelfAssessmentQuestionInput { + # # id of the corresponding item itemId: UUID! + # # Number of the question, used for ordering. # This can be omitted, in which case a number, one higher than the highest number of the existing questions, will be used. number: Int + # # Text of the question, in SlateJS JSON format. text: JSON! + # # A possible correct answer to the question. solutionSuggestion: JSON! + # # Optional hint for the question, in SlateJS JSON format. hint: JSON } @@ -889,9 +1132,11 @@ input CreateSkillInput { } input CreateStageInput { + # # updated List of UUIDs for content labeled as required in this Stage requiredContents: [UUID!]! + # # updated List of UUIDs for content labeled as optional in this Stage optionalContents: [UUID!]! } @@ -902,32 +1147,41 @@ scalar Date # A slightly refined version of RFC-3339 compliant DateTime Scalar scalar DateTime +# # Filter for date values. # If multiple filters are specified, they are combined with AND. input DateTimeFilter { + # # If specified, filters for dates after the specified value. after: DateTime + # # If specified, filters for dates before the specified value. before: DateTime } type DocumentRecordSegment implements MediaRecordSegment { + # # UUID of this segment. id: UUID! + # # UUID of the media record this search result references. mediaRecordId: UUID! + # # Page of the document this search result references. page: Int! + # # The text snippet of the document this search result references. text: String! + # # Base64-encoded image thumbnail for this segment. thumbnail: String! + # # Title of this segment. title: String @@ -935,61 +1189,78 @@ type DocumentRecordSegment implements MediaRecordSegment { mediaRecord: MediaRecord! } +# # A question with a clear, correct answer that can be automatically checked. # Differs from self-assessment questions in that the user has to enter one of the correct answers and # the answer is checked automatically. type ExactAnswerQuestion implements Question { + # # Text of the question, in SlateJS JSON format. text: JSON! + # # A list of possible correct answers. The user has to enter one of these answers. correctAnswers: [String!]! + # # If the answer is case sensitive. If true, the answer is checked case sensitive. caseSensitive: Boolean! + # # Feedback for the question when the user enters a wrong answer, in SlateJS JSON format. feedback: JSON + # # Unique identifier of the question and the id of the corresponding item itemId: UUID! + # # Number of the question, i.e., the position of the question in the list of questions. # Only relevant if questionPoolingMode is ORDERED. number: Int! + # # Type of the question. type: QuestionType! + # # Optional hint for the question, in SlateJS JSON format. hint: JSON item: Item! } +# # A flashcard is a set of two or more sides. Each side has a label and a text. # The label is used to specify which side of the flashcard is being shown to the user first for learning # and which sides he has to guess. type Flashcard { + # # Unique identifier of this flashcard, which is the id of the corresponding item itemId: UUID! + # # List of sides of this flashcard. sides: [FlashcardSide!]! + # # Progress data of the flashcard, specific to given users. # If userId is not provided, the progress data of the current user is returned. userProgressData: FlashcardProgressData! item: Item! } +# # Feedback for the logFlashcardLearned mutation. type FlashcardLearnedFeedback { + # # Whether the flashcard was learned correctly. success: Boolean! + # # Next date when the flashcard should be learned again. nextLearnDate: DateTime! + # # Progress of the whole flashcard set. flashcardSetProgress: FlashcardSetProgress! } @@ -999,13 +1270,16 @@ type FlashcardOutput { } type FlashcardProgressData { + # # The date the user learned the flashcard. # This is null it the user has not learned the content item once. lastLearned: DateTime + # # The learning interval in days for the content item. learningInterval: Int + # # The next time the content should be learned. # Calculated using the date the user completed the content item and the learning interval. # This is null if the user has not completed the content item once. @@ -1013,26 +1287,33 @@ type FlashcardProgressData { } type FlashcardProgressDataLog { + # # The id of the Log id: UUID + # # The date the user learned the flashcard. learnedAt: DateTime! + # # Whether the user knew the flashcard or not. success: Boolean! } +# # A set of flashcards. A flashcard set belongs to exactly one assessment. Therefore, the uuid of the assessment # also serves as the identifier of a flashcard set. type FlashcardSet { + # # The uuid of the assessment this flashcard set belongs to. # This also serves as the identifier of this flashcard set. assessmentId: UUID! + # # Id of the course this flashcard set belongs to. courseId: UUID! + # # List of flashcards in this set. flashcards: [Flashcard!]! @@ -1040,26 +1321,34 @@ type FlashcardSet { content: Content } +# # A set of flashcards, flashcard related fields are stored in the flashcard service. type FlashcardSetAssessment implements Assessment & Content { + # # Assessment metadata assessmentMetadata: AssessmentMetadata! + # # ID of the content id: UUID! + # # Metadata of the content metadata: ContentMetadata! + # # Progress data of the content for the current user. userProgressData: UserProgressData! + # # Progress data of the specified user. progressDataForUser(userId: UUID!): UserProgressData! + # # the items that belong to the Flashcard items: [Item!]! + # # For the current user, returns true if this content could be worked on by the user (i.e. it is not locked), false # if content is not available to be worked on (e.g. because previous stage has not been completed) isAvailableToBeWorkedOn: Boolean! @@ -1076,9 +1365,11 @@ type FlashcardSetAssessment implements Assessment & Content { } type FlashcardSetMutation { + # # ID of the flashcard set that is being modified. assessmentId: UUID! + # # Deletes the flashcard with the specified ID. Throws an error if the flashcard does not exist. deleteFlashcard(id: UUID!): UUID! @@ -1091,38 +1382,48 @@ type FlashcardSetMutation { } type FlashcardSetProgress { + # # Percentage of how many flashcards in the set have been learned. percentageLearned: Float! + # # Percentage of how many flashcards have been learned correctly of the ones that have been learned. correctness: Float! } type FlashcardSide { + # # Text of this flashcard side as rich text in SlateJS json. text: JSON! + # # Label of this flashcard side. E.g. "Front" or "Back", or "Question" or "Answer". label: String! + # # Whether this side is a question, i.e. should be shown to the user to guess the other sides or not. isQuestion: Boolean! + # # Whether this side is also an answer. Some Flashcards can have their sides be # used as both questions or answers for the other sides isAnswer: Boolean! } input FlashcardSideInput { + # # Text of this flashcard side. text: JSON! + # # Label of this flashcard side. E.g. "Front" or "Back", or "Question" or "Answer". label: String! + # # Whether this side is a question, i.e. should be shown to the user to guess the other sides or not. isQuestion: Boolean! + # # Whether this side is also an answer. Some Flashcards can have their sides be # used as both questions or answers for the other sides isAnswer: Boolean! @@ -1142,48 +1443,61 @@ input IngestMediaRecordInput { id: UUID! } +# # Filter for integer values. # If multiple filters are specified, they are combined with AND. input IntFilter { + # # An integer value to match exactly. equals: Int + # # If specified, filters for values greater than to the specified value. greaterThan: Int + # # If specified, filters for values less than to the specified value. lessThan: Int } +# # An item is a part of an assessment. Based on students' performances on items the # SkillLevel Service estimates a students knowledge. # An item is something like a question in a quiz, a flashcard of a flashcard set. type Item { + # # the id of the item id: UUID! + # # The skills or the competencies the item belongs to. associatedSkills: [Skill!]! + # # The Level of Blooms Taxonomy the item belongs to associatedBloomLevels: [BloomLevel!]! } input ItemInput { + # # might be empty if a new item is created id: UUID + # # The skills or the competencies the item belongs to. associatedSkills: [SkillInput!]! + # # The Level of Blooms Taxonomy the item belongs to associatedBloomLevels: [BloomLevel!]! } type ItemProgress { + # # the id of the corresponding item itemId: UUID! + # # the correctness of the users response. # Value between 0 and 1 representing the user's correctness on the content item. responseCorrectness: Float! @@ -1196,37 +1510,47 @@ scalar JSON scalar LocalTime input LogFlashcardLearnedInput { + # # The id of the flashcard that was learned. flashcardId: UUID! + # # If the user knew the flashcard or not. successful: Boolean! } input LogFlashcardSetLearnedInput { + # # The id of the flashcard that was learned. flashcardSetId: UUID! + # # The id of the user that learned the flashcard. userId: UUID! + # # The percentage of flashcards in the set that the user knew. percentageSuccess: Float! } type MediaContent implements Content { + # # ID of the content id: UUID! + # # Metadata of the content metadata: ContentMetadata! + # # Progress data of the content for the current user. userProgressData: UserProgressData! + # # Progress data of the specified user. progressDataForUser(userId: UUID!): UserProgressData! + # # For the current user, returns true if this content could be worked on by the user (i.e. it is not locked), false # if content is not available to be worked on (e.g. because previous stage has not been completed) isAvailableToBeWorkedOn: Boolean! @@ -1251,48 +1575,60 @@ type MediaContent implements Content { # this can be done in a separate files as long as they are in this folder and # end with .graphqls type MediaRecord { + # # ID of the media record id: UUID! + # # Ids of the courses this MediaRecord is associated with courseIds: [UUID!]! + # # Name of the media record name: String! + # # User ID of the creator of the media record. creatorId: UUID! + # # Type of the media record type: MediaType! + # # IDs of the MediaContents this media record is associated with contentIds: [UUID!]! + # # Temporary upload url for the media record uploadUrl: String! + # # Temporary download url for the media record downloadUrl: String! + # # Temporary download url for the media record where, if the media record is uploaded in a non-standardized format, a # converted version of that file is served. - # + # # For documents, this is a PDF version of the document. - # + # # May be NULL if no standardized version is available. standardizedDownloadUrl: String + # # Temporary upload url for the media record which can only be used from within the system. # (This is necessary because the MinIO pre-signed URLs cannot be changed, meaning we cannot use the same URL for both # internal and external access because the hostname changes.) internalUploadUrl: String! + # # Temporary download url for the media record which can only be used from within the system. # (This is necessary because the MinIO pre-signed URLs cannot be changed, meaning we cannot use the same URL for both # internal and external access because the hostname changes.) internalDownloadUrl: String! + # # The progress data of the given user for this medium. userProgressData: MediaRecordProgressData! @@ -1320,24 +1656,30 @@ type MediaRecord { } type MediaRecordProgressData { + # # Whether the medium has been worked on by the user. workedOn: Boolean! + # # Date on which the medium was worked on by the user. # This is null if the medium has not been worked on by the user. dateWorkedOn: DateTime } interface MediaRecordSegment { + # # UUID of this segment. id: UUID! + # # UUID of the media record this segment is part of. mediaRecordId: UUID! + # # Base64-encoded image thumbnail for this segment. thumbnail: String! + # # Title of this segment. title: String } @@ -1348,13 +1690,16 @@ type MediaRecordSegmentLink { } type MediaRecordSegmentSemanticSearchResult implements SemanticSearchResult { + # # The similarity score of the search result. score: Float! + # # The media record segment this search result is referencing. mediaRecordSegment: MediaRecordSegment! } +# # The type of the media record enum MediaType { VIDEO @@ -1366,176 +1711,218 @@ enum MediaType { } type MultipleChoiceAnswer { + # # Text of the answer, in SlateJS JSON format. answerText: JSON! + # # Whether the answer is correct or not. correct: Boolean! + # # Feedback for when the user selects this answer, in SlateJS JSON format. feedback: JSON } input MultipleChoiceAnswerInput { + # # Text of the answer, in SlateJS JSON format. answerText: JSON! + # # Whether the answer is correct or not. correct: Boolean! + # # Feedback for when the user selects this answer, in SlateJS JSON format. feedback: JSON } +# # Multiple choice question, i.e., a question with multiple answers of which the user has to select the correct ones. type MultipleChoiceQuestion implements Question { + # # Text of the question, in SlateJS JSON format. text: JSON! + # # List of answers. answers: [MultipleChoiceAnswer!]! + # # How many answers the user has to select. This is computed from the list of answers. numberOfCorrectAnswers: Int! + # # Unique identifier of the question and the id of the corresponding item itemId: UUID! + # # Number of the question, i.e., the position of the question in the list of questions. # Only relevant if questionPoolingMode is ORDERED. number: Int! + # # Type of the question. type: QuestionType! + # # Optional hint for the question, in SlateJS JSON format. hint: JSON item: Item! } type Mutation { - # ONLY FOR TESTING PURPOSES. DO NOT USE IN FRONTEND. WILL BE REMOVED. - # - # Triggers the recalculation of the reward score of the user. - # This is done automatically at some time in the night. - # - # The purpose of this mutation is to allow testing of the reward score and demonstrate the functionality. - # 🔒 The user be an admin in the course with the given courseId to perform this action. - recalculateScores(courseId: UUID!, userId: UUID!): RewardScores! @deprecated(reason: "Only for testing purposes. Will be removed.") - - # ONLY FOR TESTING PURPOSES. DO NOT USE IN FRONTEND. WILL BE REMOVED. - # - # Triggers the recalculation of the skill level of the user. - # This is done automatically at some time in the night. - # - # The purpose of this mutation is to allow testing of the skill level score and demonstrate the functionality. - # 🔒 The user must be a super-user, otherwise an exception is thrown. - recalculateLevels(chapterId: UUID!, userId: UUID!): SkillLevels! @deprecated(reason: "Only for testing purposes. Will be removed.") - - # Modify Content - # 🔒 The user must have admin access to the course containing the section to perform this action. - mutateContent(contentId: UUID!): ContentMutation! - - # Modify the section with the given id. - # 🔒 The user must have admin access to the course containing the section to perform this action. - mutateSection(sectionId: UUID!): SectionMutation! - - # Modify a flashcard set. - # 🔒 The user must be an admin the course the flashcard set is in to perform this action. - mutateFlashcardSet(assessmentId: UUID!): FlashcardSetMutation! - - # Logs that a user has learned a flashcard. - # 🔒 The user must be enrolled in the course the flashcard set is in to perform this action. - logFlashcardLearned(input: LogFlashcardLearnedInput!): FlashcardLearnedFeedback! - + # # Creates a new course with the given input and returns the created course. createCourse(input: CreateCourseInput!): Course! + # # Creates a new chapter with the given input and returns the created chapter. # The course id must be a course id of an existing course. # 🔒 The user must be an admin in this course to perform this action. createChapter(input: CreateChapterInput!): Chapter! + # # Updates an existing course with the given input and returns the updated course. # The course id must be a course id of an existing course. # 🔒 The user must be an admin in this course to perform this action. updateCourse(input: UpdateCourseInput!): Course! + # # Updates an existing chapter with the given input and returns the updated chapter. # The chapter id must be a chapter id of an existing chapter. # 🔒 The user must be an admin in this course to perform this action. updateChapter(input: UpdateChapterInput!): Chapter! + # # Deletes an existing course, throws an error if no course with the given id exists. # 🔒 The user must be an admin in this course to perform this action. deleteCourse(id: UUID!): UUID! + # # Deletes an existing chapter, throws an error if no chapter with the given id exists. # 🔒 The user must be an admin in this course to perform this action. deleteChapter(id: UUID!): UUID! + # # Lets the current user join a course as a student. joinCourse(courseId: UUID!): CourseMembership! + # # Lets the current user leave a course. Returns the membership that was deleted. leaveCourse(courseId: UUID!): CourseMembership! + # # Adds the specified user to the specified course with the specified role. # 🔒 The calling user must be an admin in this course to perform this action. createMembership(input: CourseMembershipInput!): CourseMembership! + # # Updates a user's membership in a course with the given input. # 🔒 The calling user must be an admin in this course to perform this action. updateMembership(input: CourseMembershipInput!): CourseMembership! + # # Removes the specified user's access to the specified course. # 🔒 The calling user must be an admin in this course to perform this action. deleteMembership(input: CourseMembershipInput!): CourseMembership! + # + # Modify a quiz. + # 🔒 The user must be an admin the course the quiz is in to perform this action. + mutateQuiz(assessmentId: UUID!): QuizMutation! + + # + # Delete a quiz. + deleteQuiz(assessmentId: UUID!): UUID! @deprecated(reason: "Only use if you specifically only want to delete the quiz and not the whole assessment. Otherwise, use deleteAssessment in contents service instead.") + + # + # Log that a multiple choice quiz is completed. + # 🔒 The user must be enrolled in the course the quiz is in to perform this action. + logQuizCompleted(input: QuizCompletedInput!): QuizCompletionFeedback! + aiGenerateQuestions(context: AiGenQuestionContext!): Quiz! + + # + # ONLY FOR TESTING PURPOSES. DO NOT USE IN FRONTEND. WILL BE REMOVED. + # + # Triggers the recalculation of the reward score of the user. + # This is done automatically at some time in the night. + # + # The purpose of this mutation is to allow testing of the reward score and demonstrate the functionality. + # 🔒 The user be an admin in the course with the given courseId to perform this action. + recalculateScores(courseId: UUID!, userId: UUID!): RewardScores! @deprecated(reason: "Only for testing purposes. Will be removed.") + + # + # ONLY FOR TESTING PURPOSES. DO NOT USE IN FRONTEND. WILL BE REMOVED. + # + # Triggers the recalculation of the skill level of the user. + # This is done automatically at some time in the night. + # + # The purpose of this mutation is to allow testing of the skill level score and demonstrate the functionality. + # 🔒 The user must be a super-user, otherwise an exception is thrown. + recalculateLevels(chapterId: UUID!, userId: UUID!): SkillLevels! @deprecated(reason: "Only for testing purposes. Will be removed.") + + # # Creates a new media record # 🔒 The user must have the "course-creator" role to perform this action. # 🔒 If the mediaRecord is associated with courses the user must be an administrator of all courses or a super-user. createMediaRecord(input: CreateMediaRecordInput!): MediaRecord! + # # Updates an existing media record with the given UUID # 🔒 If the mediaRecord is associated with courses the user must be an administrator of at least one of the courses. updateMediaRecord(input: UpdateMediaRecordInput!): MediaRecord! + # # Deletes the media record with the given UUID # 🔒 If the mediaRecord is associated with courses the user must be an administrator of at least one of the courses. deleteMediaRecord(id: UUID!): UUID! + # # For a given MediaContent, sets the linked media records of it to the ones with the given UUIDs. # This means that for the content, all already linked media records are removed and replaced by the given ones. # 🔒 If the mediaRecord is associated with courses the user must be an administrator of at least one of the courses. setLinkedMediaRecordsForContent(contentId: UUID!, mediaRecordIds: [UUID!]!): [MediaRecord!]! + # # Logs that a media has been worked on by the current user. # See https://gits-enpro.readthedocs.io/en/latest/dev-manuals/gamification/userProgress.html - # + # # Possible side effects: # When all media records of a content have been worked on by a user, # a user-progress event is emitted for the content. # 🔒 If the mediaRecord is associated with courses the user must be a member of at least one of the courses. logMediaRecordWorkedOn(mediaRecordId: UUID!): MediaRecord! + # # Add the MediaRecords with the given UUIDS to the Course with the given UUID. # 🔒 If the mediaRecord is associated with courses the user must be an administrator of at least one of the courses. setMediaRecordsForCourse(courseId: UUID!, mediaRecordIds: [UUID!]!): [MediaRecord!]! - # Modify a quiz. - # 🔒 The user must be an admin the course the quiz is in to perform this action. - mutateQuiz(assessmentId: UUID!): QuizMutation! - - # Delete a quiz. - deleteQuiz(assessmentId: UUID!): UUID! @deprecated(reason: "Only use if you specifically only want to delete the quiz and not the whole assessment. Otherwise, use deleteAssessment in contents service instead.") + # + # Modify a flashcard set. + # 🔒 The user must be an admin the course the flashcard set is in to perform this action. + mutateFlashcardSet(assessmentId: UUID!): FlashcardSetMutation! + # # Delete a flashcard set. - deleteFlashcardSet(assessmentId: UUID!): UUID! @deprecated(reason: "Only use if you specifically only want to delete the flashcardset and not the whole assessment. Otherwise, use deleteAssessment in contents service instead.") + deleteFlashcardSet(assessmentId: UUID!): UUID! @deprecated(reason: "Only use if you specifically only want to delete the flashcard set and not the whole assessment. Otherwise, use deleteAssessment in contents service instead.") - # Log that a multiple choice quiz is completed. - # 🔒 The user must be enrolled in the course the quiz is in to perform this action. - logQuizCompleted(input: QuizCompletedInput!): QuizCompletionFeedback! + # + # Logs that a user has learned a flashcard. + # 🔒 The user must be enrolled in the course the flashcard set is in to perform this action. + logFlashcardLearned(input: LogFlashcardLearnedInput!): FlashcardLearnedFeedback! + + # + # Modify Content + # 🔒 The user must have admin access to the course containing the section to perform this action. + mutateContent(contentId: UUID!): ContentMutation! + + # + # Modify the section with the given id. + # 🔒 The user must have admin access to the course containing the section to perform this action. + mutateSection(sectionId: UUID!): SectionMutation! # Creates a new media content and links the given media records to it. createMediaContentAndLinkRecords(contentInput: CreateMediaContentInput!, mediaRecordIds: [UUID!]!): MediaContent! @@ -1551,81 +1938,104 @@ type Mutation { } type NumericQuestion implements Question { + # # Text of the question, in SlateJS JSON format. text: JSON! + # # The correct answer to the question. correctAnswer: Float! + # # The tolerance for the correct answer. The user's answer is correct if it is within the tolerance of the correct answer. tolerance: Float! + # # Feedback for the question when the user enters a wrong answer, in SlateJS JSON format. feedback: JSON + # # Unique identifier of the question and the id of the corresponding item itemId: UUID! + # # Number of the question, i.e., the position of the question in the list of questions. # Only relevant if questionPoolingMode is ORDERED. number: Int! + # # Type of the question. type: QuestionType! + # # Optional hint for the question, in SlateJS JSON format. hint: JSON item: Item! } +# # Specifies the page size and page number for paginated results. input Pagination { + # # The page number, starting at 0. # If not specified, the default value is 0. # For values greater than 0, the page size must be specified. # If this value is larger than the number of pages, an empty page is returned. page: Int! = 0 + # # The number of elements per page. size: Int! } +# # Return type for information about paginated results. type PaginationInfo { + # # The current page number. page: Int! + # # The number of elements per page. size: Int! + # # The total number of elements across all pages. totalElements: Int! + # # The total number of pages. totalPages: Int! + # # Whether there is a next page. hasNext: Boolean! } type ProgressLogItem { + # # The date the user completed the content item. timestamp: DateTime! + # # Whether the user completed the content item successfully. success: Boolean! + # # Value between 0 and 1 representing the user's correctness on the content item. # Can be null as some contents cannot provide a meaningful correctness value. correctness: Float! + # # How many hints the user used to complete the content item. hintsUsed: Int! + # # Time in milliseconds it took the user to complete the content item. # Can be null for contents that do not measure completion time. timeToComplete: Int + # # !OPTIONAL # the items the user has completed and the students' performance on these items # Can be null as some contents don't contains items for assessments @@ -1638,142 +2048,169 @@ type PublicUserInfo { } type Query { + # # Gets the publicly available information for a list of users with the specified IDs. # If a user does not exist, null is returned for that user. findPublicUserInfos(ids: [UUID!]!): [PublicUserInfo]! + # # Gets the user information of the currently authorized user. currentUserInfo: UserInfo! + # # Gets all of the users' information for a list of users with the specified IDs. # Only available to privileged users. # If a user does not exist, null is returned for that user. findUserInfos(ids: [UUID!]!): [UserInfo]! - # Get the reward score of the current user for the specified course. - # 🔒 The user must have access to the course with the given id to access their scores, otherwise an error is thrown. - userCourseRewardScores(courseId: UUID!): RewardScores! - - # Get the reward score of the specified user for the specified course. - # 🔒 The user be an admin in the course with the given courseId to perform this action. - courseRewardScoresForUser(courseId: UUID!, userId: UUID!): RewardScores! - - # Gets the power scores for each user in the course, ordered by power score descending. - # 🔒 The user must have access to the course with the given id to access the scoreboard, otherwise an error is thrown. - scoreboard(courseId: UUID!): [ScoreboardItem!]! - - # Retrieves all existing contents for a given course. - # 🔒 The user must have access to the courses with the given ids to access their contents, otherwise an error is thrown. - contentsByCourseIds(courseIds: [UUID!]!): [[Content!]!] - - # Get contents by ids. Throws an error if any of the ids are not found. - # 🔒 The user must have access to the courses containing the contents with the given ids to access their contents, - # otherwise an error is thrown. - contentsByIds(ids: [UUID!]!): [Content!]! - - # Get contents by ids. If any of the given ids are not found, the corresponding element in the result list will be null. - # 🔒 The user must have access to the courses containing the contents with the given ids, otherwise null is returned - # for the respective contents. - findContentsByIds(ids: [UUID!]!): [Content]! - - # Get contents by chapter ids. Returns a list containing sublists, where each sublist contains all contents - # associated with that chapter - # 🔒 The user must have access to the courses containing the chapters with the given ids, otherwise an error is thrown. - contentsByChapterIds(chapterIds: [UUID!]!): [[Content!]!]! - - # Generates user specific suggestions for multiple chapters. - # - # Only content that the user can access will be considered. - # The contents will be ranked by suggested date, with the most overdue or most urgent content first. - # - # 🔒 The user must have access to the courses containing the chapters with the given ids, otherwise an error is thrown. - suggestionsByChapterIds( - # The ids of the chapters for which suggestions should be generated. - chapterIds: [UUID!]! - - # The amount of suggestions to generate in total. - amount: Int! - - # Only suggestions for these skill types will be generated. - # If no skill types are given, suggestions for all skill types will be generated, - # also containing suggestions for media content (which do not have a skill type). - skillTypes: [SkillType!]! = [] - ): [Suggestion!]! - items(ids: [UUID!]!): [Item!]! - - # Get flashcards by their ids. - # 🔒 The user must be enrolled in the course the flashcards belong to. Otherwise an error is thrown. - flashcardsByIds(itemIds: [UUID!]!): [Flashcard!]! - - # Get flashcard sets by their assessment ids. - # Returns a list of flashcard sets in the same order as the provided ids. - # Each element is null if the corresponding id is not found. - # 🔒 The user must be enrolled in the course the flashcard sets belong to. Otherwise for that element null is returned. - findFlashcardSetsByAssessmentIds(assessmentIds: [UUID!]!): [FlashcardSet]! - - # Get flashcards of a course that are due to be reviewed. - # 🔒 The user must be enrolled in the course the flashcards belong to. Otherwise an error is thrown. - dueFlashcardsByCourseId(courseId: UUID!): [Flashcard!]! - + # # Get a list of courses. Can be filtered, sorted and paginated. # Courses and their basic data can be queried by any user, even if they are not enrolled in the course. courses( filter: CourseFilter + # # The fields to sort by. # Throws an error if no field with the given name exists. sortBy: [String!] + # # The sort direction for each field. If not specified, defaults to ASC. sortDirection: [SortDirection!]! = [ASC] pagination: Pagination ): CoursePayload! + # # Returns the courses with the given ids. # Courses and their basic data can be queried by any user, even if they are not enrolled in the course. coursesByIds(ids: [UUID!]!): [Course!]! + # + # Get quiz by assessment ID. + # If any of the assessment IDs are not found, the corresponding quiz will be null. + # 🔒 The user must be enrolled in the course the quizzes belong to to access them. Otherwise null is returned for + # an quiz if the user has no access to it. + findQuizzesByAssessmentIds(assessmentIds: [UUID!]!): [Quiz]! + + # + # Get the reward score of the current user for the specified course. + # 🔒 The user must have access to the course with the given id to access their scores, otherwise an error is thrown. + userCourseRewardScores(courseId: UUID!): RewardScores! + + # + # Get the reward score of the specified user for the specified course. + # 🔒 The user be an admin in the course with the given courseId to perform this action. + courseRewardScoresForUser(courseId: UUID!, userId: UUID!): RewardScores! + + # + # Gets the power scores for each user in the course, ordered by power score descending. + # 🔒 The user must have access to the course with the given id to access the scoreboard, otherwise an error is thrown. + scoreboard(courseId: UUID!): [ScoreboardItem!]! + + # # Returns the media records with the given IDs. Throws an error if a MediaRecord corresponding to a given ID # cannot be found. - # + # # 🔒 If the mediaRecord is associated with coursed the user must be a member of at least one of the courses. mediaRecordsByIds(ids: [UUID!]!): [MediaRecord!]! + # # Like mediaRecordsByIds() returns the media records with the given IDs, but instead of throwing an error if an ID # cannot be found, it instead returns NULL for that media record. - # + # # 🔒 If the mediaRecord is associated with coursed the user must be a member of at least one of the courses. findMediaRecordsByIds(ids: [UUID!]!): [MediaRecord]! + # # Returns all media records of the system. - # + # # 🔒 The user must be a super-user, otherwise an exception is thrown. mediaRecords: [MediaRecord!]! @deprecated(reason: "In production there should probably be no way to get all media records of the system.") + # # Returns all media records which the current user created. - # + # # 🔒 If the mediaRecord is associated with coursed the user must be a member of at least one of the courses. userMediaRecords: [MediaRecord!]! + # # Returns the media records associated the given content IDs as a list of lists where each sublist contains # the media records associated with the content ID at the same index in the input list - # + # # 🔒 If the mediaRecord is associated with courses the user must be a member of at least one of the courses. mediaRecordsByContentIds(contentIds: [UUID!]!): [[MediaRecord!]!]! + # # Returns all media records for the given CourseIds - # + # # 🔒 If the mediaRecord is associated with coursed the user must be a member of at least one of the courses. mediaRecordsForCourses(courseIds: [UUID!]!): [[MediaRecord!]!]! + # # Returns all media records which were created by the users. mediaRecordsForUsers(userIds: [UUID!]!): [[MediaRecord!]!]! - # Get quiz by assessment ID. - # If any of the assessment IDs are not found, the corresponding quiz will be null. - # 🔒 The user must be enrolled in the course the quizzes belong to to access them. Otherwise null is returned for - # an quiz if the user has no access to it. - findQuizzesByAssessmentIds(assessmentIds: [UUID!]!): [Quiz]! + # + # Get flashcards by their ids. + # 🔒 The user must be enrolled in the course the flashcards belong to. Otherwise an error is thrown. + flashcardsByIds(itemIds: [UUID!]!): [Flashcard!]! + + # + # Get flashcard sets by their assessment ids. + # Returns a list of flashcard sets in the same order as the provided ids. + # Each element is null if the corresponding id is not found. + # 🔒 The user must be enrolled in the course the flashcard sets belong to. Otherwise for that element null is returned. + findFlashcardSetsByAssessmentIds(assessmentIds: [UUID!]!): [FlashcardSet]! + + # + # Get flashcards of a course that are due to be reviewed. + # 🔒 The user must be enrolled in the course the flashcards belong to. Otherwise an error is thrown. + dueFlashcardsByCourseId(courseId: UUID!): [Flashcard!]! + + # + # Retrieves all existing contents for a given course. + # 🔒 The user must have access to the courses with the given ids to access their contents, otherwise an error is thrown. + contentsByCourseIds(courseIds: [UUID!]!): [[Content!]!] + + # + # Get contents by ids. Throws an error if any of the ids are not found. + # 🔒 The user must have access to the courses containing the contents with the given ids to access their contents, + # otherwise an error is thrown. + contentsByIds(ids: [UUID!]!): [Content!]! + + # + # Get contents by ids. If any of the given ids are not found, the corresponding element in the result list will be null. + # 🔒 The user must have access to the courses containing the contents with the given ids, otherwise null is returned + # for the respective contents. + findContentsByIds(ids: [UUID!]!): [Content]! + + # + # Get contents by chapter ids. Returns a list containing sublists, where each sublist contains all contents + # associated with that chapter + # 🔒 The user must have access to the courses containing the chapters with the given ids, otherwise an error is thrown. + contentsByChapterIds(chapterIds: [UUID!]!): [[Content!]!]! + + # + # Generates user specific suggestions for multiple chapters. + # Only content that the user can access will be considered. + # The contents will be ranked by suggested date, with the most overdue or most urgent content first. + # + # 🔒 The user must have access to the courses containing the chapters with the given ids, otherwise an error is thrown. + suggestionsByChapterIds( + # + # The ids of the chapters for which suggestions should be generated. + chapterIds: [UUID!]! + + # + # The amount of suggestions to generate in total. + amount: Int! + + # + # Only suggestions for these skill types will be generated. + # If no skill types are given, suggestions for all skill types will be generated, + # also containing suggestions for media content (which do not have a skill type). + skillTypes: [SkillType!]! = [] + ): [Suggestion!]! # Performs a semantic search with the specified search term. Returns at most `count` results. If a courseWhitelist is # provided, only results from the specified courses will be returned. @@ -1785,41 +2222,52 @@ type Query { getSemanticallySimilarEntities(segmentId: UUID!, count: Int! = 10, excludeEntitiesWithSameParent: Boolean, courseWhitelist: [UUID!]): [SemanticSearchResult!]! } +# # Generic question interface. interface Question { + # # Unique identifier of the question and the id of the corresponding item itemId: UUID! + # # Number of the question, i.e., the position of the question in the list of questions. # Only relevant if questionPoolingMode is ORDERED. number: Int! + # # Type of the question. type: QuestionType! + # # Optional hint for the question, in SlateJS JSON format. hint: JSON } input QuestionCompletedInput { + # # ID of the question. questionId: UUID! + # # true when question was answered correctly correct: Boolean! + # # true when a hint was used for the question usedHint: Boolean! } enum QuestionPoolingMode { + # # Questions are randomly selected from the list of questions. RANDOM + # # Questions are selected in order from the list of questions. ORDERED } +# # The type of a question. enum QuestionType { MULTIPLE_CHOICE @@ -1830,37 +2278,45 @@ enum QuestionType { SELF_ASSESSMENT } +# # A quiz is a set of questions that the user has to answer correctly to pass the quiz. # Questions can be of different types, e.g., multiple choice, clozes, or open questions. type Quiz { + # # Identifier of the quiz, same as the identifier of the assessment. assessmentId: UUID! + # # List of questions. questionPool: [Question!]! + # # Threshold of the quiz, i.e., how many questions the user has to answer correctly to pass the quiz. # If this number is greater than the number of questions, the behavior is the same # as if it was equal to the number of questions. requiredCorrectAnswers: Int! + # # Question pooling mode of the quiz. questionPoolingMode: QuestionPoolingMode! + # # Number of questions that are randomly selected from the list of questions. # Will only be considered if questionPoolingMode is RANDOM. - # + # # If this is greater than the number of questions, the behavior is the same # as if it was equal to the number of questions. - # + # # If this is null or not set, the behavior is the same as if it was equal to the number of questions. numberOfRandomlySelectedQuestions: Int + # # The selected questions of the question pool. # This is identical to the list of questions if questionPoolingMode is ORDERED. # This will be different each time it is queried if questionPoolingMode is RANDOM. selectedQuestions: [Question!]! + # # Id of the course this quiz belongs to. courseId: UUID! @@ -1868,26 +2324,34 @@ type Quiz { content: Content } +# # A quiz, quiz related fields are stored in the quiz service. type QuizAssessment implements Assessment & Content { + # # Assessment metadata assessmentMetadata: AssessmentMetadata! + # # ID of the content id: UUID! + # # Metadata of the content metadata: ContentMetadata! + # # Progress data of the content for the current user. userProgressData: UserProgressData! + # # Progress data of the specified user. progressDataForUser(userId: UUID!): UserProgressData! + # # the items that belong to the Quiz items: [Item!]! + # # For the current user, returns true if this content could be worked on by the user (i.e. it is not locked), false # if content is not available to be worked on (e.g. because previous stage has not been completed) isAvailableToBeWorkedOn: Boolean! @@ -1905,46 +2369,63 @@ type QuizAssessment implements Assessment & Content { } input QuizCompletedInput { + # # ID of the quiz. quizId: UUID! + # # List of questions that were answered in the quiz. completedQuestions: [QuestionCompletedInput!]! } +# # Feedback data when `logQuizCompletion` is called. type QuizCompletionFeedback { + # # Whether the quiz was passed or not. success: Boolean! + # # The number of questions that were answered correctly. correctness: Float! + # # The number of hints that were used. hintsUsed: Int! } type QuizMutation { + # # Id of the quiz to modify. assessmentId: UUID! + # # Removes the question with the given number from the quiz. # This will also update the numbers of the following questions. removeQuestion(number: Int!): Quiz! + # # Switch the position of two questions with the given numbers. switchQuestions(firstNumber: Int!, secondNumber: Int!): Quiz! + # # Set the threshold of the quiz, i.e., how many questions the user has to answer correctly to pass the quiz. setRequiredCorrectAnswers(requiredCorrectAnswers: Int!): Quiz! + # # Set the question pooling mode of the quiz. setQuestionPoolingMode(questionPoolingMode: QuestionPoolingMode!): Quiz! + # # Set the number of questions that are randomly selected from the list of questions. # Will only be considered if questionPoolingMode is RANDOM. setNumberOfRandomlySelectedQuestions(numberOfRandomlySelectedQuestions: Int!): Quiz! + # + # Generate a question using AI. It will append to the end of the quiz questions. + # If will return the quiz to which the question will be added + aiGenerateQuestionAsync(context: AiGenQuestionContext): Quiz! + # Add a multiple choice question to the quiz questions, at the end of the list. addMultipleChoiceQuestion(questionInput: CreateMultipleChoiceQuestionInputWithoutItem!, assessmentId: UUID!, item: CreateItemInput!): QuizOutput! @@ -1990,109 +2471,141 @@ type QuizOutput { scalar ResolveToSourceArgs +# # The reason why the reward score has changed. enum RewardChangeReason { + # # The user has completed a content for the first time. # The associated contents are the content that were completed. CONTENT_DONE + # # The user has reviewed a content. # The associated contents are the content that were reviewed. CONTENT_REVIEWED + # # There exists a content that is due for learning. # The associated contents are the content that are due for learning. CONTENT_DUE_FOR_LEARNING + # # There exists a content that is due for repetition. # The associated contents are the content that are due for repetition. CONTENT_DUE_FOR_REPETITION + # # The score changed because the underlying scores changed. # Relevant for the power score. COMPOSITE_VALUE } +# # An item in the reward score log. type RewardLogItem { + # # The date when the reward score changed. date: DateTime! + # # The difference between the previous and the new reward score. difference: Int! + # # The old reward score. oldValue: Int! + # # The new reward score. newValue: Int! + # # The reason why the reward score has changed. reason: RewardChangeReason! + # # The ids of the contents that are associated with the change. associatedContentIds: [UUID!]! associatedContents: [Content]! } +# # The reward score of a user. type RewardScore { + # # The absolute value of the reward score. # Health and fitness are between 0 and 100. # Growth, strength and power can be any non-negative integer. value: Int! + # # The relative value of the reward score. # Shows how many points relative to the total points have been achieved. # Only used for growth currently. percentage: Float! + # # A log of the changes to the reward score, ordered by date descending. log: [RewardLogItem!]! } +# # The five reward scores of a user. type RewardScores { + # # Health represents how up-to-date the user is with the course. health: RewardScore! + # # Fitness represents how well the user repeats previously learned content. fitness: RewardScore! + # # Growth represents the overall progress of the user. growth: RewardScore! + # # Strength is earned by competing with other users. strength: RewardScore! + # # A composite score of all the other scores. power: RewardScore! } +# # An item in the scoreboard. type ScoreboardItem { + # # The user id of the user. userId: UUID! + # # The power score of the user. powerScore: Int! user: PublicUserInfo } +# # Representation of a Section type Section { + # # Unique identifier of the Section Object id: UUID! + # # Id of the Course the Section is located in. courseId: UUID! + # # Name of the Section name: String! + # # Chapter the Section is located in chapterId: UUID! + # # List of Stages contained in a Section stages: [Stage!]! @@ -2101,80 +2614,103 @@ type Section { } type SectionMutation { + # # Identifier of the section sectionId: UUID! + # # update the name of a Section updateSectionName(name: String!): Section! + # # delete a Section by ID deleteSection: UUID! + # # create new Stage in Section createStage(input: CreateStageInput): Stage! + # # Update Content of Stage updateStage(input: UpdateStageInput): Stage! + # # delete Stage by ID deleteStage(id: UUID!): UUID! + # # update Order of Stages within a Section updateStageOrder(stages: [UUID!]!): Section! } +# # A single question with a free text answer field, where the answer is not automatically checked. # The user has to enter a solution and self-assess whether it is correct or not. # This is useful for questions where the answer is not clear-cut, e.g. when the user should explain a concept. type SelfAssessmentQuestion implements Question { + # # Text of the question, in SlateJS JSON format. text: JSON! + # # A possible correct answer to the question. solutionSuggestion: JSON! + # # Unique identifier of the question and the id of the corresponding item itemId: UUID! + # # Number of the question, i.e., the position of the question in the list of questions. # Only relevant if questionPoolingMode is ORDERED. number: Int! + # # Type of the question. type: QuestionType! + # # Optional hint for the question, in SlateJS JSON format. hint: JSON } interface SemanticSearchResult { + # # The similarity score of the search result. score: Float! } type SingleAssociation { + # # The left side of the association, in SlateJS JSON format. left: JSON! + # # The right side of the association, in SlateJS JSON format. right: JSON! + # # Feedback for the association when the user assigns a wrong answer, in SlateJS JSON format. feedback: JSON } +# # a skill or compentency. # Something like loops or data structures. type Skill { + # # the id of a skill id: UUID! + # # the name of the skill skillName: String! + # # the category of the skill skillCategory: String! + # # whether the skill is a custom-created by the user and no IEEE skill isCustomSkill: Boolean! @@ -2183,77 +2719,100 @@ type Skill { } input SkillInput { + # # the id of a skill. Field is optional, because not all required skills may # exist, if a new item is created. If the id is empty a new skill, # will be created id: UUID + # # the name of the skill skillName: String! + # # the category of the skill skillCategory: String! + # # whether the skill is a custom-created by the user and no IEEE skill isCustomSkill: Boolean! } +# # The skill level of a user. type SkillLevel { + # # The value of the skill level. # levels are between 0 and 1. value: Float! + # # A log of the changes to the skill level log: [SkillLevelLogItem!]! } +# # An item in the skill level change log. type SkillLevelLogItem { + # # The date when the skill level changed. date: DateTime! + # # The difference between the previous and the new skill level. difference: Float! + # # The old skill level. oldValue: Float! + # # The new skill level. newValue: Float! + # # The ids of the contents that are associated with the change. associatedItemId: UUID! + # # the response of the user to the item userResponse: Float! + # # the probability of a correct response, that M-Elo predicts predictedCorrectness: Float! associatedContents: [Content]! } +# # The four skill level of a user. type SkillLevels { + # # remember represents how much user remember the concept remember: SkillLevel + # # understand represents how well the user understands learned content. understand: SkillLevel + # # apply represents the how well user applies the learned concept during assessment. apply: SkillLevel + # # apply is how much user can evaluate information and draw conclusions analyze: SkillLevel + # # evaluate represent how well a user can use the learned content to evaluate evaluate: SkillLevel + # # create represents how well a user can create new things based on the learned content create: SkillLevel } +# # Type of the assessment enum SkillType { CREATE @@ -2264,55 +2823,71 @@ enum SkillType { ANALYZE } +# # Specifies the sort direction, either ascending or descending. enum SortDirection { ASC DESC } +# # Representation of a Stage type Stage { + # # Unique identifier of the Stage Object id: UUID! + # # Position of the Stage within the Section position: Int! + # # List of Content that is labeled as required content requiredContents: [Content!]! + # # Percentage of User Progress made to required Content requiredContentsProgress: Float! + # # List of Content that is labeled as optional content optionalContents: [Content!]! + # # Percentage of Progress made to optional Content optionalContentsProgress: Float! + # # For the current user, returns true if this stage could be worked on by the user (i.e. it is not locked), false # if stage is not available to be worked on (e.g. because previous stage has not been completed) isAvailableToBeWorkedOn: Boolean! } +# # Filter for string values. # If multiple filters are specified, they are combined with AND. input StringFilter { + # # A string value to match exactly. equals: String + # # A string value that must be contained in the field that is being filtered. contains: String + # # If true, the filter is case-insensitive. ignoreCase: Boolean! = false } +# # Represents a suggestion for a user to learn new content or review old content. type Suggestion { + # # The content that is suggested to the user. content: Content! + # # The type of suggestion. type: SuggestionType! } @@ -2326,227 +2901,292 @@ enum SuggestionType { scalar Time input UpdateAssessmentInput { + # # Metadata for the new Content metadata: UpdateContentMetadataInput! + # # Assessment metadata assessmentMetadata: AssessmentMetadataInput! + # # items of the new assessments items: [ItemInput!] } input UpdateAssociationQuestionInput { + # # UUID of the question to update and the id of the corresponding item. itemId: UUID! + # # Text of the question, in SlateJS JSON format. text: JSON! + # # List of associations. correctAssociations: [AssociationInput!]! + # # Optional hint for the question, in SlateJS JSON format. hint: JSON } +# # Input type for updating chapters. # The ID field specifies which chapter should be updated, all other fields specify the new values. input UpdateChapterInput { + # # UUID of the chapter that should be updated. id: UUID! + # # Title of the chapter, maximum length is 255 characters, must not be blank. title: String! + # # Description of the chapter, maximum length is 3000 characters. description: String! + # # Number of the chapter, determines the order of the chapters, must be positive. number: Int! + # # Start date of the chapter, ISO 8601 format. # Must be before the end date. startDate: DateTime! + # # End date of the chapter, ISO 8601 format. # Must be after the start date. endDate: DateTime! + # # Suggested Start date to start the chapter, ISO 8601 format. # Must be after Start Date and before the End dates. suggestedStartDate: DateTime + # # Suggested End date of the chapter, ISO 8601 format. # Must be after the Start Dates and before the End dates. suggestedEndDate: DateTime } input UpdateClozeQuestionInput { + # # UUID of the question to update and the id of the corresponding item. itemId: UUID! + # # List of cloze elements. clozeElements: [ClozeElementInput!]! + # # List of additional wrong answers. additionalWrongAnswers: [String!]! + # # If true, the list of possible answers will be shown to the user. showBlanksList: Boolean! = true + # # Optional hint for the question, in SlateJS JSON format. hint: JSON } input UpdateContentMetadataInput { + # # Name of the content name: String! + # # Date when the content should be done suggestedDate: DateTime! + # # Number of reward points a student receives for completing this content rewardPoints: Int! + # # ID of the chapter this content is associated with chapterId: UUID! + # # TagNames this content is tagged with tagNames: [String!]! = [] } +# # Input type for updating an existing course. See also on the course type for detailed field descriptions. # The id specifies the course that should be updated, the other fields specify the new values. input UpdateCourseInput { + # # UUID of the course that should be updated. # Must be an id of an existing course, otherwise an error is returned. id: UUID! + # # The new title of the course, max 255 characters, must not be blank. title: String! + # # The new description of the course, max 3000 characters. description: String! + # # The new start date of the course, ISO 8601 format. startDate: DateTime! + # # The new end date of the course, ISO 8601 format. endDate: DateTime! + # # The new published status of the course. published: Boolean! + # # The year in which the term starts. startYear: Int + # # The division of the academic calendar in which the term takes place. yearDivision: YearDivision } input UpdateExactAnswerQuestionInput { + # # UUID of the question to update and the id of the corresponding item. itemId: UUID! + # # Text of the question, in SlateJS JSON format. text: JSON! + # # A list of possible correct answers. correctAnswers: [String!]! + # # If the answer is case sensitive. If true, the answer is checked case sensitive. caseSensitive: Boolean! = false + # # Feedback for the question when the user enters a wrong answer, in SlateJS JSON format. feedback: JSON + # # Optional hint for the question, in SlateJS JSON format. hint: JSON } input UpdateFlashcardInput { + # # Id of the flashcard to update, which is the id of the corresponding item. itemId: UUID! + # # List of sides of this flashcard. Must be at least two sides. sides: [FlashcardSideInput!]! } input UpdateMediaContentInput { + # # Metadata for the new Content metadata: UpdateContentMetadataInput! } input UpdateMediaRecordInput { + # # ID of the media record which should be updated id: UUID! + # # New name of the media record. Cannot be blank, maximum length 255 characters. name: String! + # # New type of the media record. type: MediaType! + # # IDs of the MediaContents this media record is associated with contentIds: [UUID!]! } input UpdateMultipleChoiceQuestionInput { + # # UUID of the question to update and the id of the corresponding item. itemId: UUID! + # # Text of the question, in SlateJS JSON format. text: JSON! + # # List of answers. answers: [MultipleChoiceAnswerInput!]! + # # Optional hint for the question, in SlateJS JSON format. hint: JSON } input UpdateNumericQuestionInput { + # # UUID of the question to update and the id of the corresponding item. itemId: UUID! + # # Text of the question, in SlateJS JSON format. text: JSON! + # # The correct answer for the question. correctAnswer: Float! + # # The allowed deviation from the correct answer. tolerance: Float! + # # Feedback for the question when the user enters a wrong answer, in SlateJS JSON format. feedback: JSON + # # Optional hint for the question, in SlateJS JSON format. hint: JSON } input UpdateSelfAssessmentQuestionInput { + # # UUID of the question to update and the id of the corresponding item. itemId: UUID! + # # Text of the question, in SlateJS JSON format. text: JSON! + # # A possible correct answer to the question. solutionSuggestion: JSON! + # # Optional hint for the question, in SlateJS JSON format. hint: JSON } input UpdateStageInput { + # # Identifier of the Stage id: UUID! + # # updated List of UUIDs for content labeled as required in this Stage requiredContents: [UUID!]! + # # updated List of UUIDs for content labeled as optional in this Stage optionalContents: [UUID!]! } @@ -2570,39 +3210,49 @@ type UserInfo { mediaRecords: [MediaRecord!]! } +# # Represents a user's progress on a content item. # See https://gits-enpro.readthedocs.io/en/latest/dev-manuals/gamification/userProgress.html type UserProgressData { + # # The user's id. userId: UUID! + # # The id of the content item. contentId: UUID! + # # A list of entries each representing the user completing the content item. # Sorted by date in descending order. log: [ProgressLogItem]! + # # The learning interval in days for the content item. # If null, the content item is not scheduled for learning. learningInterval: Int + # # The next time the content should be learned. # Calculated using the date the user completed the content item and the learning interval. # This is null if the user has not completed the content item once. nextLearnDate: DateTime + # # The last time the content was learned successfully. # This is null if the user has not completed the content item once. lastLearnDate: DateTime + # # True if the user has completed the content item at least once successfully. isLearned: Boolean! + # # True if the assessment is due for review. isDueForReview: Boolean! } +# # Enum containing all valid roles a user can have in a course. enum UserRoleInCourse { STUDENT @@ -2614,24 +3264,31 @@ enum UserRoleInCourse { scalar UUID type VideoRecordSegment implements MediaRecordSegment { + # # UUID of this segment. id: UUID! + # # UUID of the media record this search result references. mediaRecordId: UUID! + # # Start time in seconds of the snippet of the video this search result references. startTime: Int! + # # Text on the screen during this video snippet. screenText: String! + # # Textual transcript of the spoken text during the video snippet this search result references. transcript: String! + # # Base64-encoded image thumbnail for this segment. thumbnail: String! + # # Title of this segment. title: String @@ -2639,6 +3296,7 @@ type VideoRecordSegment implements MediaRecordSegment { mediaRecord: MediaRecord! } +# # The division of the academic year. enum YearDivision { FIRST_SEMESTER From 651a2018d9e4ff5a2920defdf68c14c92fc7c9f0 Mon Sep 17 00:00:00 2001 From: Can Date: Tue, 2 Sep 2025 15:02:52 +0200 Subject: [PATCH 6/7] updated schema and queries --- components/GenerateQuizModal.tsx | 26 +- src/schema.graphql | 2545 +----------------------------- 2 files changed, 21 insertions(+), 2550 deletions(-) diff --git a/components/GenerateQuizModal.tsx b/components/GenerateQuizModal.tsx index 1d75c49f..388a7b76 100644 --- a/components/GenerateQuizModal.tsx +++ b/components/GenerateQuizModal.tsx @@ -1,4 +1,9 @@ "use client"; +import { GenerateQuizModalMediaQuery } from "@/__generated__/GenerateQuizModalMediaQuery.graphql"; +import { + AiGenQuestionContext, + GenerateQuizModalMutation, +} from "@/__generated__/GenerateQuizModalMutation.graphql"; import { FormDivider } from "@/components/Form"; import { Alert, @@ -18,12 +23,7 @@ import { EducationalObjective, } from "./quiz/CapabilitiesTabPanel"; import { LectureMaterialsTabPanel } from "./quiz/LectureMaterialsTabPanel"; -import { GenerateQuizModalMediaQuery } from "@/__generated__/GenerateQuizModalMediaQuery.graphql"; import { QuestionsTabPanel } from "./quiz/QuestionsTabPanel"; -import { - AiGenQuestionContext, - GenerateQuizModalMutation, -} from "@/__generated__/GenerateQuizModalMutation.graphql"; interface TabPanelProps { children?: React.ReactNode; @@ -138,31 +138,29 @@ export function GenerateQuizModal({ ) { mutateQuiz(assessmentId: $assessmentId) { aiGenerateQuestionAsync(context: $context) { - assessmentId + quiz { + assessmentId + } } } } `); function handleSubmit() { - const sumOfQuesitonAmount = Object.values(questionAmount).reduce( - (acc, val) => acc + val, - 0 - ); const context: AiGenQuestionContext = { description: "Use the following keywords as context to generate the questions:\n" + capabilities.keywords.join(", "), - allowMultipleCorrectAnswers: false, maxAnswersPerQuestion: 5, maxExactQuestions: 0, + minExactQuestions: 0, maxFreeTextQuestions: 0, + minFreeTextQuestions: 0, maxMultipleChoiceQuestions: questionAmount.multipleChoiceAmount, + minMultipleChoiceQuestions: 0, maxNumericQuestions: 0, - maxQuestions: sumOfQuesitonAmount, + minNumericQuestions: 0, mediaRecordIds: materialIds, - minQuestions: sumOfQuesitonAmount, - quizId: quizId, }; generate({ variables: { context, assessmentId: quizId }, diff --git a/src/schema.graphql b/src/schema.graphql index 7f703eec..498ca48b 100644 --- a/src/schema.graphql +++ b/src/schema.graphql @@ -50,36 +50,12 @@ input AnswerInput { } interface Assessment { - # - # Assessment metadata assessmentMetadata: AssessmentMetadata! - - # - # ID of the content id: UUID! - - # - # Metadata of the content isAvailableToBeWorkedOn: Boolean! items: [Item!]! metadata: ContentMetadata! - - # - # Progress data of the content for the current user. - userProgressData: UserProgressData! - - # - # Progress data of the specified user. progressDataForUser(userId: UUID!): UserProgressData! - - # - # the items that belong to the Assessment - items: [Item!]! - - # - # For the current user, returns true if this content could be worked on by the user (i.e. it is not locked), false - # if content is not available to be worked on (e.g. because previous stage has not been completed) - isAvailableToBeWorkedOn: Boolean! userProgressData: UserProgressData! } @@ -88,41 +64,15 @@ type AssessmentContentReference { } type AssessmentMetadata { - # - # Number of skill points a student receives for completing this content initialLearningInterval: Int skillPoints: Int! - - # - # Type of the assessment skillTypes: [SkillType!]! - - # - # The initial learning interval for the assessment in days. - # This is the interval that is applied after the assessment is completed the first time. - # Following intervals are calculated based on the previous interval and the user's performance. - # If this is null, the assessment will never be scheduled for review, which - # is useful for assessments that are not meant to be repeated. - initialLearningInterval: Int } input AssessmentMetadataInput { - # - # Number of skill points a student receives for completing this content initialLearningInterval: Int skillPoints: Int! - - # - # Type of the assessment skillTypes: [SkillType!]! - - # - # The initial learning interval for the assessment in days. - # This is the interval that is applied after the assessment is completed the first time. - # Following intervals are calculated based on the previous interval and the user's performance. - # If this is null, the assessment will never be scheduled for review, which - # is useful for assessments that are not meant to be repeated. - initialLearningInterval: Int } type AssessmentSemanticSearchResult implements SemanticSearchResult { @@ -131,160 +81,48 @@ type AssessmentSemanticSearchResult implements SemanticSearchResult { score: Float! } -# -# An assignment is an external source of tasks, which can be imported. This includes exercise sheets and physical tests. type Assignment { - # - # Identifier of the assignment, same as the identifier of the assessment. assessmentId: UUID! - - # - # Id of the course this assignment belongs to. assignmentType: AssignmentType! codeAssignmentMetadata: CodeAssignmentMetadata content: Content courseId: UUID! - - # - # List of exercises making up the assignment. - # Optional for CODE_ASSIGNMENT since GH Classroom does not provide exercises. - exercises: [Exercise!] - - # - # The date at which the assignment had to be handed in (optional). date: DateTime - - # - # Number of total credits in the assignment. - totalCredits: Float - - # - # Type of the assignment, e.g. exercise sheet or physical test. - assignmentType: AssignmentType! - - # - # Description of the assignment (optional). description: String - - # - # The required percentage to pass the assignment. A value between 0 and 1. Defaults to 0.5. (optional) - requiredPercentage: Float - - # - # The id of the exercise sheet in an external system like TMS. (optional) - # This is needed for mapping grading data to assignments. exercises: [Exercise!] externalId: String - - # - # CodeAssignmentMetadata contains metadata for the external code assignment. - codeAssignmentMetadata: CodeAssignmentMetadata - - # The content this assignment belongs to. - content: Content -} - -# -# An assignment, assignment related fields are stored in the assignment service. requiredPercentage: Float totalCredits: Float } type AssignmentAssessment implements Assessment & Content { - # - # Assessment metadata aiProcessingProgress: AiEntityProcessingProgress! assessmentMetadata: AssessmentMetadata! - - # - # ID of the content assignment: Assignment id: UUID! - - # - # Metadata of the content isAvailableToBeWorkedOn: Boolean! items: [Item!]! metadata: ContentMetadata! - - # - # Progress data of the content for the current user. - userProgressData: UserProgressData! - - # - # Progress data of the specified user. progressDataForUser(userId: UUID!): UserProgressData! - - # - # the items that belong to the Assignment - items: [Item!]! - - # - # For the current user, returns true if this content could be worked on by the user (i.e. it is not locked), false - # if content is not available to be worked on (e.g. because previous stage has not been completed) - isAvailableToBeWorkedOn: Boolean! - - # The assignment of the assessment. - # If this is null the system is in an inconsistent state and the assessment should be deleted. - assignment: Assignment - - # The progress of processing the assessment. In particular when processing is done, - # the assessment's task contents will have been indexed for search. - aiProcessingProgress: AiEntityProcessingProgress! - - # Tags suggested for this assessment by the AI system. suggestedTags: [String!]! userProgressData: UserProgressData! } -# -# Feedback data when "logAssignmentCompleted" is called. type AssignmentCompletedFeedback { - # - # Whether the assignment was passed or not. - success: Boolean! - - # - # The percentage of achieved credits compared to total credits. correctness: Float! success: Boolean! } type AssignmentMutation { - # - # ID of the assignment that is being modified. assessmentId: UUID! - - # - # Creates a new exercise. Throws an error if the assignment does not exist. createExercise(input: CreateExerciseInput!): Exercise! - - # - # Updates an exercise. Throws an error if the exercise does not exist. - updateExercise(input: UpdateExerciseInput!): Exercise! - - # - # Deletes the exercise with the specified ID. Throws an error if the exercise does not exist. - deleteExercise(itemId: UUID!): UUID! - - # - # Creates a new subexercise. Throws an error if the assignment does not exist. createSubexercise(input: CreateSubexerciseInput!): Subexercise! - - # - # Updates a subexercise. Throws an error if the subexercise does not exist. - updateSubexercise(input: UpdateSubexerciseInput!): Subexercise! - - # - # Deletes the subexercise with the specified ID. Throws an error if the subexercise does not exist. deleteExercise(itemId: UUID!): UUID! deleteSubexercise(itemId: UUID!): UUID! updateExercise(input: UpdateExerciseInput!): Exercise! updateSubexercise(input: UpdateSubexerciseInput!): Subexercise! } -# -# The type of assignment. enum AssignmentType { CODE_ASSIGNMENT EXERCISE_SHEET @@ -292,70 +130,25 @@ enum AssignmentType { } input AssociationInput { - # - # id of the corresponding item feedback: JSON itemId: UUID - - # - # Text of the left side of the association, in SlateJS JSON format. left: String! - - # - # Text of the right side of the association, in SlateJS JSON format. right: String! - - # - # Feedback for the association when the user selects a wrong answer, in SlateJS JSON format. - feedback: JSON } -# -# Association question, i.e., a question where the user has to assign the correct right side to each left side. type AssociationQuestion implements Question { - # - # Text to display above the association question, in SlateJS JSON format. - text: JSON! - - # - # List of correct associations. aiGenerated: Boolean! correctAssociations: [SingleAssociation!]! - - # - # Computed list of all the left sides of the associations, shuffled. - leftSide: [String!]! - - # - # Computed list of all the right sides of the associations, shuffled. - rightSide: [String!]! - - # - # Unique identifier of the question and the id of the corresponding item hint: JSON item: Item! itemId: UUID! - - # - # Number of the question, i.e., the position of the question in the list of questions. - # Only relevant if questionPoolingMode is ORDERED. leftSide: [String!]! number: Int! - - # - # Type of the question. rightSide: [String!]! text: JSON! type: QuestionType! - - # - # Optional hint for the question, in SlateJS JSON format. - hint: JSON - item: Item! } -# -# Level of Blooms Taxonomy enum BloomLevel { ANALYZE APPLY @@ -365,69 +158,19 @@ enum BloomLevel { UNDERSTAND } -# -# A chapter is a part of a course. type Chapter { - # - # UUID of the chapter, generated automatically - id: UUID! - - # - # Title of the chapter, maximum length is 255 characters. - title: String! - - # - # Description of the chapter, maximum length is 3000 characters. achievableSkillTypes: [SkillType]! contents: [Content!]! contentsWithNoSection: [Content!]! course: Course! description: String! - - # - # Number of the chapter, determines the order of the chapters. endDate: DateTime! id: UUID! number: Int! - - # - # Start date of the chapter, ISO 8601 format. sections: [Section!]! skills: [Skill]! startDate: DateTime! - - # - # End date of the chapter, ISO 8601 format. - endDate: DateTime! - - # - # Suggested Start date to start the chapter, ISO 8601 format. - # Must be after Start Date and before the End dates. - suggestedStartDate: DateTime - - # - # Suggested End date of the chapter, ISO 8601 format. - # Must be after the Start Dates and before the End dates. suggestedEndDate: DateTime - - # - # The course the chapter belongs to. - course: Course! - - # Contents of this chapter. - contents: [Content!]! - - # Contents of this chapter which are not in any section. - contentsWithNoSection: [Content!]! - - # Sections of this chapter. - sections: [Section!]! - - # The skill types which are achievable in this chapter. - # A skill type is achievable if there is at least one assessment in this chapter with this skill type. - achievableSkillTypes: [SkillType]! - - # The progress of the current user in this chapter. suggestedStartDate: DateTime title: String! userProgress: CompositeProgressInformation! @@ -446,40 +189,20 @@ input ChapterFilter { title: StringFilter } -# -# Return type of the chapters query, contains a list of chapters and pagination info. type ChapterPayload { elements: [Chapter!]! pagination: PaginationInfo! } type ClozeBlankElement { - # - # The correct answer for the blank. correctAnswer: String! - - # - # Feedback for the blank when the user selects a wrong answer, in SlateJS JSON format. feedback: JSON } union ClozeElement = ClozeTextElement | ClozeBlankElement input ClozeElementInput { - # - # Type of the element. - type: ClozeElementType! - - # - # Text of the element. Only used for TEXT type. - text: JSON - - # - # The correct answer for the blank. Only used for BLANK type. correctAnswer: String - - # - # Feedback for the blank when the user selects a wrong answer, in SlateJS JSON format. Only used for BLANK type. feedback: JSON text: JSON type: ClozeElementType! @@ -491,92 +214,36 @@ enum ClozeElementType { } type ClozeQuestion implements Question { - # - # The elements of the cloze question. - clozeElements: [ClozeElement!]! - - # - # Addtional wrong answers for the blanks. additionalWrongAnswers: [String!]! - - # - # All selectable answers for the blanks (computed). This contains the correct answers as well as wrong answers. aiGenerated: Boolean! allBlanks: [String!]! - - # - # Whether the blanks must be answered in free text or by selecting the correct answer from a list. - showBlanksList: Boolean! - - # - # Unique identifier of the question and the id of the corresponding item clozeElements: [ClozeElement!]! hint: JSON item: Item! itemId: UUID! - - # - # Number of the question, i.e., the position of the question in the list of questions. - # Only relevant if questionPoolingMode is ORDERED. number: Int! - - # - # Type of the question. showBlanksList: Boolean! type: QuestionType! - - # - # Optional hint for the question, in SlateJS JSON format. - hint: JSON - item: Item! } type ClozeTextElement { - # - # Text of the element, in SlateJS JSON format. text: JSON! } type CodeAssignmentGradingMetadata { - # - # The repository link of for the external code assignment. feedbackTableHtml: String repoLink: String - - # - # The pipeline status of the corresponding repository. status: String - - # - # The Github worfklow run log table HTML of the corresponding repository. - feedbackTableHtml: String } type CodeAssignmentMetadata { - # - # Link to the GitHub Classroom or equivalent (optional, CODE_ASSIGNMENT only). assignmentLink: String - - # - # Invitation link for students to join the assignment (optional, CODE_ASSIGNMENT only). invitationLink: String - - # - # README content in HTML format for the assignment (optional, CODE_ASSIGNMENT only). readmeHtml: String } type CompositeProgressInformation { - # - # percentage of completedContents/totalContents - progress: Float! - - # - # absolut number of completed content completedContents: Int! - - # - # absolut number of total content progress: Float! totalContents: Int! } @@ -586,59 +253,18 @@ directive @ContainerNotEmpty(message: String = "graphql.validation.ContainerNotE directive @ContainerSize(min: Int = 0, max: Int = 2147483647, message: String = "graphql.validation.ContainerSize.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION interface Content { - # - # ID of the content id: UUID! - - # - # Metadata of the content isAvailableToBeWorkedOn: Boolean! metadata: ContentMetadata! - - # - # Progress data of the content for the current user. - userProgressData: UserProgressData! - - # - # Progress data of the specified user. progressDataForUser(userId: UUID!): UserProgressData! - - # - # For the current user, returns true if this content could be worked on by the user (i.e. it is not locked), false - # if content is not available to be worked on (e.g. because previous stage has not been completed) - isAvailableToBeWorkedOn: Boolean! userProgressData: UserProgressData! } type ContentMetadata { - # - # Name of the content - name: String! - - # - # Content type - type: ContentType! - - # - # Suggested date when the content should be done - suggestedDate: DateTime! - - # - # Number of reward points a student receives for completing this content - rewardPoints: Int! - - # - # ID of the chapter this content is associated with chapter: Chapter! chapterId: UUID! - - # - # ID of the course this content is associated with course: Course! courseId: UUID! - - # - # TagNames this content is tagged with name: String! rewardPoints: Int! suggestedDate: DateTime! @@ -647,46 +273,19 @@ type ContentMetadata { } type ContentMutation { - # - # Identifier of Content addTagToContent(tagName: String): Content! contentId: UUID! - - # - # Update an existing Content - updateMediaContent(input: UpdateMediaContentInput!): MediaContent! - - # - # Update an existing Assessment - updateAssessment(input: UpdateAssessmentInput!): Assessment! - - # - # Delete an existing Content, throws an error if no Content with the given id exists deleteContent: UUID! - - # - # Add a tag to an existing content - addTagToContent(tagName: String): Content! - - # - # Remove a tag from an existing content removeTagFromContent(tagName: String): Content! updateAssessment(input: UpdateAssessmentInput!): Assessment! updateMediaContent(input: UpdateMediaContentInput!): MediaContent! } type ContentPayload { - # - # the contents elements: [Content!]! - - # - # pagination info pageInfo: PaginationInfo! } -# -# Type of the content enum ContentType { ASSIGNMENT FLASHCARDS @@ -694,68 +293,10 @@ enum ContentType { QUIZ } -# -# Courses are the main entity of the application. They are the top level of the -# hierarchy and contain chapters. type Course { - # - # UUID of the course. Generated automatically when creating a new course. - id: UUID! - - # - # Title of the course. Maximal length is 255 characters, must not be blank. - title: String! - - # - # Detailed description of the course. Maximal length is 3000 characters. chapters(filter: ChapterFilter, pagination: Pagination, sortBy: [String!]! = [], sortDirection: [SortDirection!]! = [ASC]): ChapterPayload! description: String! - - # - # Start date of the course, ISO 8601 format. - # Users can only access the course and work on course content after the start date. - # Must be before the end date. - startDate: DateTime! - - # - # End date of the course, ISO 8601 format. - # Users can no longer access the course and work on course content after the end date. - # Must be after the start date. endDate: DateTime! - - # - # Published state of the course. If the course is published, it is visible to users. - published: Boolean! - - # - # The year in which the term starts. - startYear: Int - - # - # The division of the academic calendar in which the term takes place. - yearDivision: YearDivision - - # - # Chapters of the course. Can be filtered and sorted. - # 🔒 User needs to be enrolled in the course to access this field. - chapters( - filter: ChapterFilter - - # - # The fields to sort by. The default sort order is by chapter number. - # Throws an error if no field with the given name exists. - sortBy: [String!]! = [] - - # - # The sort direction for each field. If not specified, defaults to ASC. - sortDirection: [SortDirection!]! = [ASC] - pagination: Pagination - ): ChapterPayload! - - # - # Course Memberships of this course. Contains information about which users are members of the course and what - # role they have in it. - # 🔒 User needs to be at least an admin of the course to access this field. id: UUID! mediaRecords: [MediaRecord!]! memberships: [CourseMembership!]! @@ -763,11 +304,6 @@ type Course { rewardScores: RewardScores! scoreboard: [ScoreboardItem!]! skills: [Skill!]! -} - -# -# Input type for filtering courses. All fields are optional. -# If multiple filters are specified, they are combined with AND (except for the or field). startDate: DateTime! startYear: Int suggestions(amount: Int!, skillTypes: [SkillType!]! = []): [Suggestion!]! @@ -787,101 +323,35 @@ input CourseFilter { title: StringFilter } -# -# Represents a course membership object of a user. Each user can be a member of -# set of courses and some users can also own courses type CourseMembership { - # - # Id of the user. - userId: UUID! - - # - # Id of the course the user is a member of. course: Course! courseId: UUID! - - # - # The role of the user in the course. role: UserRoleInCourse! - - # - # Course of the Course Membership - course: Course! - - # The user of this course membership. user: PublicUserInfo userId: UUID! } -# -# Represents a course membership input object of a user. input CourseMembershipInput { - # - # Id of the user. - userId: UUID! - - # - # Id of the course the user is a member of. courseId: UUID! - - # - # The role of the user in the course. role: UserRoleInCourse! userId: UUID! } -# -# Return type for the course query. Contains the course and the pagination info. type CoursePayload { elements: [Course!]! pagination: PaginationInfo! } input CreateAssessmentInput { - # - # Metadata for the new Content - metadata: CreateContentMetadataInput! - - # - # Assessment metadata assessmentMetadata: AssessmentMetadataInput! - - # - # items of the new assessments items: [CreateItemInput!] metadata: CreateContentMetadataInput! } input CreateAssignmentInput { - # - # Number of total credits in the assignment. Optional for CODE_ASSIGNMENT. - # Can be set later when grades are available. - totalCredits: Float - - # - # List of exercises in this Assignment - # Optional for CODE_ASSIGNMENT since GH Classroom does not provide exercises. - exercises: [CreateExerciseInput!] - - # - # Type of the assignment, e.g. exercise sheet or physical test. assignmentType: AssignmentType! - - # - # The date at which the assignment had to be handed in (optional). date: DateTime - - # - # Description of the assignment (optional). description: String - - # - # The required percentage to pass the assignment. A value between 0 and 1. Defaults to 0.5. (optional) - requiredPercentage: Float - - # - # The id of the exercise sheet in an external system like TMS. (optional) - # This is needed for mapping grading data to assignments. exercises: [CreateExerciseInput!] externalId: String requiredPercentage: Float @@ -889,43 +359,17 @@ input CreateAssignmentInput { } input CreateAssociationInput { - # - # Text of the left side of the association, in SlateJS JSON format. feedback: JSON left: String! - - # - # Text of the right side of the association, in SlateJS JSON format. right: String! - - # - # Feedback for the association when the user selects a wrong answer, in SlateJS JSON format. - feedback: JSON } input CreateAssociationQuestionInput { - # - # id of the corresponding item correctAssociations: [AssociationInput!]! hint: JSON itemId: UUID! - - # - # Number of the question, used for ordering. - # This can be omitted, in which case a number, one higher than the highest number of the existing questions, will be used. number: Int - - # - # Text of the question, in SlateJS JSON format. text: JSON! - - # - # List of associations. - correctAssociations: [AssociationInput!]! - - # - # Optional hint for the question, in SlateJS JSON format. - hint: JSON } input CreateAssociationQuestionInputWithoutItem { @@ -935,99 +379,31 @@ input CreateAssociationQuestionInputWithoutItem { text: JSON! } -# -# Input type for creating chapters. input CreateChapterInput { - # - # Title of the chapter, maximum length is 255 characters, must not be blank. - title: String! - - # - # Description of the chapter, maximum length is 3000 characters. courseId: UUID! description: String! - - # - # Number of the chapter, determines the order of the chapters, must be positive. endDate: DateTime! number: Int! - - # - # Start date of the chapter, ISO 8601 format. - # Must be before the end date. startDate: DateTime! - - # - # End date of the chapter, ISO 8601 format. - # Must be after the start date. - endDate: DateTime! - - # - # Suggested Start date to start the chapter, ISO 8601 format. - # Must be after Start Date and before the End dates. - suggestedStartDate: DateTime - - # - # Suggested End date of the chapter, ISO 8601 format. - # Must be after the Start Dates and before the End dates. suggestedEndDate: DateTime - - # - # ID of the course the chapter belongs to. - # Must be a UUID of an existing course. - courseId: UUID! suggestedStartDate: DateTime title: String! } input CreateClozeElementInput { - # - # Type of the element. - type: ClozeElementType! - - # - # Text of the element. Only used for TEXT type. - text: JSON - - # - # The correct answer for the blank. Only used for BLANK type. correctAnswer: String - - # - # Feedback for the blank when the user selects a wrong answer, in SlateJS JSON format. Only used for BLANK type. feedback: JSON text: JSON type: ClozeElementType! } input CreateClozeQuestionInput { - # - # id of the corresponding item additionalWrongAnswers: [String!]! = [] clozeElements: [ClozeElementInput!]! hint: JSON itemId: UUID! - - # - # Number of the question, used for ordering. - # This can be omitted, in which case a number, one higher than the highest number of the existing questions, will be used. number: Int - - # - # List of cloze elements. - clozeElements: [ClozeElementInput!]! - - # - # List of additional wrong answers. - additionalWrongAnswers: [String!]! = [] - - # - # If true, the list of possible answers will be shown to the user. showBlanksList: Boolean! = true - - # - # Optional hint for the question, in SlateJS JSON format. - hint: JSON } input CreateClozeQuestionInputWithoutItem { @@ -1039,98 +415,28 @@ input CreateClozeQuestionInputWithoutItem { } input CreateContentMetadataInput { - # - # Name of the content chapterId: UUID! name: String! - - # - # Type of the content - type: ContentType! - - # - # Suggested date when the content should be done - suggestedDate: DateTime! - - # - # Number of reward points a student receives for completing this content rewardPoints: Int! - - # - # ID of the chapter this content is associated with - chapterId: UUID! - - # - # TagNames this content is tagged with suggestedDate: DateTime! tagNames: [String!]! = [] type: ContentType! } -# -# Input type for creating a new course. See also on the course type for detailed field descriptions. input CreateCourseInput { - # - # Title of the course, max 255 characters, must not be blank. - title: String! - - # - # Description of the course, max 3000 characters. description: String! - - # - # Start date of the course, ISO 8601 format. - # Must be before the end date. - startDate: DateTime! - - # - # End date of the course, ISO 8601 format. - # Must be after the start date. endDate: DateTime! - - # - # Published status of the course. published: Boolean! - - # - # The year in which the term starts. startDate: DateTime! startYear: Int - - # - # The division of the academic calendar in which the term takes place. title: String! yearDivision: YearDivision } input CreateExactAnswerQuestionInput { - # - # id of the corresponding item - itemId: UUID - - # - # Number of the question, used for ordering. - # This can be omitted, in which case a number, one higher than the highest number of the existing questions, will be used. - number: Int - - # - # Text of the question, in SlateJS JSON format. - text: JSON! - - # - # If the answer is case sensitive. If true, the answer is checked case sensitive. caseSensitive: Boolean! = false - - # - # A list of possible correct answers. correctAnswers: [String!]! - - # - # Feedback for the question when the user enters a wrong answer, in SlateJS JSON format. feedback: JSON - - # - # Optional hint for the question, in SlateJS JSON format. hint: JSON itemId: UUID number: Int @@ -1147,32 +453,14 @@ input CreateExactAnswerQuestionInputWithoutItem { } input CreateExerciseInput { - # - # the id of the item the exercise belongs to itemId: UUID! - - # - # The amount of credits that can be earned on this exercise including all sub-exercises. (Positive or zero) - totalExerciseCredits: Float! - - # - # Sub-exercises making up the exercise, i.e. parts a),b),c),... - subexercises: [CreateSubexerciseInput!]! - - # - # The number of the exercise on the exercise sheet, may be something such as 2 (optional). number: String subexercises: [CreateSubexerciseInput!]! totalExerciseCredits: Float! } input CreateFlashcardInput { - # - # id of the item the flashcard belongs to itemId: UUID - - # - # List of sides of this flashcard. Must be at least two sides. sides: [FlashcardSideInput!]! } @@ -1181,8 +469,6 @@ input CreateFlashcardInputWithoutItem { } input CreateFlashcardSetInput { - # - # List of flashcards in this set. flashcards: [CreateFlashcardInput!]! } @@ -1191,52 +477,22 @@ input CreateItemInput { associatedSkills: [CreateSkillInput!]! } -# -# Input for creating new media content. Media specific fields are stored in the Media Service. input CreateMediaContentInput { - # - # Metadata for the new Content metadata: CreateContentMetadataInput! } input CreateMediaRecordInput { - # - # Name of the media record. Cannot be blank, maximum length 255 characters. contentIds: [UUID!]! name: String! - - # - # Type of the media record. type: MediaType! - - # - # IDs of the MediaContents this media record is associated with - contentIds: [UUID!]! } input CreateMultipleChoiceQuestionInput { - # - # UUID of the question to update and the id of the corresponding item. answers: [MultipleChoiceAnswerInput!]! hint: JSON itemId: UUID! - - # - # Number of the question, used for ordering. - # This can be omitted, in which case a number, one higher than the highest number of the existing questions, will be used. number: Int - - # - # Text of the question, in SlateJS JSON format. text: JSON! - - # - # List of answers. - answers: [MultipleChoiceAnswerInput!]! - - # - # Optional hint for the question, in SlateJS JSON format. - hint: JSON } input CreateMultipleChoiceQuestionInputWithoutItem { @@ -1247,37 +503,13 @@ input CreateMultipleChoiceQuestionInputWithoutItem { } input CreateNumericQuestionInput { - # - # id of the corresponding item correctAnswer: Float! feedback: JSON hint: JSON itemId: UUID! - - # - # Number of the question, used for ordering. - # This can be omitted, in which case a number, one higher than the highest number of the existing questions, will be used. number: Int - - # - # Text of the question, in SlateJS JSON format. text: JSON! - - # - # The correct answer for the question. - correctAnswer: Float! - - # - # The allowed deviation from the correct answer. tolerance: Float! - - # - # Feedback for the question when the user enters a wrong answer, in SlateJS JSON format. - feedback: JSON - - # - # Optional hint for the question, in SlateJS JSON format. - hint: JSON } input CreateNumericQuestionInputWithoutItem { @@ -1290,62 +522,21 @@ input CreateNumericQuestionInputWithoutItem { } input CreateQuizInput { - # - # Threshold of the quiz, i.e., how many questions the user has to answer correctly to pass the quiz. - # - # If this is greater than the number of questions, the behavior is the same - # as if it was equal to the number of questions. - requiredCorrectAnswers: Int! - - # - # Question pooling mode of the quiz. - questionPoolingMode: QuestionPoolingMode! - - # - # Number of questions that are randomly selected from the list of questions. - # Should only be set if questionPoolingMode is RANDOM. - # - # If this is greater than the number of questions, the behavior is the same - # as if it was equal to the number of questions. - # - # If this is null or not set, the behavior is the same as if it was equal to the number of questions. numberOfRandomlySelectedQuestions: Int questionPoolingMode: QuestionPoolingMode! requiredCorrectAnswers: Int! } input CreateSectionInput { - # - # Chapter Section will belong to chapterId: UUID! - - # - # name given to Section name: String! } input CreateSelfAssessmentQuestionInput { - # - # id of the corresponding item hint: JSON itemId: UUID! - - # - # Number of the question, used for ordering. - # This can be omitted, in which case a number, one higher than the highest number of the existing questions, will be used. number: Int - - # - # Text of the question, in SlateJS JSON format. - text: JSON! - - # - # A possible correct answer to the question. solutionSuggestion: JSON! - - # - # Optional hint for the question, in SlateJS JSON format. - hint: JSON text: JSON! } @@ -1363,49 +554,23 @@ input CreateSkillInput { } input CreateStageInput { - # - # updated List of UUIDs for content labeled as required in this Stage - requiredContents: [UUID!]! - - # - # updated List of UUIDs for content labeled as optional in this Stage optionalContents: [UUID!]! requiredContents: [UUID!]! } input CreateSubexerciseInput { - # - # the id of the item the subexercise belongs to itemId: UUID! - - # - # the id of the exercise this subexercise belongs to number: String parentExerciseId: UUID! - - # - # The amount of credits that can be earned on this sub-exercise. (Positive or zero) totalSubexerciseCredits: Float! - - # - # The number of the exercise on the exercise sheet, may be something such as 2b (optional). - number: String } scalar Date scalar DateTime -# -# Filter for date values. -# If multiple filters are specified, they are combined with AND. input DateTimeFilter { - # - # If specified, filters for dates after the specified value. after: DateTime - - # - # If specified, filters for dates before the specified value. before: DateTime } @@ -1423,15 +588,6 @@ type DocumentRecordSegment implements MediaRecordSegment { text: String! thumbnail: String! title: String - - # The media record this segment is part of. - mediaRecord: MediaRecord! -} - -# -# A question with a clear, correct answer that can be automatically checked. -# Differs from self-assessment questions in that the user has to enter one of the correct answers and -# the answer is checked automatically. } type DocumentSource implements Source { @@ -1441,108 +597,39 @@ type DocumentSource implements Source { } type ExactAnswerQuestion implements Question { - # - # Text of the question, in SlateJS JSON format. - text: JSON! - - # - # A list of possible correct answers. The user has to enter one of these answers. - correctAnswers: [String!]! - - # - # If the answer is case sensitive. If true, the answer is checked case sensitive. aiGenerated: Boolean! caseSensitive: Boolean! - - # - # Feedback for the question when the user enters a wrong answer, in SlateJS JSON format. correctAnswers: [String!]! feedback: JSON - - # - # Unique identifier of the question and the id of the corresponding item hint: JSON item: Item! itemId: UUID! - - # - # Number of the question, i.e., the position of the question in the list of questions. - # Only relevant if questionPoolingMode is ORDERED. number: Int! - - # - # Type of the question. text: JSON! type: QuestionType! - - # - # Optional hint for the question, in SlateJS JSON format. - hint: JSON - item: Item! } type Exercise { - # - # Unique identifier of the exercise and the id of the corresponding item itemId: UUID! - - # - # The amount of credits that can be earned on this exercise including all sub-exercises. - totalExerciseCredits: Float! - - # - # Sub-exercises making up the exercise, i.e. parts a),b),c),... - subexercises: [Subexercise!]! - - # - # The number of the exercise on the exercise sheet, may be something such as 2 (optional). number: String - - # - # Feedback given by a tutor on the exercise (optional). subexercises: [Subexercise!]! totalExerciseCredits: Float! tutorFeedback: String } input ExerciseCompletedInput { - # - # ID of the exercise. - itemId: UUID! - - # - # The absolute number of achieved credits. achievedCredits: Float! - - # - # List of subexercises that were completed in the exercise. Can be empty, if there are no subexercises within the exercise. completedSubexercises: [SubexerciseCompletedInput]! itemId: UUID! } type ExerciseGrading { - # - # ID of the exercise. achievedCredits: Float! itemId: UUID! - - # - # ID of the student the exercise-grading belongs to. studentId: UUID! - - # - # The absolute number of achieved credits on the exercise. - achievedCredits: Float! - - # - # List of subexercise-gradings for each subexercise in the exercise. Can be - # empty, if there are no subexercises within the exercise. subexerciseGradings: [SubexerciseGrading]! } -# -# An external Assignment such as the ones from TMS. These are needed for mapping -# Meitrex Assignments to external ones for importing gradings. directive @experimental_disableErrorPropagation on QUERY | MUTATION | SUBSCRIPTION type ExternalAssignment { @@ -1551,13 +638,7 @@ type ExternalAssignment { } type ExternalCourse { - # - # The name of the course. courseTitle: String! - - # - # The url to the course. - organizationName: String! url: String! } @@ -1570,39 +651,14 @@ type ExternalUserIdWithUser { userId: UUID! } -# -# A flashcard is a set of two or more sides. Each side has a label and a text. -# The label is used to specify which side of the flashcard is being shown to the user first for learning -# and which sides he has to guess. type Flashcard { - # - # Unique identifier of this flashcard, which is the id of the corresponding item item: Item! itemId: UUID! - - # - # List of sides of this flashcard. sides: [FlashcardSide!]! - - # - # Progress data of the flashcard, specific to given users. - # If userId is not provided, the progress data of the current user is returned. userProgressData: FlashcardProgressData! } -# -# Feedback for the logFlashcardLearned mutation. type FlashcardLearnedFeedback { - # - # Whether the flashcard was learned correctly. - success: Boolean! - - # - # Next date when the flashcard should be learned again. - nextLearnDate: DateTime! - - # - # Progress of the whole flashcard set. flashcardSetProgress: FlashcardSetProgress! nextLearnDate: DateTime! success: Boolean! @@ -1613,142 +669,50 @@ type FlashcardOutput { } type FlashcardProgressData { - # - # The date the user learned the flashcard. - # This is null it the user has not learned the content item once. lastLearned: DateTime - - # - # The learning interval in days for the content item. learningInterval: Int - - # - # The next time the content should be learned. - # Calculated using the date the user completed the content item and the learning interval. - # This is null if the user has not completed the content item once. nextLearn: DateTime } type FlashcardProgressDataLog { - # - # The id of the Log id: UUID - - # - # The date the user learned the flashcard. learnedAt: DateTime! - - # - # Whether the user knew the flashcard or not. success: Boolean! } -# -# A set of flashcards. A flashcard set belongs to exactly one assessment. Therefore, the uuid of the assessment -# also serves as the identifier of a flashcard set. type FlashcardSet { - # - # The uuid of the assessment this flashcard set belongs to. - # This also serves as the identifier of this flashcard set. assessmentId: UUID! - - # - # Id of the course this flashcard set belongs to. content: Content courseId: UUID! - - # - # List of flashcards in this set. flashcards: [Flashcard!]! } -# -# A set of flashcards, flashcard related fields are stored in the flashcard service. -type FlashcardSetAssessment implements Assessment & Content { - # - # Assessment metadata - aiProcessingProgress: AiEntityProcessingProgress! - assessmentMetadata: AssessmentMetadata! - - # - # ID of the content - flashcardSet: FlashcardSet - id: UUID! - - # - # Metadata of the content - isAvailableToBeWorkedOn: Boolean! - items: [Item!]! - metadata: ContentMetadata! - - # - # Progress data of the content for the current user. - userProgressData: UserProgressData! - - # - # Progress data of the specified user. - progressDataForUser(userId: UUID!): UserProgressData! - - # - # the items that belong to the Flashcard - items: [Item!]! - - # - # For the current user, returns true if this content could be worked on by the user (i.e. it is not locked), false - # if content is not available to be worked on (e.g. because previous stage has not been completed) - isAvailableToBeWorkedOn: Boolean! - - # The FlashcardSet of the assessment. - flashcardSet: FlashcardSet - - # The progress of processing the assessment. In particular when processing is done, - # the assessment's task contents will have been indexed for search. +type FlashcardSetAssessment implements Assessment & Content { aiProcessingProgress: AiEntityProcessingProgress! - - # Tags suggested for this assessment by the AI system. + assessmentMetadata: AssessmentMetadata! + flashcardSet: FlashcardSet + id: UUID! + isAvailableToBeWorkedOn: Boolean! + items: [Item!]! + metadata: ContentMetadata! + progressDataForUser(userId: UUID!): UserProgressData! suggestedTags: [String!]! userProgressData: UserProgressData! } type FlashcardSetMutation { - # - # ID of the flashcard set that is being modified. assessmentId: UUID! - - # - # Deletes the flashcard with the specified ID. Throws an error if the flashcard does not exist. createFlashcard(assessmentId: UUID!, flashcardInput: CreateFlashcardInputWithoutItem!, item: CreateItemInput!): FlashcardOutput! deleteFlashcard(id: UUID!): UUID! updateFlashcard(assessmentId: UUID!, flashcardInput: UpdateFlashcardInput!, item: ItemInput!): FlashcardOutput! } type FlashcardSetProgress { - # - # Percentage of how many flashcards in the set have been learned. - percentageLearned: Float! - - # - # Percentage of how many flashcards have been learned correctly of the ones that have been learned. correctness: Float! percentageLearned: Float! } type FlashcardSide { - # - # Text of this flashcard side as rich text in SlateJS json. - text: JSON! - - # - # Label of this flashcard side. E.g. "Front" or "Back", or "Question" or "Answer". - label: String! - - # - # Whether this side is a question, i.e. should be shown to the user to guess the other sides or not. - isQuestion: Boolean! - - # - # Whether this side is also an answer. Some Flashcards can have their sides be - # used as both questions or answers for the other sides isAnswer: Boolean! isQuestion: Boolean! label: String! @@ -1756,25 +720,10 @@ type FlashcardSide { } input FlashcardSideInput { - # - # Text of this flashcard side. isAnswer: Boolean! isQuestion: Boolean! label: String! text: JSON! - - # - # Label of this flashcard side. E.g. "Front" or "Back", or "Question" or "Answer". - label: String! - - # - # Whether this side is a question, i.e. should be shown to the user to guess the other sides or not. - isQuestion: Boolean! - - # - # Whether this side is also an answer. Some Flashcards can have their sides be - # used as both questions or answers for the other sides - isAnswer: Boolean! } type Forum { @@ -1811,41 +760,14 @@ enum GlobalUserRole { SUPER_USER } -# -# A grading contains a user's achieved credits on an assignment and its exercises and subexercises. type Grading { - # - # ID of the assignment. achievedCredits: Float assessmentId: UUID! - - # - # ID of the student the grading belongs to. codeAssignmentGradingMetadata: CodeAssignmentGradingMetadata date: DateTime exerciseGradings: [ExerciseGrading!] student: PublicUserInfo studentId: UUID! - - # - # The date and time of when the tutor corrected the assignment. - date: DateTime - - # - # The absolute number of achieved credits on the assignment. - achievedCredits: Float - - # - # CodeAssignmentGradingMetadata contains metadata for the external code assignment grading. - codeAssignmentGradingMetadata: CodeAssignmentGradingMetadata - - # - # List of exercise-gradings for each exercise in the assignment. Can be empty, - # if there are no exercises within the assignment. - exerciseGradings: [ExerciseGrading!] - - # The user this grading belongs to. - student: PublicUserInfo } interface HasGoal { @@ -1872,9 +794,6 @@ input IngestMediaRecordInput { id: UUID! } -# -# Filter for integer values. -# If multiple filters are specified, they are combined with AND. input InputForum { courseId: UUID! id: UUID! @@ -1915,34 +834,11 @@ input InputThreadContentReferenceOnCreate { } input IntFilter { - # - # An integer value to match exactly. equals: Int - - # - # If specified, filters for values greater than to the specified value. greaterThan: Int - - # - # If specified, filters for values less than to the specified value. lessThan: Int } -# -# An item is a part of an assessment. Based on students' performances on items the -# SkillLevel Service estimates a students knowledge. -# An item is something like a question in a quiz, a flashcard of a flashcard set. -type Item { - # - # the id of the item - id: UUID! - - # - # The skills or the competencies the item belongs to. - associatedSkills: [Skill!]! - - # - # The Level of Blooms Taxonomy the item belongs to type Inventory { items: [UserItem!]! unspentPoints: Int! @@ -1956,29 +852,13 @@ type Item { } input ItemInput { - # - # might be empty if a new item is created - id: UUID - - # - # The skills or the competencies the item belongs to. - associatedSkills: [SkillInput!]! - - # - # The Level of Blooms Taxonomy the item belongs to associatedBloomLevels: [BloomLevel!]! associatedSkills: [SkillInput!]! id: UUID } type ItemProgress { - # - # the id of the corresponding item itemId: UUID! - - # - # the correctness of the users response. - # Value between 0 and 1 representing the user's correctness on the content item. responseCorrectness: Float! } @@ -1992,80 +872,32 @@ type LectureQuestionResponse { scalar LocalTime input LogAssignmentCompletedInput { - # - # ID of the assignment. - assessmentId: UUID! - - # - # The absolute number of achieved credits. achievedCredits: Float! - - # - # List of exercises that were completed in the assignment. Can be empty, if there are no exercises within the assignment. assessmentId: UUID! completedExercises: [ExerciseCompletedInput]! } input LogFlashcardLearnedInput { - # - # The id of the flashcard that was learned. flashcardId: UUID! - - # - # If the user knew the flashcard or not. successful: Boolean! } input LogFlashcardSetLearnedInput { - # - # The id of the flashcard that was learned. flashcardSetId: UUID! - - # - # The id of the user that learned the flashcard. - userId: UUID! - - # - # The percentage of flashcards in the set that the user knew. percentageSuccess: Float! userId: UUID! } -# -# An object to represent a student where the backend could not automatically map the external student to a meitrex user. type ManualMappingInstance { - # - # Student Id in external system like TMS externalStudentId: String! - - # - # JSON Object containing all available information on the external student. externalStudentInfo: String! } directive @Max(value: Int! = 2147483647, message: String = "graphql.validation.Max.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION type MediaContent implements Content { - # - # ID of the content aiProcessingProgress: AiEntityProcessingProgress! id: UUID! - - # - # Metadata of the content - metadata: ContentMetadata! - - # - # Progress data of the content for the current user. - userProgressData: UserProgressData! - - # - # Progress data of the specified user. - progressDataForUser(userId: UUID!): UserProgressData! - - # - # For the current user, returns true if this content could be worked on by the user (i.e. it is not locked), false - # if content is not available to be worked on (e.g. because previous stage has not been completed) isAvailableToBeWorkedOn: Boolean! mediaRecords: [MediaRecord!]! metadata: ContentMetadata! @@ -2075,73 +907,15 @@ type MediaContent implements Content { } type MediaRecord { - # - # ID of the media record - id: UUID! - - # - # Ids of the courses this MediaRecord is associated with aiProcessingProgress: AiEntityProcessingProgress! closedCaptions: String contentIds: [UUID!]! contents: [Content]! courseIds: [UUID!]! - - # - # Name of the media record - name: String! - - # - # User ID of the creator of the media record. creatorId: UUID! - - # - # Type of the media record - type: MediaType! - - # - # IDs of the MediaContents this media record is associated with - contentIds: [UUID!]! - - # - # Temporary upload url for the media record - uploadUrl: String! - - # - # Temporary download url for the media record downloadUrl: String! - - # - # Temporary download url for the media record where, if the media record is uploaded in a non-standardized format, a - # converted version of that file is served. - # - # For documents, this is a PDF version of the document. - # - # May be NULL if no standardized version is available. - standardizedDownloadUrl: String - - # - # Temporary upload url for the media record which can only be used from within the system. - # (This is necessary because the MinIO pre-signed URLs cannot be changed, meaning we cannot use the same URL for both - # internal and external access because the hostname changes.) - internalUploadUrl: String! - - # - # Temporary download url for the media record which can only be used from within the system. - # (This is necessary because the MinIO pre-signed URLs cannot be changed, meaning we cannot use the same URL for both - # internal and external access because the hostname changes.) id: UUID! internalDownloadUrl: String! - - # - # The progress data of the given user for this medium. - userProgressData: MediaRecordProgressData! - - # Returns the contents this media record is linked to. If the user does not have access to a particular - # content, null will be returned in its place. - contents: [Content]! - - # Returns the segments this media record consists of. internalUploadUrl: String! name: String! segments: [MediaRecordSegment!]! @@ -2154,13 +928,6 @@ type MediaRecord { } type MediaRecordProgressData { - # - # Whether the medium has been worked on by the user. - workedOn: Boolean! - - # - # Date on which the medium was worked on by the user. - # This is null if the medium has not been worked on by the user. dateWorkedOn: DateTime workedOn: Boolean! } @@ -2182,8 +949,6 @@ type MediaRecordSegmentSemanticSearchResult implements SemanticSearchResult { score: Float! } -# -# The type of the media record enum MediaType { AUDIO DOCUMENT @@ -2196,85 +961,30 @@ enum MediaType { directive @Min(value: Int! = 0, message: String = "graphql.validation.Min.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION type MultipleChoiceAnswer { - # - # Text of the answer, in SlateJS JSON format. answerText: JSON! - - # - # Whether the answer is correct or not. correct: Boolean! - - # - # Feedback for when the user selects this answer, in SlateJS JSON format. feedback: JSON } input MultipleChoiceAnswerInput { - # - # Text of the answer, in SlateJS JSON format. answerText: JSON! - - # - # Whether the answer is correct or not. correct: Boolean! - - # - # Feedback for when the user selects this answer, in SlateJS JSON format. feedback: JSON } -# -# Multiple choice question, i.e., a question with multiple answers of which the user has to select the correct ones. type MultipleChoiceQuestion implements Question { - # - # Text of the question, in SlateJS JSON format. - text: JSON! - - # - # List of answers. aiGenerated: Boolean! answers: [MultipleChoiceAnswer!]! - - # - # How many answers the user has to select. This is computed from the list of answers. - numberOfCorrectAnswers: Int! - - # - # Unique identifier of the question and the id of the corresponding item hint: JSON item: Item! itemId: UUID! - - # - # Number of the question, i.e., the position of the question in the list of questions. - # Only relevant if questionPoolingMode is ORDERED. number: Int! - - # - # Type of the question. numberOfCorrectAnswers: Int! text: JSON! type: QuestionType! - - # - # Optional hint for the question, in SlateJS JSON format. - hint: JSON - item: Item! } -# -# Mutations for the assignment service. Provides mutations for creating, updating, and deleting assignments. type Mutation { - evaluatePlayerHexadScore(userId: UUID!, input: PlayerAnswerInput!): PlayerHexadScore! - - # - # Creates a new course with the given input and returns the created course. - createCourse(input: CreateCourseInput!): Course! - - # - # Creates a new chapter with the given input and returns the created chapter. - # The course id must be a course id of an existing course. - # 🔒 The user must be an admin in this course to perform this action. addPost(post: InputPost!): Post addThreadToContent(threadContentReference: InputThreadContentReference!): ThreadContentReference addUserToForum(forumId: UUID!): Forum @@ -2282,40 +992,6 @@ type Mutation { buyItem(itemId: UUID!): Inventory createAssignmentAssessment(assessmentInput: CreateAssessmentInput!, assignmentInput: CreateAssignmentInput!): AssignmentAssessment! createChapter(input: CreateChapterInput!): Chapter! - - # - # Updates an existing course with the given input and returns the updated course. - # The course id must be a course id of an existing course. - # 🔒 The user must be an admin in this course to perform this action. - updateCourse(input: UpdateCourseInput!): Course! - - # - # Updates an existing chapter with the given input and returns the updated chapter. - # The chapter id must be a chapter id of an existing chapter. - # 🔒 The user must be an admin in this course to perform this action. - updateChapter(input: UpdateChapterInput!): Chapter! - - # - # Deletes an existing course, throws an error if no course with the given id exists. - # 🔒 The user must be an admin in this course to perform this action. - deleteCourse(id: UUID!): UUID! - - # - # Deletes an existing chapter, throws an error if no chapter with the given id exists. - # 🔒 The user must be an admin in this course to perform this action. - deleteChapter(id: UUID!): UUID! - - # - # Lets the current user join a course as a student. - joinCourse(courseId: UUID!): CourseMembership! - - # - # Lets the current user leave a course. Returns the membership that was deleted. - leaveCourse(courseId: UUID!): CourseMembership! - - # - # Adds the specified user to the specified course with the specified role. - # 🔒 The calling user must be an admin in this course to perform this action. createCourse(input: CreateCourseInput!): Course! createFlashcardSetAssessment(assessmentInput: CreateAssessmentInput!, flashcardSetInput: CreateFlashcardSetInput!): FlashcardSetAssessment createForum(courseId: UUID!): Forum @@ -2323,36 +999,11 @@ type Mutation { createMediaContentAndLinkRecords(contentInput: CreateMediaContentInput!, mediaRecordIds: [UUID!]!): MediaContent! createMediaRecord(input: CreateMediaRecordInput!): MediaRecord! createMembership(input: CourseMembershipInput!): CourseMembership! - - # - # Updates a user's membership in a course with the given input. - # 🔒 The calling user must be an admin in this course to perform this action. - updateMembership(input: CourseMembershipInput!): CourseMembership! - - # - # Removes the specified user's access to the specified course. - # 🔒 The calling user must be an admin in this course to perform this action. - deleteMembership(input: CourseMembershipInput!): CourseMembership! - - # - # Generates an access token for the given provider using an authorization code obtained from the OAuth flow. - # This should be called **only after** the user completes authorization and the frontend retrieves the auth code. - # After the access token is generated, the user is redirected to the redirect URI. - generateAccessToken(input: GenerateAccessTokenInput!): Boolean! - updateSettings(userId: UUID!, input: SettingsInput!): Settings! createQuestionThread(thread: InputQuestionThread!): QuestionThread createQuizAssessment(assessmentInput: CreateAssessmentInput!, quizInput: CreateQuizInput!): QuizAssessment! createSection(input: CreateSectionInput!): Section! currencyReward(points: Int!): Inventory defaultSettings(userId: UUID!): Settings! - - # - # Modify a quiz. - # 🔒 The user must be an admin the course the quiz is in to perform this action. - mutateQuiz(assessmentId: UUID!): QuizMutation! - - # - # Delete a quiz. deleteChapter(id: UUID!): UUID! deleteCourse(id: UUID!): UUID! deleteFlashcardSet(assessmentId: UUID!): UUID! @deprecated(reason: "Only use if you specifically only want to delete the flashcard set and not the whole assessment. Otherwise, use deleteAssessment in contents service instead.") @@ -2371,121 +1022,26 @@ type Mutation { leaveCourse(courseId: UUID!): CourseMembership! logAssignmentCompleted(input: LogAssignmentCompletedInput!): AssignmentCompletedFeedback! logFlashcardLearned(input: LogFlashcardLearnedInput!): FlashcardLearnedFeedback! - - # - # ONLY FOR TESTING PURPOSES. DO NOT USE IN FRONTEND. WILL BE REMOVED. - # - # Triggers the recalculation of the skill level of the user. - # This is done automatically at some time in the night. - # - # The purpose of this mutation is to allow testing of the skill level score and demonstrate the functionality. - # 🔒 The user must be a super-user, otherwise an exception is thrown. - recalculateLevels(chapterId: UUID!, userId: UUID!): SkillLevels! @deprecated(reason: "Only for testing purposes. Will be removed.") - - # - # Modify Content - # 🔒 The user must have admin access to the course containing the section to perform this action. loginUser(courseId: UUID): UUID logMediaRecordWorkedOn(mediaRecordId: UUID!): MediaRecord! logQuizCompleted(input: QuizCompletedInput!): QuizCompletionFeedback! lotteryRun: UserItemComplete mutateAssignment(assessmentId: UUID!): AssignmentMutation! mutateContent(contentId: UUID!): ContentMutation! - - # - # Modify the section with the given id. - # 🔒 The user must have admin access to the course containing the section to perform this action. mutateFlashcardSet(assessmentId: UUID!): FlashcardSetMutation! mutateQuiz(assessmentId: UUID!): QuizMutation! mutateSection(sectionId: UUID!): SectionMutation! - - # - # Creates a new media record - # 🔒 The user must have the "course-creator" role to perform this action. - # 🔒 If the mediaRecord is associated with courses the user must be an administrator of all courses or a super-user. - createMediaRecord(input: CreateMediaRecordInput!): MediaRecord! - - # - # Updates an existing media record with the given UUID - # 🔒 If the mediaRecord is associated with courses the user must be an administrator of at least one of the courses. - updateMediaRecord(input: UpdateMediaRecordInput!): MediaRecord! - - # - # Deletes the media record with the given UUID - # 🔒 If the mediaRecord is associated with courses the user must be an administrator of at least one of the courses. - deleteMediaRecord(id: UUID!): UUID! - - # - # For a given MediaContent, sets the linked media records of it to the ones with the given UUIDs. - # This means that for the content, all already linked media records are removed and replaced by the given ones. - # 🔒 If the mediaRecord is associated with courses the user must be an administrator of at least one of the courses. recalculateLevels(chapterId: UUID!, userId: UUID!): SkillLevels! @deprecated(reason: "Only for testing purposes. Will be removed.") recalculateScores(courseId: UUID!, userId: UUID!): RewardScores! @deprecated(reason: "Only for testing purposes. Will be removed.") saveStudentMappings(courseId: UUID!, studentMappingInputs: [StudentMappingInput!]!): [String]! selectAnswer(postId: UUID!): QuestionThread! sendMessage(courseId: UUID, userInput: String!): LectureQuestionResponse! setLinkedMediaRecordsForContent(contentId: UUID!, mediaRecordIds: [UUID!]!): [MediaRecord!]! - - # - # Logs that a media has been worked on by the current user. - # See https://gits-enpro.readthedocs.io/en/latest/dev-manuals/gamification/userProgress.html - # - # Possible side effects: - # When all media records of a content have been worked on by a user, - # a user-progress event is emitted for the content. - # 🔒 If the mediaRecord is associated with courses the user must be a member of at least one of the courses. - logMediaRecordWorkedOn(mediaRecordId: UUID!): MediaRecord! - - # - # Add the MediaRecords with the given UUIDS to the Course with the given UUID. - # 🔒 If the mediaRecord is associated with courses the user must be an administrator of at least one of the courses. setMediaRecordsForCourse(courseId: UUID!, mediaRecordIds: [UUID!]!): [MediaRecord!]! - - # - # Update top-level fields of an assignment. - # 🔒 The user must be an admin in the course the assignment belongs to. setNickname(nickname: String!): UserInfo! syncAssignmentsForCourse(courseId: UUID!): Boolean! unequipItem(itemId: UUID!): Inventory updateAssignment(assessmentId: UUID!, input: UpdateAssignmentInput!): Assignment! - - # - # Modify an assignment. - # 🔒 The user must be an admin in the course the assignment is in to perform this action. - mutateAssignment(assessmentId: UUID!): AssignmentMutation! - - # - # Logs that a user's assignment score has been imported, i.e. the user has completed the assignment. - # 🔒 The user must be a tutor in the course the assignment is in to perform this action. - logAssignmentCompleted(input: LogAssignmentCompletedInput!): AssignmentCompletedFeedback! - - # - # Saves mappings of meitrex users to external students. - # Used to deal with ManualMappingInstances. - # Returns list of all deleted ManualMappingInstance ids. - # Returns null if connection to UserService failed. - # 🔒 The user must be an admin in the course to perform this action. - saveStudentMappings(courseId: UUID!, studentMappingInputs: [StudentMappingInput!]!): [String]! - - # - # Fetches assignment info from external code assessment provider for the given course - syncAssignmentsForCourse(courseId: UUID!): Boolean! - - # Creates a new media content and links the given media records to it. - createMediaContentAndLinkRecords(contentInput: CreateMediaContentInput!, mediaRecordIds: [UUID!]!): MediaContent! - - # Creates a new quiz assessment and a new, linked quiz with the given properties. - createQuizAssessment(assessmentInput: CreateAssessmentInput!, quizInput: CreateQuizInput!): QuizAssessment! - - # Creates a new flashcard set assessment and a new, linked flashcard set with the given properties. - createFlashcardSetAssessment(assessmentInput: CreateAssessmentInput!, flashcardSetInput: CreateFlashcardSetInput!): FlashcardSetAssessment - - # Creates a new assignment assessment and a new, linked assignment with the given properties. - createAssignmentAssessment(assessmentInput: CreateAssessmentInput!, assignmentInput: CreateAssignmentInput!): AssignmentAssessment! - - # Creates a new section in a chapter. - createSection(input: CreateSectionInput!): Section! -} updateChapter(input: UpdateChapterInput!): Chapter! updateCourse(input: UpdateCourseInput!): Course! updateMediaRecord(input: UpdateMediaRecordInput!): MediaRecord! @@ -2514,48 +1070,16 @@ input NotificationInput { } type NumericQuestion implements Question { - # - # Text of the question, in SlateJS JSON format. - text: JSON! - - # - # The correct answer to the question. aiGenerated: Boolean! correctAnswer: Float! - - # - # The tolerance for the correct answer. The user's answer is correct if it is within the tolerance of the correct answer. - tolerance: Float! - - # - # Feedback for the question when the user enters a wrong answer, in SlateJS JSON format. feedback: JSON - - # - # Unique identifier of the question and the id of the corresponding item hint: JSON item: Item! itemId: UUID! - - # - # Number of the question, i.e., the position of the question in the list of questions. - # Only relevant if questionPoolingMode is ORDERED. number: Int! - - # - # Type of the question. text: JSON! tolerance: Float! type: QuestionType! - - # - # Optional hint for the question, in SlateJS JSON format. - hint: JSON - item: Item! -} - -# -# Specifies the page size and page number for paginated results. } directive @OnDemand on FIELD_DEFINITION @@ -2563,41 +1087,16 @@ directive @OnDemand on FIELD_DEFINITION directive @oneOf on INPUT_OBJECT input Pagination { - # - # The page number, starting at 0. - # If not specified, the default value is 0. - # For values greater than 0, the page size must be specified. - # If this value is larger than the number of pages, an empty page is returned. page: Int! = 0 - - # - # The number of elements per page. size: Int! } -# -# Return type for information about paginated results. type PaginationInfo { - # - # The current page number. hasNext: Boolean! page: Int! - - # - # The number of elements per page. size: Int! - - # - # The total number of elements across all pages. totalElements: Int! - - # - # The total number of pages. totalPages: Int! - - # - # Whether there is a next page. - hasNext: Boolean! } directive @Pattern(regexp: String! = ".*", message: String = "graphql.validation.Pattern.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION @@ -2624,22 +1123,6 @@ type PlayerTypeScore { value: Float! } -type ProgressLogItem { - # - # The date the user completed the content item. - timestamp: DateTime! - - # - # Whether the user completed the content item successfully. - success: Boolean! - - # - # Value between 0 and 1 representing the user's correctness on the content item. - # Can be null as some contents cannot provide a meaningful correctness value. - correctness: Float! - - # - # How many hints the user used to complete the content item. directive @Positive(message: String = "graphql.validation.Positive.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION directive @PositiveOrZero(message: String = "graphql.validation.PositiveOrZero.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION @@ -2657,16 +1140,6 @@ type Post { type ProgressLogItem { correctness: Float! hintsUsed: Int! - - # - # Time in milliseconds it took the user to complete the content item. - # Can be null for contents that do not measure completion time. - timeToComplete: Int - - # - # !OPTIONAL - # the items the user has completed and the students' performance on these items - # Can be null as some contents don't contains items for assessments progressPerItem: ItemProgress! success: Boolean! timestamp: DateTime! @@ -2679,29 +1152,7 @@ type PublicUserInfo { } type Query { - getPlayerHexadScoreById(userId: UUID!): PlayerHexadScore! - PlayerHexadScoreExists(userId: UUID!): Boolean! - - # - # Get a list of courses. Can be filtered, sorted and paginated. - # Courses and their basic data can be queried by any user, even if they are not enrolled in the course. - courses( - filter: CourseFilter - - # - # The fields to sort by. - # Throws an error if no field with the given name exists. - sortBy: [String!] - - # - # The sort direction for each field. If not specified, defaults to ASC. - sortDirection: [SortDirection!]! = [ASC] - pagination: Pagination - ): CoursePayload! - - # - # Returns the courses with the given ids. - # Courses and their basic data can be queried by any user, even if they are not enrolled in the course. + _empty: String achievementsByCourseId(courseId: UUID!): [Achievement!]! achievementsByUserId(userId: UUID): [Achievement!]! contentsByChapterIds(chapterIds: [UUID!]!): [[Content!]!]! @@ -2710,20 +1161,7 @@ type Query { courseRewardScoresForUser(courseId: UUID!, userId: UUID!): RewardScores! courses(filter: CourseFilter, pagination: Pagination, sortBy: [String!], sortDirection: [SortDirection!]! = [ASC]): CoursePayload! coursesByIds(ids: [UUID!]!): [Course!]! - - # - # Gets the publicly available information for a list of users with the specified IDs. - # If a user does not exist, null is returned for that user. - findPublicUserInfos(ids: [UUID!]!): [PublicUserInfo]! - - # - # Gets the user information of the currently authorized user. currentUserInfo: UserInfo! - - # - # Gets all of the users' information for a list of users with the specified IDs. - # Only available to privileged users. - # If a user does not exist, null is returned for that user. dueFlashcardsByCourseId(courseId: UUID!): [Flashcard!]! findAssignmentsByAssessmentIds(assessmentIds: [UUID!]!): [Assignment]! findContentsByIds(ids: [UUID!]!): [Content]! @@ -2732,198 +1170,18 @@ type Query { findPublicUserInfos(ids: [UUID!]!): [PublicUserInfo]! findQuizzesByAssessmentIds(assessmentIds: [UUID!]!): [Quiz]! findUserInfos(ids: [UUID!]!): [UserInfo]! - - # - # Checks whether an access token for a given third-party provider exists and is - # still valid for the currently authenticated user. - # Returns `true` if: - # - The access token exists and is not expired, OR - # - The refresh token exists and is not expired. - isAccessTokenAvailable(provider: ExternalServiceProviderDto!): Boolean! findUserSettings(userId: UUID): Settings! findUsersSettings(usersIds: [UUID]!): [Settings]! - - # - # Get quiz by assessment ID. - # If any of the assessment IDs are not found, the corresponding quiz will be null. - # 🔒 The user must be enrolled in the course the quizzes belong to to access them. Otherwise null is returned for - # an quiz if the user has no access to it. - findQuizzesByAssessmentIds(assessmentIds: [UUID!]!): [Quiz]! - - # - # Get the reward score of the current user for the specified course. - # 🔒 The user must have access to the course with the given id to access their scores, otherwise an error is thrown. - userCourseRewardScores(courseId: UUID!): RewardScores! - - # - # Get the reward score of the specified user for the specified course. - # 🔒 The user be an admin in the course with the given courseId to perform this action. - courseRewardScoresForUser(courseId: UUID!, userId: UUID!): RewardScores! - - # - # Gets the power scores for each user in the course, ordered by power score descending. - # 🔒 The user must have access to the course with the given id to access the scoreboard, otherwise an error is thrown. - scoreboard(courseId: UUID!): [ScoreboardItem!]! - - # - # Get flashcards by their ids. - # 🔒 The user must be enrolled in the course the flashcards belong to. Otherwise an error is thrown. flashcardsByIds(itemIds: [UUID!]!): [Flashcard!]! - - # - # Get flashcard sets by their assessment ids. - # Returns a list of flashcard sets in the same order as the provided ids. - # Each element is null if the corresponding id is not found. - # 🔒 The user must be enrolled in the course the flashcard sets belong to. Otherwise for that element null is returned. - findFlashcardSetsByAssessmentIds(assessmentIds: [UUID!]!): [FlashcardSet]! - - # - # Get flashcards of a course that are due to be reviewed. - # 🔒 The user must be enrolled in the course the flashcards belong to. Otherwise an error is thrown. - dueFlashcardsByCourseId(courseId: UUID!): [Flashcard!]! - - # - # Retrieves all existing contents for a given course. - # 🔒 The user must have access to the courses with the given ids to access their contents, otherwise an error is thrown. - contentsByCourseIds(courseIds: [UUID!]!): [[Content!]!] - - # - # Get contents by ids. Throws an error if any of the ids are not found. - # 🔒 The user must have access to the courses containing the contents with the given ids to access their contents, - # otherwise an error is thrown. - contentsByIds(ids: [UUID!]!): [Content!]! - - # - # Get contents by ids. If any of the given ids are not found, the corresponding element in the result list will be null. - # 🔒 The user must have access to the courses containing the contents with the given ids, otherwise null is returned - # for the respective contents. - findContentsByIds(ids: [UUID!]!): [Content]! - - # - # Get contents by chapter ids. Returns a list containing sublists, where each sublist contains all contents - # associated with that chapter - # 🔒 The user must have access to the courses containing the chapters with the given ids, otherwise an error is thrown. - contentsByChapterIds(chapterIds: [UUID!]!): [[Content!]!]! - - # - # Generates user specific suggestions for multiple chapters. - # - # Only content that the user can access will be considered. - # The contents will be ranked by suggested date, with the most overdue or most urgent content first. - # - # 🔒 The user must have access to the courses containing the chapters with the given ids, otherwise an error is thrown. - suggestionsByChapterIds( - # - # The ids of the chapters for which suggestions should be generated. - chapterIds: [UUID!]! - - # - # The amount of suggestions to generate in total. - amount: Int! - - # - # Only suggestions for these skill types will be generated. - # If no skill types are given, suggestions for all skill types will be generated, - # also containing suggestions for media content (which do not have a skill type). - skillTypes: [SkillType!]! = [] - ): [Suggestion!]! - - # - # Returns the media records with the given IDs. Throws an error if a MediaRecord corresponding to a given ID - # cannot be found. - # - # 🔒 If the mediaRecord is associated with coursed the user must be a member of at least one of the courses. - mediaRecordsByIds(ids: [UUID!]!): [MediaRecord!]! - - # - # Like mediaRecordsByIds() returns the media records with the given IDs, but instead of throwing an error if an ID - # cannot be found, it instead returns NULL for that media record. - # - # 🔒 If the mediaRecord is associated with coursed the user must be a member of at least one of the courses. - findMediaRecordsByIds(ids: [UUID!]!): [MediaRecord]! - - # - # Returns all media records of the system. - # - # 🔒 The user must be a super-user, otherwise an exception is thrown. - mediaRecords: [MediaRecord!]! @deprecated(reason: "In production there should probably be no way to get all media records of the system.") - - # - # Returns all media records which the current user created. - # - # 🔒 If the mediaRecord is associated with coursed the user must be a member of at least one of the courses. - userMediaRecords: [MediaRecord!]! - - # - # Returns the media records associated the given content IDs as a list of lists where each sublist contains - # the media records associated with the content ID at the same index in the input list - # - # 🔒 If the mediaRecord is associated with courses the user must be a member of at least one of the courses. - mediaRecordsByContentIds(contentIds: [UUID!]!): [[MediaRecord!]!]! - - # - # Returns all media records for the given CourseIds - # - # 🔒 If the mediaRecord is associated with coursed the user must be a member of at least one of the courses. - mediaRecordsForCourses(courseIds: [UUID!]!): [[MediaRecord!]!]! - - # - # Returns all media records which were created by the users. - mediaRecordsForUsers(userIds: [UUID!]!): [[MediaRecord!]!]! - - # - # Get assignment by assessment ID. - # If any of the assessment IDs are not found, the corresponding assignment will be null. - # 🔒 The user must be enrolled in the course the assignments belong to to access them. Otherwise null is returned for - # an assignment if the user has no access to it. - findAssignmentsByAssessmentIds(assessmentIds: [UUID!]!): [Assignment]! - - # - # Get all gradings for one assignment - # 🔒 The user must be an admin in the course the assignment belongs to to access them. Otherwise null is returned for - # an assignment if the user has no access to it. - getGradingsForAssignment(assessmentId: UUID!): [Grading!]! - - # - # Gets all the available external exercises. - # CourseId is the id of the course the user is currently working in. - # 🔒 The user must be an admin in the course. Otherwise null is returned. forum(id: UUID!): Forum forumActivity(id: UUID!): [ForumActivityEntry!]! forumActivityByUserId: [ForumActivityEntry!]! forumByCourseId(id: UUID!): Forum getExternalAssignments(courseId: UUID!): [ExternalAssignment!]! - - # - # Gets all the available external code exercises. - # CourseId is the id of the course the user is currently working in. - # 🔒 The user must be an admin in the course. Otherwise null is returned. getExternalCodeAssignments(courseId: UUID!): [String!]! - - # - # Get the corresponding external course for the given courseId. - # CourseId is the id of the course the user is currently working in. - # 🔒 The user must be an admin in the course. Otherwise null is returned. getExternalCourse(courseId: UUID!): ExternalCourse - - # - # Gets all manual student mappings, i.e. all students where the backend could not map to a meitrex user. - # 🔒 The user must be an admin in the course. Otherwise null is returned. getGradingsForAssignment(assessmentId: UUID!): [Grading!]! getManualMappingInstances(courseId: UUID!): [ManualMappingInstance]! - - # Performs a semantic search with the specified search term. Returns at most `count` results. If a courseWhitelist is - # provided, only results from the specified courses will be returned. - semanticSearch(queryText: String!, count: Int! = 10, courseWhitelist: [UUID!]): [SemanticSearchResult!]! - - # Returns semantic search results of entities that are semantically similar to the entity with the specified ID. - # Returns at most `count` results. If `excludeEntitiesWithSameParent` is true, segments from the same entity as the - # specified segment will be excluded from the results. - getSemanticallySimilarEntities(segmentId: UUID!, count: Int! = 10, excludeEntitiesWithSameParent: Boolean, courseWhitelist: [UUID!]): [SemanticSearchResult!]! -} - -# -# Generic question interface. getPlayerHexadScoreById(userId: UUID!): PlayerHexadScore! getSemanticallySimilarEntities(count: Int! = 10, courseWhitelist: [UUID!], excludeEntitiesWithSameParent: Boolean, segmentId: UUID!): [SemanticSearchResult!]! inventoryForUser: Inventory! @@ -2947,37 +1205,15 @@ type Query { } interface Question { - # - # Unique identifier of the question and the id of the corresponding item aiGenerated: Boolean! hint: JSON itemId: UUID! - - # - # Number of the question, i.e., the position of the question in the list of questions. - # Only relevant if questionPoolingMode is ORDERED. number: Int! - - # - # Type of the question. type: QuestionType! - - # - # Optional hint for the question, in SlateJS JSON format. - hint: JSON } input QuestionCompletedInput { - # - # ID of the question. - questionId: UUID! - - # - # true when question was answered correctly correct: Boolean! - - # - # true when a hint was used for the question questionId: UUID! usedHint: Boolean! } @@ -2989,18 +1225,8 @@ input QuestionInput { } enum QuestionPoolingMode { - # - # Questions are randomly selected from the list of questions. ORDERED RANDOM - - # - # Questions are selected in order from the list of questions. - ORDERED -} - -# -# The type of a question. } type QuestionThread implements Thread { @@ -3024,58 +1250,15 @@ enum QuestionType { SELF_ASSESSMENT } -# -# A quiz is a set of questions that the user has to answer correctly to pass the quiz. -# Questions can be of different types, e.g., multiple choice, clozes, or open questions. type Quiz { - # - # Identifier of the quiz, same as the identifier of the assessment. assessmentId: UUID! - - # - # List of questions. content: Content courseId: UUID! numberOfRandomlySelectedQuestions: Int questionPool: [Question!]! - - # - # Threshold of the quiz, i.e., how many questions the user has to answer correctly to pass the quiz. - # If this number is greater than the number of questions, the behavior is the same - # as if it was equal to the number of questions. - requiredCorrectAnswers: Int! - - # - # Question pooling mode of the quiz. questionPoolingMode: QuestionPoolingMode! - - # - # Number of questions that are randomly selected from the list of questions. - # Will only be considered if questionPoolingMode is RANDOM. - # - # If this is greater than the number of questions, the behavior is the same - # as if it was equal to the number of questions. - # - # If this is null or not set, the behavior is the same as if it was equal to the number of questions. - numberOfRandomlySelectedQuestions: Int - - # - # The selected questions of the question pool. - # This is identical to the list of questions if questionPoolingMode is ORDERED. - # This will be different each time it is queried if questionPoolingMode is RANDOM. requiredCorrectAnswers: Int! selectedQuestions: [Question!]! - - # - # Id of the course this quiz belongs to. - courseId: UUID! - - # The content this quiz belongs to. - content: Content -} - -# -# A quiz, quiz related fields are stored in the quiz service. } type QuizAIGenAsyncResponse { @@ -3083,76 +1266,30 @@ type QuizAIGenAsyncResponse { } type QuizAssessment implements Assessment & Content { - # - # Assessment metadata aiProcessingProgress: AiEntityProcessingProgress! assessmentMetadata: AssessmentMetadata! - - # - # ID of the content id: UUID! - - # - # Metadata of the content isAvailableToBeWorkedOn: Boolean! items: [Item!]! metadata: ContentMetadata! - - # - # Progress data of the content for the current user. - userProgressData: UserProgressData! - - # - # Progress data of the specified user. progressDataForUser(userId: UUID!): UserProgressData! - - # - # the items that belong to the Quiz - items: [Item!]! - - # - # For the current user, returns true if this content could be worked on by the user (i.e. it is not locked), false - # if content is not available to be worked on (e.g. because previous stage has not been completed) - isAvailableToBeWorkedOn: Boolean! - - # The quiz of the assessment. - # If this is null the system is in an inconsistent state and the assessment should be deleted. quiz: Quiz suggestedTags: [String!]! userProgressData: UserProgressData! } input QuizCompletedInput { - # - # ID of the quiz. - quizId: UUID! - - # - # List of questions that were answered in the quiz. completedQuestions: [QuestionCompletedInput!]! quizId: UUID! } -# -# Feedback data when `logQuizCompletion` is called. type QuizCompletionFeedback { - # - # Whether the quiz was passed or not. - success: Boolean! - - # - # The number of questions that were answered correctly. correctness: Float! - - # - # The number of hints that were used. hintsUsed: Int! success: Boolean! } type QuizMutation { - # - # Id of the quiz to modify. addAssociationQuestion(assessmentId: UUID!, item: CreateItemInput!, questionInput: CreateAssociationQuestionInputWithoutItem!): QuizOutput! addClozeQuestion(assessmentId: UUID!, item: CreateItemInput!, questionInput: CreateClozeQuestionInputWithoutItem!): QuizOutput! addExactAnswerQuestion(assessmentId: UUID!, item: CreateItemInput!, questionInput: CreateExactAnswerQuestionInputWithoutItem!): QuizOutput! @@ -3161,27 +1298,7 @@ type QuizMutation { addSelfAssessmentQuestion(assessmentId: UUID!, item: CreateItemInput!, questionInput: CreateSelfAssessmentQuestionInputWithoutItem!): QuizOutput! aiGenerateQuestionAsync(context: AiGenQuestionContext): QuizAIGenAsyncResponse assessmentId: UUID! - - # - # Removes the question with the given number from the quiz. - # This will also update the numbers of the following questions. removeQuestion(number: Int!): Quiz! - - # - # Switch the position of two questions with the given numbers. - switchQuestions(firstNumber: Int!, secondNumber: Int!): Quiz! - - # - # Set the threshold of the quiz, i.e., how many questions the user has to answer correctly to pass the quiz. - setRequiredCorrectAnswers(requiredCorrectAnswers: Int!): Quiz! - - # - # Set the question pooling mode of the quiz. - setQuestionPoolingMode(questionPoolingMode: QuestionPoolingMode!): Quiz! - - # - # Set the number of questions that are randomly selected from the list of questions. - # Will only be considered if questionPoolingMode is RANDOM. setNumberOfRandomlySelectedQuestions(numberOfRandomlySelectedQuestions: Int!): Quiz! setQuestionPoolingMode(questionPoolingMode: QuestionPoolingMode!): Quiz! setRequiredCorrectAnswers(requiredCorrectAnswers: Int!): Quiz! @@ -3206,226 +1323,71 @@ directive @resolveTo(requiredSelectionSet: String, sourceName: String, sourceTyp scalar ResolveToSourceArgs -# -# The reason why the reward score has changed. enum RewardChangeReason { - # - # The user has completed a content for the first time. - # The associated contents are the content that were completed. COMPOSITE_VALUE CONTENT_DONE - - # - # The user has reviewed a content. - # The associated contents are the content that were reviewed. - CONTENT_REVIEWED - - # - # There exists a content that is due for learning. - # The associated contents are the content that are due for learning. CONTENT_DUE_FOR_LEARNING - - # - # There exists a content that is due for repetition. - # The associated contents are the content that are due for repetition. CONTENT_DUE_FOR_REPETITION - - # - # The score changed because the underlying scores changed. - # Relevant for the power score. - COMPOSITE_VALUE -} - -# -# An item in the reward score log. CONTENT_REVIEWED } type RewardLogItem { - # - # The date when the reward score changed. associatedContentIds: [UUID!]! associatedContents: [Content]! date: DateTime! - - # - # The difference between the previous and the new reward score. difference: Int! - - # - # The old reward score. - oldValue: Int! - - # - # The new reward score. newValue: Int! - - # - # The reason why the reward score has changed. oldValue: Int! reason: RewardChangeReason! - - # - # The ids of the contents that are associated with the change. - associatedContentIds: [UUID!]! - associatedContents: [Content]! } -# -# The reward score of a user. type RewardScore { - # - # The absolute value of the reward score. - # Health and fitness are between 0 and 100. - # Growth, strength and power can be any non-negative integer. - value: Int! - - # - # The relative value of the reward score. - # Shows how many points relative to the total points have been achieved. - # Only used for growth currently. - percentage: Float! - - # - # A log of the changes to the reward score, ordered by date descending. log: [RewardLogItem!]! percentage: Float! value: Int! } -# -# The five reward scores of a user. type RewardScores { - # - # Health represents how up-to-date the user is with the course. - health: RewardScore! - - # - # Fitness represents how well the user repeats previously learned content. fitness: RewardScore! - - # - # Growth represents the overall progress of the user. growth: RewardScore! - - # - # Strength is earned by competing with other users. - strength: RewardScore! - - # - # A composite score of all the other scores. health: RewardScore! power: RewardScore! strength: RewardScore! } -# -# An item in the scoreboard. type ScoreboardItem { - # - # The user id of the user. - userId: UUID! - - # - # The power score of the user. powerScore: Int! user: PublicUserInfo userId: UUID! } -# -# Representation of a Section type Section { - # - # Unique identifier of the Section Object - id: UUID! - - # - # Id of the Course the Section is located in. chapter: Chapter! chapterId: UUID! courseId: UUID! - - # - # Name of the Section id: UUID! name: String! - - # - # Chapter the Section is located in - chapterId: UUID! - - # - # List of Stages contained in a Section stages: [Stage!]! } type SectionMutation { - # - # Identifier of the section createStage(input: CreateStageInput): Stage! deleteSection: UUID! deleteStage(id: UUID!): UUID! sectionId: UUID! - - # - # update the name of a Section updateSectionName(name: String!): Section! - - # - # delete a Section by ID - deleteSection: UUID! - - # - # create new Stage in Section - createStage(input: CreateStageInput): Stage! - - # - # Update Content of Stage updateStage(input: UpdateStageInput): Stage! - - # - # delete Stage by ID - deleteStage(id: UUID!): UUID! - - # - # update Order of Stages within a Section updateStageOrder(stages: [UUID!]!): Section! } -# -# A single question with a free text answer field, where the answer is not automatically checked. -# The user has to enter a solution and self-assess whether it is correct or not. -# This is useful for questions where the answer is not clear-cut, e.g. when the user should explain a concept. type SelfAssessmentQuestion implements Question { - # - # Text of the question, in SlateJS JSON format. - text: JSON! - - # - # A possible correct answer to the question. - solutionSuggestion: JSON! - - # - # Unique identifier of the question and the id of the corresponding item aiGenerated: Boolean! hint: JSON itemId: UUID! - - # - # Number of the question, i.e., the position of the question in the list of questions. - # Only relevant if questionPoolingMode is ORDERED. number: Int! - - # - # Type of the question. solutionSuggestion: JSON! text: JSON! type: QuestionType! - - # - # Optional hint for the question, in SlateJS JSON format. - hint: JSON } interface SemanticSearchResult { @@ -3443,40 +1405,15 @@ input SettingsInput { } type SingleAssociation { - # - # The left side of the association, in SlateJS JSON format. feedback: JSON left: JSON! - - # - # The right side of the association, in SlateJS JSON format. right: JSON! - - # - # Feedback for the association when the user assigns a wrong answer, in SlateJS JSON format. - feedback: JSON } -# -# a skill or compentency. -# Something like loops or data structures. directive @Size(min: Int = 0, max: Int = 2147483647, message: String = "graphql.validation.Size.message") on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION type Skill { - # - # the id of a skill id: UUID! - - # - # the name of the skill - skillName: String! - - # - # the category of the skill - skillCategory: String! - - # - # whether the skill is a custom-created by the user and no IEEE skill isCustomSkill: Boolean! skillCategory: String! skillLevels: SkillLevels @@ -3484,102 +1421,30 @@ type Skill { } input SkillInput { - # - # the id of a skill. Field is optional, because not all required skills may - # exist, if a new item is created. If the id is empty a new skill, - # will be created id: UUID - - # - # the name of the skill - skillName: String! - - # - # the category of the skill - skillCategory: String! - - # - # whether the skill is a custom-created by the user and no IEEE skill isCustomSkill: Boolean! skillCategory: String! skillName: String! } -# -# The skill level of a user. type SkillLevel { - # - # The value of the skill level. - # levels are between 0 and 1. - value: Float! - - # - # A log of the changes to the skill level log: [SkillLevelLogItem!]! value: Float! } -# -# An item in the skill level change log. type SkillLevelLogItem { - # - # The date when the skill level changed. associatedContents: [Content]! associatedItemId: UUID! date: DateTime! - - # - # The difference between the previous and the new skill level. difference: Float! - - # - # The old skill level. - oldValue: Float! - - # - # The new skill level. newValue: Float! - - # - # The ids of the contents that are associated with the change. - associatedItemId: UUID! - - # - # the response of the user to the item - userResponse: Float! - - # - # the probability of a correct response, that M-Elo predicts oldValue: Float! predictedCorrectness: Float! userResponse: Float! } -# -# The four skill level of a user. type SkillLevels { - # - # remember represents how much user remember the concept - remember: SkillLevel - - # - # understand represents how well the user understands learned content. - understand: SkillLevel - - # - # apply represents the how well user applies the learned concept during assessment. - apply: SkillLevel - - # - # apply is how much user can evaluate information and draw conclusions analyze: SkillLevel - - # - # evaluate represent how well a user can use the learned content to evaluate - evaluate: SkillLevel - - # - # create represents how well a user can create new things based on the learned content apply: SkillLevel create: SkillLevel evaluate: SkillLevel @@ -3587,8 +1452,6 @@ type SkillLevels { understand: SkillLevel } -# -# Type of the assessment enum SkillType { ANALYZE APPLY @@ -3598,15 +1461,11 @@ enum SkillType { UNDERSTAND } -# -# Specifies the sort direction, either ascending or descending. enum SortDirection { ASC DESC } -# -# Representation of a Stage interface Source { mediaRecordId: UUID! } @@ -3614,135 +1473,51 @@ interface Source { directive @specifiedBy(url: String!) on SCALAR type Stage { - # - # Unique identifier of the Stage Object id: UUID! - - # - # Position of the Stage within the Section isAvailableToBeWorkedOn: Boolean! optionalContents: [Content!]! optionalContentsProgress: Float! position: Int! - - # - # List of Content that is labeled as required content requiredContents: [Content!]! - - # - # Percentage of User Progress made to required Content requiredContentsProgress: Float! - - # - # List of Content that is labeled as optional content - optionalContents: [Content!]! - - # - # Percentage of Progress made to optional Content - optionalContentsProgress: Float! - - # - # For the current user, returns true if this stage could be worked on by the user (i.e. it is not locked), false - # if stage is not available to be worked on (e.g. because previous stage has not been completed) - isAvailableToBeWorkedOn: Boolean! -} - -# -# Filter for string values. -# If multiple filters are specified, they are combined with AND. } input StringFilter { - # - # A string value to match exactly. - equals: String - - # - # A string value that must be contained in the field that is being filtered. contains: String - - # - # If true, the filter is case-insensitive. equals: String ignoreCase: Boolean! = false } type StudentMapping { - # - # Student Id in Meitrex - meitrexStudentId: UUID! - - # - # Student Id in external system like TMS externalStudentId: String! meitrexStudentId: UUID! } input StudentMappingInput { - # - # Student Id in Meitrex - meitrexStudentId: UUID! - - # - # Student Id in external system like TMS externalStudentId: String! meitrexStudentId: UUID! } type Subexercise { - # - # Unique identifier of the exercise and the id of the corresponding item itemId: UUID! - - # - # The amount of credits that can be earned on this sub-exercise. - totalSubexerciseCredits: Float! - - # - # The number of the exercise on the exercise sheet, may be something such as 2b (optional). number: String - - # - # Feedback given by a tutor on the exercise (optional). totalSubexerciseCredits: Float! tutorFeedback: String } input SubexerciseCompletedInput { - # - # ID of the subexercise. - itemId: UUID! - - # - # The absolute number of achieved credits. achievedCredits: Float! itemId: UUID! } type SubexerciseGrading { - # - # ID of the subexercise. achievedCredits: Float! itemId: UUID! - - # - # ID of the student the subexercise-grading belongs to. studentId: UUID! - - # - # The absolute number of achieved credits on the subexercise. - achievedCredits: Float! } -# -# Represents a suggestion for a user to learn new content or review old content. type Suggestion { - # - # The content that is suggested to the user. content: Content! - - # - # The type of suggestion. type: SuggestionType! } @@ -3770,40 +1545,15 @@ type ThreadContentReference { scalar Time -# -# An Unfinished Grading is created, when importing and parsing gradings from external systems like TMS goes wrong -# because the student id has to be mapped manually. -# After an admin mapped ids manually, these unfinished gradings will be tried again. type UnfinishedGrading { - # - # Student Id in external system like TMS - externalStudentId: String! - - # - # Assignment/HandIn id in MEITREX assignmentId: UUID! - - # - # JSON representation of the grading externalStudentId: String! gradingJson: String! - - # - # The number of times importing and parsing was tried. Might be useful for detecting and manually deleting broken gradings. numberOfTries: Int! } input UpdateAssessmentInput { - # - # Metadata for the new Content - metadata: UpdateContentMetadataInput! - - # - # Assessment metadata assessmentMetadata: AssessmentMetadataInput! - - # - # items of the new assessments items: [ItemInput!] metadata: UpdateContentMetadataInput! } @@ -3813,89 +1563,25 @@ input UpdateAssignmentInput { } input UpdateAssociationQuestionInput { - # - # UUID of the question to update and the id of the corresponding item. - itemId: UUID! - - # - # Text of the question, in SlateJS JSON format. - text: JSON! - - # - # List of associations. correctAssociations: [AssociationInput!]! - - # - # Optional hint for the question, in SlateJS JSON format. hint: JSON itemId: UUID! text: JSON! } -# -# Input type for updating chapters. -# The ID field specifies which chapter should be updated, all other fields specify the new values. input UpdateChapterInput { - # - # UUID of the chapter that should be updated. - id: UUID! - - # - # Title of the chapter, maximum length is 255 characters, must not be blank. - title: String! - - # - # Description of the chapter, maximum length is 3000 characters. description: String! - - # - # Number of the chapter, determines the order of the chapters, must be positive. endDate: DateTime! id: UUID! number: Int! - - # - # Start date of the chapter, ISO 8601 format. - # Must be before the end date. startDate: DateTime! - - # - # End date of the chapter, ISO 8601 format. - # Must be after the start date. - endDate: DateTime! - - # - # Suggested Start date to start the chapter, ISO 8601 format. - # Must be after Start Date and before the End dates. - suggestedStartDate: DateTime - - # - # Suggested End date of the chapter, ISO 8601 format. - # Must be after the Start Dates and before the End dates. suggestedEndDate: DateTime suggestedStartDate: DateTime title: String! } input UpdateClozeQuestionInput { - # - # UUID of the question to update and the id of the corresponding item. - itemId: UUID! - - # - # List of cloze elements. - clozeElements: [ClozeElementInput!]! - - # - # List of additional wrong answers. additionalWrongAnswers: [String!]! - - # - # If true, the list of possible answers will be shown to the user. - showBlanksList: Boolean! = true - - # - # Optional hint for the question, in SlateJS JSON format. clozeElements: [ClozeElementInput!]! hint: JSON itemId: UUID! @@ -3903,197 +1589,66 @@ input UpdateClozeQuestionInput { } input UpdateContentMetadataInput { - # - # Name of the content chapterId: UUID! name: String! - - # - # Date when the content should be done - suggestedDate: DateTime! - - # - # Number of reward points a student receives for completing this content rewardPoints: Int! - - # - # ID of the chapter this content is associated with - chapterId: UUID! - - # - # TagNames this content is tagged with suggestedDate: DateTime! tagNames: [String!]! = [] } -# -# Input type for updating an existing course. See also on the course type for detailed field descriptions. -# The id specifies the course that should be updated, the other fields specify the new values. input UpdateCourseInput { - # - # UUID of the course that should be updated. - # Must be an id of an existing course, otherwise an error is returned. - id: UUID! - - # - # The new title of the course, max 255 characters, must not be blank. - title: String! - - # - # The new description of the course, max 3000 characters. description: String! - - # - # The new start date of the course, ISO 8601 format. - startDate: DateTime! - - # - # The new end date of the course, ISO 8601 format. endDate: DateTime! - - # - # The new published status of the course. id: UUID! published: Boolean! - - # - # The year in which the term starts. startDate: DateTime! startYear: Int - - # - # The division of the academic calendar in which the term takes place. title: String! yearDivision: YearDivision } input UpdateExactAnswerQuestionInput { - # - # UUID of the question to update and the id of the corresponding item. - itemId: UUID! - - # - # Text of the question, in SlateJS JSON format. - text: JSON! - - # - # A list of possible correct answers. - correctAnswers: [String!]! - - # - # If the answer is case sensitive. If true, the answer is checked case sensitive. caseSensitive: Boolean! = false - - # - # Feedback for the question when the user enters a wrong answer, in SlateJS JSON format. correctAnswers: [String!]! feedback: JSON - - # - # Optional hint for the question, in SlateJS JSON format. hint: JSON itemId: UUID! text: JSON! } input UpdateExerciseInput { - # - # Id of the exercise to update. itemId: UUID! - - # - # The amount of credits that can be earned on this exercise including all sub-exercises. (Positive or zero) - totalExerciseCredits: Float! - - # - # Sub-exercises making up the exercise, i.e. parts a),b),c),... - subexercises: [CreateSubexerciseInput!]! - - # - # The number of the exercise on the exercise sheet, may be something such as 2 (optional). number: String subexercises: [CreateSubexerciseInput!]! totalExerciseCredits: Float! } input UpdateFlashcardInput { - # - # Id of the flashcard to update, which is the id of the corresponding item. itemId: UUID! - - # - # List of sides of this flashcard. Must be at least two sides. sides: [FlashcardSideInput!]! } input UpdateMediaContentInput { - # - # Metadata for the new Content metadata: UpdateContentMetadataInput! } input UpdateMediaRecordInput { - # - # ID of the media record which should be updated contentIds: [UUID!]! id: UUID! - - # - # New name of the media record. Cannot be blank, maximum length 255 characters. name: String! - - # - # New type of the media record. type: MediaType! - - # - # IDs of the MediaContents this media record is associated with - contentIds: [UUID!]! } input UpdateMultipleChoiceQuestionInput { - # - # UUID of the question to update and the id of the corresponding item. - itemId: UUID! - - # - # Text of the question, in SlateJS JSON format. - text: JSON! - - # - # List of answers. answers: [MultipleChoiceAnswerInput!]! - - # - # Optional hint for the question, in SlateJS JSON format. hint: JSON -} - -input UpdateNumericQuestionInput { - # - # UUID of the question to update and the id of the corresponding item. itemId: UUID! - - # - # Text of the question, in SlateJS JSON format. text: JSON! } - # - # The correct answer for the question. input UpdateNumericQuestionInput { correctAnswer: Float! - - # - # The allowed deviation from the correct answer. - tolerance: Float! - - # - # Feedback for the question when the user enters a wrong answer, in SlateJS JSON format. feedback: JSON - - # - # Optional hint for the question, in SlateJS JSON format. hint: JSON itemId: UUID! text: JSON! @@ -4101,51 +1656,20 @@ input UpdateNumericQuestionInput { } input UpdateSelfAssessmentQuestionInput { - # - # UUID of the question to update and the id of the corresponding item. hint: JSON itemId: UUID! - - # - # Text of the question, in SlateJS JSON format. - text: JSON! - - # - # A possible correct answer to the question. solutionSuggestion: JSON! - - # - # Optional hint for the question, in SlateJS JSON format. - hint: JSON text: JSON! } input UpdateStageInput { - # - # Identifier of the Stage id: UUID! - - # - # updated List of UUIDs for content labeled as required in this Stage - requiredContents: [UUID!]! - - # - # updated List of UUIDs for content labeled as optional in this Stage optionalContents: [UUID!]! requiredContents: [UUID!]! } input UpdateSubexerciseInput { - # - # Id of the subexercise to update. itemId: UUID! - - # - # The amount of credits that can be earned on this sub-exercise. (Positive or zero) - totalSubexerciseCredits: Float! - - # - # The number of the exercise on the exercise sheet, may be something such as 2b (optional). number: String totalSubexerciseCredits: Float! } @@ -4162,21 +1686,6 @@ type UserInfo { nickname: String! realmRoles: [GlobalUserRole!]! unavailableCourseMemberships: [CourseMembership!]! - - # Media records of this user. - mediaRecords: [MediaRecord!]! -} - -# -# Represents a user's progress on a content item. -# See https://gits-enpro.readthedocs.io/en/latest/dev-manuals/gamification/userProgress.html -type UserProgressData { - # - # The user's id. - userId: UUID! - - # - # The id of the content item. userName: String! } @@ -4211,44 +1720,15 @@ type UserItemComplete { type UserProgressData { contentId: UUID! - - # - # A list of entries each representing the user completing the content item. - # Sorted by date in descending order. - log: [ProgressLogItem]! - - # - # The learning interval in days for the content item. - # If null, the content item is not scheduled for learning. isDueForReview: Boolean! isLearned: Boolean! lastLearnDate: DateTime learningInterval: Int - - # - # The next time the content should be learned. - # Calculated using the date the user completed the content item and the learning interval. - # This is null if the user has not completed the content item once. log: [ProgressLogItem]! nextLearnDate: DateTime - - # - # The last time the content was learned successfully. - # This is null if the user has not completed the content item once. - lastLearnDate: DateTime - - # - # True if the user has completed the content item at least once successfully. - isLearned: Boolean! - - # - # True if the assessment is due for review. - isDueForReview: Boolean! userId: UUID! } -# -# Enum containing all valid roles a user can have in a course. enum UserRoleInCourse { ADMINISTRATOR STUDENT @@ -4265,13 +1745,6 @@ type VideoRecordSegment implements MediaRecordSegment { startTime: Int! thumbnail: String! title: String - - # The media record this segment is part of. - mediaRecord: MediaRecord! -} - -# -# The division of the academic year. transcript: String! } From 8f8124300baa50defb517ef1aa8daf43fd8b9a3a Mon Sep 17 00:00:00 2001 From: Can Date: Tue, 2 Sep 2025 15:34:50 +0200 Subject: [PATCH 7/7] Added icon to symbolize question was ai generated --- components/quiz/QuestionPreview.tsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/components/quiz/QuestionPreview.tsx b/components/quiz/QuestionPreview.tsx index 8a82b8c3..7367211c 100644 --- a/components/quiz/QuestionPreview.tsx +++ b/components/quiz/QuestionPreview.tsx @@ -4,8 +4,8 @@ import { BloomLevel, QuestionPreviewFragment$key, } from "@/__generated__/QuestionPreviewFragment.graphql"; -import { Edit } from "@mui/icons-material"; -import { Button } from "@mui/material"; +import { AutoAwesome, Edit } from "@mui/icons-material"; +import { Button, Tooltip } from "@mui/material"; import { useParams } from "next/navigation"; import { useCallback, @@ -31,6 +31,7 @@ import { MultipleChoiceQuestionPreview } from "./MultipleChoiceQuestionPreview"; const QuestionFragment = graphql` fragment QuestionPreviewFragment on Question { __typename + aiGenerated hint number itemId @@ -113,7 +114,7 @@ const QuestionPreview = ({ }: Props) => { const { quizId } = useParams(); const data = useFragment(QuestionFragment, question); - + console.log(data); // Destructuring necessary due to `readonly` types from relay const item = useMemo( () => ({ @@ -164,7 +165,16 @@ const QuestionPreview = ({ return ( <>
-
+
+ {data.aiGenerated && ( + + + + )}
{data.text ? (