• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

source-academy / backend / e0330f2cf38b2d8af12bffd20f4cac2158d607fc-PR-1236

31 Mar 2025 09:12AM UTC coverage: 19.982% (-73.6%) from 93.607%
e0330f2cf38b2d8af12bffd20f4cac2158d607fc-PR-1236

Pull #1236

github

RichDom2185
Redate migrations to maintain total ordering
Pull Request #1236: Added Exam mode

12 of 57 new or added lines in 8 files covered. (21.05%)

2430 existing lines in 97 files now uncovered.

671 of 3358 relevant lines covered (19.98%)

3.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

43.02
/lib/cadet_web/admin_controllers/admin_grading_controller.ex
1
defmodule CadetWeb.AdminGradingController do
2
  use CadetWeb, :controller
3
  use PhoenixSwagger
4

5
  alias Cadet.Assessments
6

7
  @doc """
8
  # Query Parameters
9
  - `pageSize`: Integer. The number of submissions to return. Default 10.
10
  - `offset`: Integer. The number of submissions to skip. Default 0.
11
  - `title`: String. Assessment title.
12
  - `status`: String. Submission status.
13
  - `isFullyGraded`: Boolean. Whether the submission is fully graded.
14
  - `isGradingPublished`: Boolean. Whether the grading is published.
15
  - `group`: Boolean. Only the groups under the grader should be returned.
16
  - `groupName`: String. Group name.
17
  - `name`: String. User name.
18
  - `username`: String. User username.
19
  - `type`: String. Assessment Config type.
20
  - `isManuallyGraded`: Boolean. Whether the assessment is manually graded.
21
  """
22
  def index(conn, %{"group" => group} = params)
23
      when group in ["true", "false"] do
UNCOV
24
    course_reg = conn.assigns[:course_reg]
×
25

UNCOV
26
    boolean_params = [:is_fully_graded, :group, :is_manually_graded]
×
UNCOV
27
    int_params = [:page_size, :offset]
×
28

29
    # Convert string keys to atoms and parse values
UNCOV
30
    params =
×
31
      params
32
      |> to_snake_case_atom_keys()
33
      |> Map.put_new(:page_size, "10")
34
      |> Map.put_new(:offset, "0")
35

UNCOV
36
    filtered_boolean_params =
×
37
      params
38
      |> Map.take(boolean_params)
39
      |> Map.keys()
40

UNCOV
41
    params =
×
42
      params
43
      |> process_map_booleans(filtered_boolean_params)
44
      |> process_map_integers(int_params)
45
      |> Assessments.parse_sort_direction()
46
      |> Assessments.parse_sort_by()
47

UNCOV
48
    case Assessments.submissions_by_grader_for_index(course_reg, params) do
×
49
      {:ok, view_model} ->
50
        conn
51
        |> put_status(:ok)
52
        |> put_resp_content_type("application/json")
UNCOV
53
        |> render("gradingsummaries.json", view_model)
×
54
    end
55
  end
56

57
  def index(conn, _) do
UNCOV
58
    index(conn, %{"group" => "false"})
×
59
  end
60

61
  def index_all_submissions(conn, _) do
62
    index(
×
63
      conn,
64
      %{
65
        "group" => "false",
66
        "pageSize" => "100000000000",
67
        "offset" => "0"
68
      }
69
    )
70
  end
71

72
  def show(conn, %{"submissionid" => submission_id}) when is_ecto_id(submission_id) do
UNCOV
73
    case Assessments.get_answers_in_submission(submission_id) do
×
74
      {:ok, {answers, assessment}} ->
UNCOV
75
        render(conn, "show.json", answers: answers, assessment: assessment)
×
76

77
      {:error, {status, message}} ->
78
        conn
79
        |> put_status(status)
UNCOV
80
        |> text(message)
×
81
    end
82
  end
83

84
  def update(
85
        conn,
86
        %{
87
          "submissionid" => submission_id,
88
          "questionid" => question_id,
89
          "grading" => raw_grading
90
        }
91
      )
92
      when is_ecto_id(submission_id) and is_ecto_id(question_id) do
UNCOV
93
    course_reg = conn.assigns[:course_reg]
×
94

UNCOV
95
    grading = raw_grading |> snake_casify_string_keys()
×
96

UNCOV
97
    case Assessments.update_grading_info(
×
98
           %{submission_id: submission_id, question_id: question_id},
99
           grading,
100
           course_reg
101
         ) do
102
      {:ok, _} ->
UNCOV
103
        text(conn, "OK")
×
104

105
      {:error, {status, message}} ->
106
        conn
107
        |> put_status(status)
UNCOV
108
        |> text(message)
×
109
    end
110
  end
111

112
  def update(conn, _params) do
113
    conn
114
    |> put_status(:bad_request)
UNCOV
115
    |> text("Missing parameter")
×
116
  end
117

118
  def unsubmit(conn, %{"submissionid" => submission_id}) when is_ecto_id(submission_id) do
UNCOV
119
    course_reg = conn.assigns[:course_reg]
×
120

UNCOV
121
    case Assessments.unsubmit_submission(submission_id, course_reg) do
×
122
      {:ok, nil} ->
UNCOV
123
        text(conn, "OK")
×
124

125
      {:error, {status, message}} ->
126
        conn
127
        |> put_status(status)
UNCOV
128
        |> text(message)
×
129
    end
130
  end
131

132
  def unpublish_grades(conn, %{"submissionid" => submission_id}) when is_ecto_id(submission_id) do
UNCOV
133
    course_reg = conn.assigns[:course_reg]
×
134

UNCOV
135
    case Assessments.unpublish_grading(submission_id, course_reg) do
×
136
      {:ok, nil} ->
UNCOV
137
        text(conn, "OK")
×
138

139
      {:error, {status, message}} ->
140
        conn
141
        |> put_status(status)
UNCOV
142
        |> text(message)
×
143
    end
144
  end
145

146
  def publish_grades(conn, %{"submissionid" => submission_id}) when is_ecto_id(submission_id) do
UNCOV
147
    course_reg = conn.assigns[:course_reg]
×
148

UNCOV
149
    case Assessments.publish_grading(submission_id, course_reg) do
×
150
      {:ok, nil} ->
UNCOV
151
        text(conn, "OK")
×
152

153
      {:error, {status, message}} ->
154
        conn
155
        |> put_status(status)
UNCOV
156
        |> text(message)
×
157
    end
158
  end
159

160
  def publish_all_grades(conn, %{"assessmentid" => assessment_id})
161
      when is_ecto_id(assessment_id) do
UNCOV
162
    course_reg = conn.assigns[:course_reg]
×
163

UNCOV
164
    case Assessments.publish_all_graded(course_reg, assessment_id) do
×
165
      {:ok, nil} ->
UNCOV
166
        text(conn, "OK")
×
167

168
      {:error, {status, message}} ->
169
        conn
170
        |> put_status(status)
171
        |> text(message)
×
172
    end
173
  end
174

175
  def unpublish_all_grades(conn, %{"assessmentid" => assessment_id})
176
      when is_ecto_id(assessment_id) do
UNCOV
177
    course_reg = conn.assigns[:course_reg]
×
178

UNCOV
179
    case Assessments.unpublish_all(course_reg, assessment_id) do
×
180
      {:ok, nil} ->
UNCOV
181
        text(conn, "OK")
×
182

183
      {:error, {status, message}} ->
184
        conn
185
        |> put_status(status)
186
        |> text(message)
×
187
    end
188
  end
189

190
  def autograde_submission(conn, %{"submissionid" => submission_id}) do
UNCOV
191
    course_reg = conn.assigns[:course_reg]
×
192

UNCOV
193
    case Assessments.force_regrade_submission(submission_id, course_reg) do
×
194
      {:ok, nil} ->
UNCOV
195
        send_resp(conn, :no_content, "")
×
196

197
      {:error, {status, message}} ->
198
        conn
199
        |> put_status(status)
UNCOV
200
        |> text(message)
×
201
    end
202
  end
203

204
  def autograde_answer(conn, %{"submissionid" => submission_id, "questionid" => question_id}) do
UNCOV
205
    course_reg = conn.assigns[:course_reg]
×
206

UNCOV
207
    case Assessments.force_regrade_answer(submission_id, question_id, course_reg) do
×
208
      {:ok, nil} ->
UNCOV
209
        send_resp(conn, :no_content, "")
×
210

211
      {:error, {status, message}} ->
212
        conn
213
        |> put_status(status)
UNCOV
214
        |> text(message)
×
215
    end
216
  end
217

218
  def grading_summary(conn, %{"course_id" => course_id}) do
UNCOV
219
    case Assessments.get_group_grading_summary(course_id) do
×
220
      {:ok, cols, summary} ->
UNCOV
221
        render(conn, "grading_summary.json", cols: cols, summary: summary)
×
222
    end
223
  end
224

225
  swagger_path :index do
1✔
226
    get("/courses/{course_id}/admin/grading")
227

228
    summary("Get a list of all submissions with current user as the grader")
229

230
    security([%{JWT: []}])
231

232
    produces("application/json")
233

234
    parameters do
235
      group(
236
        :query,
237
        :boolean,
238
        "Show only students in the grader's group when true",
239
        required: false
240
      )
241
    end
242

243
    response(200, "OK", Schema.ref(:Submissions))
244
    response(401, "Unauthorised")
245
    response(403, "Forbidden")
246
  end
247

248
  swagger_path :unsubmit do
1✔
249
    post("/courses/{course_id}/admin/grading/{submissionId}/unsubmit")
250
    summary("Unsubmit submission. Can only be done by the Avenger of a student")
251
    security([%{JWT: []}])
252

253
    parameters do
254
      submissionId(:path, :integer, "submission id", required: true)
255
    end
256

257
    response(200, "OK")
258
    response(400, "Invalid parameters")
259
    response(403, "Forbidden")
260
    response(404, "Submission not found")
261
  end
262

263
  swagger_path :autograde_submission do
1✔
264
    post("/courses/{course_id}/admin/grading/{submissionId}/autograde")
265
    summary("Force re-autograding of an entire submission")
266
    security([%{JWT: []}])
267

268
    parameters do
269
      submissionId(:path, :integer, "submission id", required: true)
270
    end
271

272
    response(204, "Successful request")
273
    response(400, "Invalid parameters or submission not submitted")
274
    response(403, "Forbidden")
275
    response(404, "Submission not found")
276
  end
277

278
  swagger_path :autograde_answer do
1✔
279
    post("/courses/{course_id}/admin/grading/{submissionId}/{questionId}/autograde")
280
    summary("Force re-autograding of a question in a submission")
281
    security([%{JWT: []}])
282

283
    parameters do
284
      submissionId(:path, :integer, "submission id", required: true)
285
      questionId(:path, :integer, "question id", required: true)
286
    end
287

288
    response(204, "Successful request")
289
    response(400, "Invalid parameters or submission not submitted")
290
    response(403, "Forbidden")
291
    response(404, "Answer not found")
292
  end
293

294
  swagger_path :show do
1✔
295
    get("/courses/{course_id}/admin/grading/{submissionId}")
296

297
    summary("Get information about a specific submission to be graded")
298

299
    security([%{JWT: []}])
300

301
    produces("application/json")
302

303
    parameters do
304
      submissionId(:path, :integer, "submission id", required: true)
305
    end
306

307
    response(200, "OK", Schema.ref(:GradingInfo))
308
    response(400, "Invalid or missing parameter(s) or submission and/or question not found")
309
    response(401, "Unauthorised")
310
    response(403, "Forbidden")
311
  end
312

313
  swagger_path :update do
1✔
314
    post("/courses/{course_id}/admin/grading/{submissionId}/{questionId}")
315

316
    summary("Update marks given to the answer of a particular question in a submission")
317

318
    security([%{JWT: []}])
319

320
    consumes("application/json")
321
    produces("application/json")
322

323
    parameters do
324
      submissionId(:path, :integer, "submission id", required: true)
325
      questionId(:path, :integer, "question id", required: true)
326
      grading(:body, Schema.ref(:Grading), "adjustments for a question", required: true)
327
    end
328

329
    response(200, "OK")
330
    response(400, "Invalid or missing parameter(s) or submission and/or question not found")
331
    response(401, "Unauthorised")
332
    response(403, "Forbidden")
333
  end
334

335
  swagger_path :grading_summary do
1✔
336
    get("/courses/{course_id}/admin/grading/summary")
337

338
    summary("Receives a summary of grading items done by this grader")
339

340
    security([%{JWT: []}])
341

342
    produces("application/json")
343

344
    response(200, "OK", Schema.array(:GradingSummary))
345
    response(400, "Invalid or missing parameter(s) or submission and/or question not found")
346
    response(401, "Unauthorised")
347
    response(403, "Forbidden")
348
  end
349

350
  def swagger_definitions do
351
    %{
1✔
352
      Submissions:
353
        swagger_schema do
1✔
354
          type(:array)
355
          items(Schema.ref(:Submission))
356
        end,
357
      Submission:
358
        swagger_schema do
1✔
359
          properties do
1✔
360
            id(:integer, "Submission id", required: true)
361
            grade(:integer, "Grade given", required: true)
362
            xp(:integer, "XP earned", required: true)
363
            xpBonus(:integer, "Bonus XP for a given submission", required: true)
364
            xpAdjustment(:integer, "XP adjustment given", required: true)
365
            adjustment(:integer, "Grade adjustment given", required: true)
366

367
            status(
368
              Schema.ref(:AssessmentStatus),
369
              "One of 'not_attempted/attempting/attempted/submitted' indicating whether the assessment has been attempted by the current user",
370
              required: true
371
            )
372

373
            gradedCount(:integer, "Number of questions in this submission that have been graded",
374
              required: true
375
            )
376

377
            assessment(Schema.ref(:AssessmentInfo), "Assessment for which the submission is for",
378
              required: true
379
            )
380

381
            student(Schema.ref(:StudentInfo), "Student who created the submission", required: true)
382

383
            unsubmittedBy(Schema.ref(:GraderInfo))
384
            unsubmittedAt(:string, "Last unsubmitted at", format: "date-time", required: false)
385

386
            isGradingPublished(:boolean, "Whether the grading is published", required: true)
1✔
387
          end
388
        end,
389
      AssessmentInfo:
390
        swagger_schema do
1✔
391
          properties do
1✔
392
            id(:integer, "assessment id", required: true)
393

394
            config(Schema.ref(:AssessmentConfig), "Either mission/sidequest/path/contest",
395
              required: true
396
            )
397

398
            title(:string, "Mission title", required: true)
399

400
            maxGrade(
401
              :integer,
402
              "The max grade for this assessment",
403
              required: true
404
            )
405

406
            maxXp(
407
              :integer,
408
              "The max xp for this assessment",
409
              required: true
410
            )
411

412
            questionCount(:integer, "number of questions in this assessment", required: true)
1✔
413
          end
414
        end,
415
      StudentInfo:
416
        swagger_schema do
1✔
417
          properties do
1✔
418
            id(:integer, "student id", required: true)
419
            name(:string, "student name", required: true)
420
            username(:string, "student username", required: true)
421
            groupName(:string, "name of student's group")
422
            groupLeaderId(:integer, "user id of group leader")
1✔
423
          end
424
        end,
425
      GraderInfo:
426
        swagger_schema do
1✔
427
          properties do
1✔
428
            id(:integer, "grader id", required: true)
429
            name(:string, "grader name", required: true)
1✔
430
          end
431
        end,
432
      GradingInfo:
433
        swagger_schema do
1✔
434
          description(
435
            "A list of questions with submitted answers, solution and previous grading info " <>
436
              "if available"
437
          )
438

439
          type(:array)
440

441
          items(
442
            Schema.new do
1✔
443
              properties do
1✔
444
                question(Schema.ref(:Question), "Question", required: true)
445
                grade(Schema.ref(:Grade), "Grading information", required: true)
446
                student(Schema.ref(:StudentInfo), "Student", required: true)
447

448
                solution(
449
                  :string,
450
                  "the marking scheme and model solution to this question. Only available for programming questions",
451
                  required: true
452
                )
453

454
                maxGrade(
455
                  :integer,
456
                  "the max grade that can be given to this question",
457
                  required: true
458
                )
459

460
                maxXp(
1✔
461
                  :integer,
462
                  "the max xp that can be given to this question",
463
                  required: true
464
                )
465
              end
466
            end
467
          )
468
        end,
469
      GradingSummary:
470
        swagger_schema do
1✔
471
          description("Summary of grading items for current user as the grader")
472

473
          properties do
1✔
474
            groupName(:string, "Name of group this grader is in", required: true)
475
            leaderName(:string, "Name of group leader", required: true)
476
            submittedMissions(:integer, "Number of submitted missions", required: true)
477
            submittedSidequests(:integer, "Number of submitted sidequests", required: true)
478
            ungradedMissions(:integer, "Number of ungraded missions", required: true)
479
            ungradedSidequests(:integer, "Number of ungraded sidequests", required: true)
1✔
480
          end
481
        end,
482
      Grade:
483
        swagger_schema do
1✔
484
          properties do
1✔
485
            grade(:integer, "Grade awarded by autograder")
486
            xp(:integer, "XP awarded by autograder")
487
            adjustment(:integer, "Grade adjustment given")
488
            xpAdjustment(:integer, "XP adjustment given", required: true)
489
            grader(Schema.ref(:GraderInfo))
490
            gradedAt(:string, "Last graded at", format: "date-time", required: false)
491
            comments(:string, "Comments given by grader")
1✔
492
          end
493
        end,
494
      Grading:
495
        swagger_schema do
1✔
496
          properties do
1✔
497
            grading(
1✔
498
              Schema.new do
1✔
499
                properties do
1✔
500
                  adjustment(:integer, "Grade adjustment given")
501
                  xpAdjustment(:integer, "XP adjustment given")
502
                  comments(:string, "Comments given by grader")
1✔
503
                end
504
              end
505
            )
506
          end
507
        end
508
    }
509
  end
510
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc