@@ -1230,92 +1230,229 @@ defmodule Cadet.Assessments do
1230
1230
1231
1231
@ doc """
1232
1232
Function returning submissions under a grader. This function returns only the
1233
- fields that are exposed in the /grading endpoint. The reason we select only
1234
- those fields is to reduce the memory usage especially when the number of
1235
- submissions is large i.e. > 25000 submissions.
1233
+ fields that are exposed in the /grading endpoint.
1236
1234
1237
- The input parameters are the user and group_only. group_only is used to check
1238
- whether only the groups under the grader should be returned. The parameter is
1239
- a boolean which is false by default.
1235
+ The input parameters are the user and query parameters. Query parameters are
1236
+ used to filter the submissions.
1240
1237
1241
- The return value is {:ok, submissions} if no errors, else it is {:error,
1242
- {:forbidden, "Forbidden."}}
1238
+ The group parameter is used to check whether only the groups under the grader
1239
+ should be returned. If pageSize and offset are not provided, the default
1240
+ values are 10 and 0 respectively.
1241
+
1242
+ The return value is
1243
+ {:ok, %{"count": count, "data": submissions}} if no errors,
1244
+ else it is {:error, {:forbidden, "Forbidden."}}
1243
1245
"""
1244
- @ spec all_submissions_by_grader_for_index ( CourseRegistration . t ( ) ) ::
1245
- { :ok , % { :assessments => [ any ( ) ] , :submissions => [ any ( ) ] , :users => [ any ( ) ] } }
1246
- def all_submissions_by_grader_for_index (
1246
+
1247
+ # We bypass Ecto here and use a raw query to generate JSON directly from
1248
+ # PostgreSQL, because doing it in Elixir/Erlang is too inefficient.
1249
+ @ spec submissions_by_grader_for_index ( CourseRegistration . t ( ) ) ::
1250
+ { :ok ,
1251
+ % {
1252
+ :count => integer ,
1253
+ :data => % { :assessments => [ any ( ) ] , :submissions => [ any ( ) ] , :users => [ any ( ) ] }
1254
+ } }
1255
+ def submissions_by_grader_for_index (
1247
1256
grader = % CourseRegistration { course_id: course_id } ,
1248
- group_only \\ false ,
1249
- _ungraded_only \\ false
1257
+ params \\ % {
1258
+ "group" => "false" ,
1259
+ "notFullyGraded" => "true" ,
1260
+ "pageSize" => "10" ,
1261
+ "offset" => "0"
1262
+ }
1250
1263
) do
1251
- show_all = not group_only
1252
-
1253
- group_filter =
1254
- if show_all ,
1255
- do: "" ,
1256
- else:
1257
- "AND s.student_id IN (SELECT cr.id FROM course_registrations AS cr INNER JOIN groups AS g ON cr.group_id = g.id WHERE g.leader_id = #{ grader . id } ) OR s.student_id = #{ grader . id } "
1258
-
1259
- # TODO: Restore ungraded filtering
1260
- # ... or more likely, decouple email logic from this function
1261
- # ungraded_where =
1262
- # if ungraded_only,
1263
- # do: "where s.\"gradedCount\" < assts.\"questionCount\"",
1264
- # else: ""
1265
-
1266
- # We bypass Ecto here and use a raw query to generate JSON directly from
1267
- # PostgreSQL, because doing it in Elixir/Erlang is too inefficient.
1268
-
1269
- submissions =
1270
- case Repo . query ( """
1271
- SELECT
1272
- s.id,
1273
- s.status,
1274
- s.unsubmitted_at,
1275
- s.unsubmitted_by_id,
1276
- s_ans.xp,
1277
- s_ans.xp_adjustment,
1278
- s.xp_bonus,
1279
- s_ans.graded_count,
1280
- s.student_id,
1281
- s.assessment_id
1282
- FROM
1283
- submissions AS s
1284
- LEFT JOIN (
1285
- SELECT
1286
- ans.submission_id,
1287
- SUM(ans.xp) AS xp,
1288
- SUM(ans.xp_adjustment) AS xp_adjustment,
1289
- COUNT(ans.id) FILTER (
1290
- WHERE
1291
- ans.grader_id IS NOT NULL
1292
- ) AS graded_count
1293
- FROM
1294
- answers AS ans
1295
- GROUP BY
1296
- ans.submission_id
1297
- ) AS s_ans ON s_ans.submission_id = s.id
1298
- WHERE
1299
- s.assessment_id IN (
1300
- SELECT
1301
- id
1302
- FROM
1303
- assessments
1304
- WHERE
1305
- assessments.course_id = #{ course_id }
1306
- ) #{ group_filter } ;
1307
- """ ) do
1308
- { :ok , % { columns: columns , rows: result } } ->
1309
- result
1310
- |> Enum . map (
1311
- & ( columns
1312
- |> Enum . map ( fn c -> String . to_atom ( c ) end )
1313
- |> Enum . zip ( & 1 )
1314
- |> Enum . into ( % { } ) )
1315
- )
1316
- end
1264
+ submission_answers_query =
1265
+ from ( ans in Answer ,
1266
+ group_by: ans . submission_id ,
1267
+ select: % {
1268
+ submission_id: ans . submission_id ,
1269
+ xp: sum ( ans . xp ) ,
1270
+ xp_adjustment: sum ( ans . xp_adjustment ) ,
1271
+ graded_count: filter ( count ( ans . id ) , not is_nil ( ans . grader_id ) )
1272
+ }
1273
+ )
1274
+
1275
+ question_answers_query =
1276
+ from ( q in Question ,
1277
+ group_by: q . assessment_id ,
1278
+ join: a in Assessment ,
1279
+ on: q . assessment_id == a . id ,
1280
+ select: % {
1281
+ assessment_id: q . assessment_id ,
1282
+ question_count: count ( q . id )
1283
+ }
1284
+ )
1285
+
1286
+ query =
1287
+ from ( s in Submission ,
1288
+ left_join: ans in subquery ( submission_answers_query ) ,
1289
+ on: ans . submission_id == s . id ,
1290
+ as: :ans ,
1291
+ left_join: asst in subquery ( question_answers_query ) ,
1292
+ on: asst . assessment_id == s . assessment_id ,
1293
+ as: :asst ,
1294
+ where: ^ build_user_filter ( params ) ,
1295
+ where: s . assessment_id in subquery ( build_assessment_filter ( params , course_id ) ) ,
1296
+ where: s . assessment_id in subquery ( build_assessment_config_filter ( params ) ) ,
1297
+ where: ^ build_submission_filter ( params ) ,
1298
+ where: ^ build_course_registration_filter ( params , grader ) ,
1299
+ order_by: [ desc: s . inserted_at ] ,
1300
+ limit: ^ elem ( Integer . parse ( Map . get ( params , "pageSize" , "10" ) ) , 0 ) ,
1301
+ offset: ^ elem ( Integer . parse ( Map . get ( params , "offset" , "0" ) ) , 0 ) ,
1302
+ select: % {
1303
+ id: s . id ,
1304
+ status: s . status ,
1305
+ xp_bonus: s . xp_bonus ,
1306
+ unsubmitted_at: s . unsubmitted_at ,
1307
+ unsubmitted_by_id: s . unsubmitted_by_id ,
1308
+ student_id: s . student_id ,
1309
+ assessment_id: s . assessment_id ,
1310
+ xp: ans . xp ,
1311
+ xp_adjustment: ans . xp_adjustment ,
1312
+ graded_count: ans . graded_count ,
1313
+ question_count: asst . question_count
1314
+ }
1315
+ )
1316
+
1317
+ submissions = Repo . all ( query )
1318
+
1319
+ count_query =
1320
+ from ( s in Submission ,
1321
+ left_join: ans in subquery ( submission_answers_query ) ,
1322
+ on: ans . submission_id == s . id ,
1323
+ as: :ans ,
1324
+ left_join: asst in subquery ( question_answers_query ) ,
1325
+ on: asst . assessment_id == s . assessment_id ,
1326
+ as: :asst ,
1327
+ where: s . assessment_id in subquery ( build_assessment_filter ( params , course_id ) ) ,
1328
+ where: s . assessment_id in subquery ( build_assessment_config_filter ( params ) ) ,
1329
+ where: ^ build_user_filter ( params ) ,
1330
+ where: ^ build_submission_filter ( params ) ,
1331
+ where: ^ build_course_registration_filter ( params , grader ) ,
1332
+ select: count ( s . id )
1333
+ )
1334
+
1335
+ count = Repo . one ( count_query )
1336
+
1337
+ { :ok , % { count: count , data: generate_grading_summary_view_model ( submissions , course_id ) } }
1338
+ end
1317
1339
1318
- { :ok , generate_grading_summary_view_model ( submissions , course_id ) }
1340
+ defp build_assessment_filter ( params , course_id ) do
1341
+ assessments_filters =
1342
+ Enum . reduce ( params , dynamic ( true ) , fn
1343
+ { "title" , value } , dynamic ->
1344
+ dynamic ( [ assessment ] , ^ dynamic and ilike ( assessment . title , ^ "%#{ value } %" ) )
1345
+
1346
+ { _ , _ } , dynamic ->
1347
+ dynamic
1348
+ end )
1349
+
1350
+ from ( a in Assessment ,
1351
+ where: a . course_id == ^ course_id ,
1352
+ where: ^ assessments_filters ,
1353
+ select: a . id
1354
+ )
1355
+ end
1356
+
1357
+ defp build_submission_filter ( params ) do
1358
+ Enum . reduce ( params , dynamic ( true ) , fn
1359
+ { "status" , value } , dynamic ->
1360
+ dynamic ( [ submission ] , ^ dynamic and submission . status == ^ value )
1361
+
1362
+ { "notFullyGraded" , "true" } , dynamic ->
1363
+ dynamic ( [ ans: ans , asst: asst ] , ^ dynamic and asst . question_count > ans . graded_count )
1364
+
1365
+ { _ , _ } , dynamic ->
1366
+ dynamic
1367
+ end )
1368
+ end
1369
+
1370
+ defp build_course_registration_filter ( params , grader ) do
1371
+ Enum . reduce ( params , dynamic ( true ) , fn
1372
+ { "group" , "true" } , dynamic ->
1373
+ dynamic (
1374
+ [ submission ] ,
1375
+ ( ^ dynamic and
1376
+ submission . student_id in subquery (
1377
+ from ( cr in CourseRegistration ,
1378
+ join: g in Group ,
1379
+ on: cr . group_id == g . id ,
1380
+ where: g . leader_id == ^ grader . id ,
1381
+ select: cr . id
1382
+ )
1383
+ ) ) or submission . student_id == ^ grader . id
1384
+ )
1385
+
1386
+ { "groupName" , value } , dynamic ->
1387
+ dynamic (
1388
+ [ submission ] ,
1389
+ ^ dynamic and
1390
+ submission . student_id in subquery (
1391
+ from ( cr in CourseRegistration ,
1392
+ join: g in Group ,
1393
+ on: cr . group_id == g . id ,
1394
+ where: g . name == ^ value ,
1395
+ select: cr . id
1396
+ )
1397
+ )
1398
+ )
1399
+
1400
+ { _ , _ } , dynamic ->
1401
+ dynamic
1402
+ end )
1403
+ end
1404
+
1405
+ defp build_user_filter ( params ) do
1406
+ Enum . reduce ( params , dynamic ( true ) , fn
1407
+ { "name" , value } , dynamic ->
1408
+ dynamic (
1409
+ [ submission ] ,
1410
+ ^ dynamic and
1411
+ submission . student_id in subquery (
1412
+ from ( user in User ,
1413
+ where: ilike ( user . name , ^ "%#{ value } %" ) ,
1414
+ select: user . id
1415
+ )
1416
+ )
1417
+ )
1418
+
1419
+ { "username" , value } , dynamic ->
1420
+ dynamic (
1421
+ [ submission ] ,
1422
+ ^ dynamic and
1423
+ submission . student_id in subquery (
1424
+ from ( user in User ,
1425
+ where: ilike ( user . username , ^ "%#{ value } %" ) ,
1426
+ select: user . id
1427
+ )
1428
+ )
1429
+ )
1430
+
1431
+ { _ , _ } , dynamic ->
1432
+ dynamic
1433
+ end )
1434
+ end
1435
+
1436
+ defp build_assessment_config_filter ( params ) do
1437
+ assessment_config_filters =
1438
+ Enum . reduce ( params , dynamic ( true ) , fn
1439
+ { "type" , value } , dynamic ->
1440
+ dynamic ( [ assessment_config: config ] , ^ dynamic and config . type == ^ value )
1441
+
1442
+ { "isManuallyGraded" , value } , dynamic ->
1443
+ dynamic ( [ assessment_config: config ] , ^ dynamic and config . is_manually_graded == ^ value )
1444
+
1445
+ { _ , _ } , dynamic ->
1446
+ dynamic
1447
+ end )
1448
+
1449
+ from ( a in Assessment ,
1450
+ inner_join: config in AssessmentConfig ,
1451
+ on: a . config_id == config . id ,
1452
+ as: :assessment_config ,
1453
+ where: ^ assessment_config_filters ,
1454
+ select: a . id
1455
+ )
1319
1456
end
1320
1457
1321
1458
defp generate_grading_summary_view_model ( submissions , course_id ) do
0 commit comments