Skip to content

Commit 81d10ca

Browse files
authored
Merge pull request #44 from MEITREX/add-submissions
Add submissions
2 parents e7d72ab + da46414 commit 81d10ca

8 files changed

Lines changed: 268 additions & 15 deletions

File tree

.meshrc.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,14 @@ additionalTypeDefs:
8585
- "./additionalTypeDefs/media.graphqls"
8686
- "./additionalTypeDefs/assignment.graphqls"
8787
- "./additionalTypeDefs/tutor.graphqls"
88+
- "./additionalTypeDefs/submission.graphqls"
8889
additionalResolvers:
8990
- "./additionalResolvers/content.ts"
9091
- "./additionalResolvers/course.ts"
9192
- "./additionalResolvers/flashcard.ts"
9293
- "./additionalResolvers/quiz.ts"
9394
- "./additionalResolvers/user.ts"
95+
- "./additionalResolvers/submission.ts"
9496
additionalEnvelopPlugins: "./envelopPlugins"
9597
transforms:
9698
- filterSchema:
@@ -100,4 +102,5 @@ transforms:
100102
- Mutation.!_internal*
101103
- Query.!_internal*
102104
- FlashcardSetMutation.!_internal*
103-
- QuizMutation.!_internal*
105+
- QuizMutation.!_internal*
106+
- SubmissionMutation.!_internal*

additionalResolvers/content.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,67 @@ const resolvers: Resolvers = {
171171
return content;
172172
}
173173
},
174+
createSubmissionAssessment: {
175+
async resolve(root, _args, context, info) {
176+
// find out in which course the chapter this assessment should be created in is
177+
let chapters = await context.CourseService.Query._internal_noauth_chaptersByIds({
178+
root,
179+
args: {
180+
ids: [_args.assessmentInput.metadata.chapterId]
181+
},
182+
selectionSet: `
183+
{
184+
course {
185+
id
186+
}
187+
}
188+
`,
189+
});
190+
191+
if (chapters.length !== 1) {
192+
throw new Error("Chapter with given id does not exist.");
193+
}
194+
195+
let courseId = chapters[0].course.id;
196+
197+
// check that the user is an admin in the course the assessment should be created in
198+
if (!context.currentUser.courseMemberships.some((membership) => {
199+
return membership.courseId === courseId && membership.role === "ADMINISTRATOR";
200+
})) {
201+
throw new Error("User is not enrolled and/or an admin in the course the assessment should be created in.");
202+
}
203+
204+
// create the assessment
205+
let content = await context.ContentService.Mutation._internal_createAssessment({
206+
root,
207+
args: {
208+
courseId: courseId,
209+
input: _args.assessmentInput
210+
},
211+
context,
212+
info
213+
});
214+
215+
// create the submissionExercise
216+
await context.MediaService.Mutation._internal_noauth_createSubmissionExercise({
217+
root,
218+
args: {
219+
courseId: courseId,
220+
assessmentId: content.id,
221+
input: _args.submissionExerciseInput
222+
},
223+
// we need to define a selection set manually here, otherwise it thinks we don't need any data
224+
// from this mutation and it won't actually be executed
225+
selectionSet: `
226+
{
227+
assessmentId
228+
}
229+
`,
230+
});
231+
232+
return content;
233+
}
234+
},
174235
createFlashcardSetAssessment: {
175236
async resolve(root, _args, context, info) {
176237
// find out in which course the chapter this assessment should be created in is

additionalResolvers/lib.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Args } from "./types";
55
export enum AssessmentItemType {
66
QuizAssessment = "QuizAssessment",
77
FlashcardAssessment = "FlashcardAssessment",
8+
SubmissionAssessment = "SubmissionAssessment",
89
}
910

1011
class Logger {
@@ -33,11 +34,17 @@ type FlashcardAssessmentMutation = {
3334
assessmentType: AssessmentItemType.FlashcardAssessment;
3435
type: FlashcardTypes;
3536
};
37+
type SubmissionAssessmentMutation = {
38+
assessmentType: AssessmentItemType.SubmissionAssessment;
39+
type: null
40+
};
3641

3742
type AssessmentMutationHandlerArgs<T extends AssessmentItemType> = (T extends AssessmentItemType.FlashcardAssessment
3843
? FlashcardAssessmentMutation
3944
: T extends AssessmentItemType.QuizAssessment
4045
? QuizAssessmentMutation
46+
: T extends AssessmentItemType.SubmissionAssessment
47+
? SubmissionAssessmentMutation
4148
: never) & {
4249
mutationName: string;
4350
callback: CallbackAfterAssessmentMutation<T>;
@@ -116,7 +123,6 @@ export const handleAssessmentMutationThenCallback = async <T extends AssessmentI
116123
);
117124
assessment = updatedAssessment.updateAssessment;
118125
}
119-
120126
// Determine the input parameter name based on the assessment type
121127

122128
let inputType;
@@ -127,6 +133,9 @@ export const handleAssessmentMutationThenCallback = async <T extends AssessmentI
127133
case AssessmentItemType.FlashcardAssessment:
128134
inputType = "flashcardInput";
129135
break;
136+
case AssessmentItemType.SubmissionAssessment:
137+
inputType = "submissionInput";
138+
break;
130139
default:
131140
throw new Error(`Unsupported assessment type: ${assessmentType}`);
132141
}
@@ -176,6 +185,9 @@ const fetchAssessmentFromContentService = async (assessmentType, root, context,
176185
case AssessmentItemType.FlashcardAssessment:
177186
assessmentMutationString = "FlashcardSetAssessment";
178187
break;
188+
case AssessmentItemType.SubmissionAssessment:
189+
assessmentMutationString = "SubmissionAssessment";
190+
break;
179191
default:
180192
throw new Error(`Unsupported assessment type: ${assessmentType}`);
181193
}
@@ -213,7 +225,6 @@ const fetchAssessmentFromContentService = async (assessmentType, root, context,
213225
context,
214226
info,
215227
});
216-
217228
return assessments[0];
218229
};
219230

@@ -251,7 +262,6 @@ const mutateAssessmentInContentService = async (root, context, info, assessment,
251262
associatedBloomLevels
252263
}
253264
}}`;
254-
255265
return await context.ContentService.Mutation.mutateContent({
256266
root,
257267
args: { contentId: assessmentId },

additionalResolvers/quiz.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const resolvers: Resolvers = {
1515
addMultipleChoiceQuestion: {
1616
/**
1717
* @param root { assessmentId: <UUID> } ???
18-
* @param _args GraqhQL function parameters defined in flashcard.graphqls file
18+
* @param _args GraqhQL function parameters defined in quiz.graphqls file
1919
* @param context Browser context (https request headers, etc.)
2020
* @param info GraphQL info/ introspection object
2121
* @param params logger, currentUser, query, token, service objects, etc.

additionalResolvers/submission.ts

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { Resolvers } from "../.mesh";
2+
import { AssessmentItemType, CallbackAfterAssessmentMutation, handleAssessmentMutationThenCallback } from "./lib";
3+
import {QuestionTypes} from "./quiz";
4+
5+
const resolvers: Resolvers = {
6+
SubmissionMutation: {
7+
addTask: {
8+
/**
9+
* @param root { assessmentId: <UUID> } ???
10+
* @param _args GraqhQL function parameters defined in submissions.graphqls file
11+
* @param context Browser context (https request headers, etc.)
12+
* @param info GraphQL info/ introspection object
13+
* @param params logger, currentUser, query, token, service objects, etc.
14+
* @returns resolved mutation
15+
*/
16+
async resolve(root, _args, context, info) {
17+
return handleAssessmentMutationThenCallback({
18+
assessmentType: AssessmentItemType.SubmissionAssessment,
19+
type: null,
20+
mutationName: "_internal_noauth_addTask",
21+
callback: handleSubmissionMutationCallback,
22+
root,
23+
_args,
24+
context,
25+
info,
26+
});
27+
},
28+
},
29+
updateTask: {
30+
/**
31+
* @param root { assessmentId: <UUID> } ???
32+
* @param _args GraqhQL function parameters defined in submissions.graphqls file
33+
* @param context Browser context (https request headers, etc.)
34+
* @param info GraphQL info/ introspection object
35+
* @param params logger, currentUser, query, token, service objects, etc.
36+
* @returns resolved mutation
37+
*/
38+
async resolve(root, _args, context, info) {
39+
return handleAssessmentMutationThenCallback({
40+
assessmentType: AssessmentItemType.SubmissionAssessment,
41+
type: null,
42+
mutationName: "_internal_noauth_updateTask",
43+
callback: handleSubmissionMutationCallback,
44+
root,
45+
_args,
46+
context,
47+
info,
48+
isUpdate: true,
49+
});
50+
},
51+
}
52+
}
53+
}
54+
55+
const handleSubmissionMutationCallback: CallbackAfterAssessmentMutation<AssessmentItemType.SubmissionAssessment> = async ({
56+
57+
logger,
58+
root,
59+
_args,
60+
context,
61+
info,
62+
isUpdate,
63+
mutationName,
64+
returnItem,
65+
contentUpdated,
66+
}) => {
67+
logger.log(4, true, `mutating submission item with id "${returnItem.id}"...`);
68+
try {
69+
const submissionMutated = await mutateSubmissionAssessmentInMediaService(
70+
logger,
71+
root,
72+
context,
73+
info,
74+
mutationName,
75+
contentUpdated,
76+
_args.assessmentId
77+
);
78+
const modifiedTask = submissionMutated[mutationName].tasks.find(
79+
(taskFetched) => taskFetched.itemId === returnItem.id
80+
);
81+
logger.log(4, false, `task content ${isUpdate ? "updated" : "added"}:`, modifiedTask);
82+
83+
logger.log(5, true, "finished");
84+
return {
85+
assessmentId: submissionMutated[mutationName].assessmentId,
86+
tasks: submissionMutated[mutationName].tasks,
87+
modifiedTask: modifiedTask,
88+
};
89+
} catch (error) {
90+
console.error("Error mutating submission:", error);
91+
}
92+
};
93+
94+
const mutateSubmissionAssessmentInMediaService = async (
95+
logger,
96+
root,
97+
context,
98+
info,
99+
mutationName,
100+
taskInput,
101+
assessmentId
102+
) => {
103+
const selectionSetMutationName = /* GraphQL */ `${mutationName}(input: {
104+
itemId: "${taskInput.itemId}",
105+
maxScore: ${taskInput.maxScore},
106+
name: "${taskInput.name}",
107+
number: ${taskInput.number}
108+
})`;
109+
logger.log(4, false, "mutation content", selectionSetMutationName);
110+
111+
return await context.MediaService.Mutation.mutateSubmission({
112+
root,
113+
args: { assessmentId: assessmentId },
114+
selectionSet: /* GraphQL */ `{
115+
${selectionSetMutationName} {
116+
assessmentId
117+
courseId
118+
endDate
119+
tasks {
120+
name,
121+
itemId,
122+
maxScore,
123+
number
124+
}
125+
}
126+
}`,
127+
context,
128+
info,
129+
});
130+
};
131+
132+
export default resolvers;

additionalResolvers/types.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@ import { AssessmentItemType } from "./lib";
33
export type Args<T extends AssessmentItemType> = {
44
item: { id: string };
55
assessmentId: string;
6-
} & (T extends AssessmentItemType.QuizAssessment
7-
? {
8-
questionInput: any;
9-
}
10-
: T extends AssessmentItemType.FlashcardAssessment
11-
? {
12-
flashcardInput: any;
13-
}
14-
: never);
6+
} & (
7+
T extends AssessmentItemType.QuizAssessment
8+
? { questionInput: any } // Quiz-specific input
9+
: T extends AssessmentItemType.FlashcardAssessment
10+
? { flashcardInput: any } // Flashcard-specific input
11+
: T extends AssessmentItemType.SubmissionAssessment
12+
? { submissionInput: any } // Submission-specific input
13+
: never);
1514

1615
type AssessmentMetadata = {
1716
name: string;

additionalTypeDefs/content.graphqls

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,8 +272,14 @@ extend type Mutation {
272272
Creates a new quiz assessment and a new, linked quiz with the given properties.
273273
"""
274274
createQuizAssessment(assessmentInput: CreateAssessmentInput!, quizInput: CreateQuizInput!): QuizAssessment! # resolved in content.ts
275-
275+
276+
"""
277+
Creates a new submission exercise assessment and a new, linked submission exercise with the given properties.
276278
"""
279+
createSubmissionAssessment(assessmentInput: CreateAssessmentInput!, submissionExerciseInput: InputSubmissionExercise!): SubmissionAssessment! # resolved in content.ts
280+
281+
282+
"""
277283
Creates a new flashcard set assessment and a new, linked flashcard set with the given properties.
278284
"""
279285
createFlashcardSetAssessment(assessmentInput: CreateAssessmentInput!, flashcardSetInput: CreateFlashcardSetInput!): FlashcardSetAssessment # resolved in content.ts
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
extend type SubmissionMutation {
2+
"""
3+
Add a task to the Submission.
4+
"""
5+
addTask(
6+
submissionInput: InputTaskWithoutItem!
7+
assessmentId: UUID!
8+
item: CreateItemInput!
9+
): TaskOutput!
10+
11+
"""
12+
Updates a task to the Submission.
13+
"""
14+
updateTask(
15+
submissionInput: InputTask!
16+
assessmentId: UUID!
17+
item: ItemInput!
18+
): TaskOutput!
19+
}
20+
21+
input InputTaskWithoutItem {
22+
number: Int!
23+
name: String!
24+
maxScore: Int!
25+
}
26+
27+
type TaskOutput {
28+
assessmentId: UUID!
29+
tasks: [Task!]!
30+
modifiedTask: Task!
31+
}
32+
33+
extend type Task {
34+
item: Item!
35+
@resolveTo(
36+
sourceName: "ContentService"
37+
sourceTypeName: "Query"
38+
sourceFieldName: "_internal_noauth_items"
39+
keyField: "itemId"
40+
keysArg: "ids"
41+
)
42+
}

0 commit comments

Comments
 (0)