@@ -1324,92 +1324,229 @@ defmodule Cadet.Assessments do
1324
1324
1325
1325
@ doc """
1326
1326
Function returning submissions under a grader. This function returns only the
1327
- fields that are exposed in the /grading endpoint. The reason we select only
1328
- those fields is to reduce the memory usage especially when the number of
1329
- submissions is large i.e. > 25000 submissions.
1327
+ fields that are exposed in the /grading endpoint.
1330
1328
1331
- The input parameters are the user and group_only. group_only is used to check
1332
- whether only the groups under the grader should be returned. The parameter is
1333
- a boolean which is false by default.
1329
+ The input parameters are the user and query parameters. Query parameters are
1330
+ used to filter the submissions.
1334
1331
1335
- The return value is {:ok, submissions} if no errors, else it is {:error,
1336
- {:forbidden, "Forbidden."}}
1332
+ The group parameter is used to check whether only the groups under the grader
1333
+ should be returned. If pageSize and offset are not provided, the default
1334
+ values are 10 and 0 respectively.
1335
+
1336
+ The return value is
1337
+ {:ok, %{"count": count, "data": submissions}} if no errors,
1338
+ else it is {:error, {:forbidden, "Forbidden."}}
1337
1339
"""
1338
- @ spec all_submissions_by_grader_for_index ( CourseRegistration . t ( ) ) ::
1339
- { :ok , % { :assessments => [ any ( ) ] , :submissions => [ any ( ) ] , :users => [ any ( ) ] } }
1340
- def all_submissions_by_grader_for_index (
1340
+
1341
+ # We bypass Ecto here and use a raw query to generate JSON directly from
1342
+ # PostgreSQL, because doing it in Elixir/Erlang is too inefficient.
1343
+ @ spec submissions_by_grader_for_index ( CourseRegistration . t ( ) ) ::
1344
+ { :ok ,
1345
+ % {
1346
+ :count => integer ,
1347
+ :data => % { :assessments => [ any ( ) ] , :submissions => [ any ( ) ] , :users => [ any ( ) ] }
1348
+ } }
1349
+ def submissions_by_grader_for_index (
1341
1350
grader = % CourseRegistration { course_id: course_id } ,
1342
- group_only \\ false ,
1343
- _ungraded_only \\ false
1351
+ params \\ % {
1352
+ "group" => "false" ,
1353
+ "notFullyGraded" => "true" ,
1354
+ "pageSize" => "10" ,
1355
+ "offset" => "0"
1356
+ }
1344
1357
) do
1345
- show_all = not group_only
1346
-
1347
- group_filter =
1348
- if show_all ,
1349
- do: "" ,
1350
- else:
1351
- "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 } "
1352
-
1353
- # TODO: Restore ungraded filtering
1354
- # ... or more likely, decouple email logic from this function
1355
- # ungraded_where =
1356
- # if ungraded_only,
1357
- # do: "where s.\"gradedCount\" < assts.\"questionCount\"",
1358
- # else: ""
1359
-
1360
- # We bypass Ecto here and use a raw query to generate JSON directly from
1361
- # PostgreSQL, because doing it in Elixir/Erlang is too inefficient.
1362
-
1363
- submissions =
1364
- case Repo . query ( """
1365
- SELECT
1366
- s.id,
1367
- s.status,
1368
- s.unsubmitted_at,
1369
- s.unsubmitted_by_id,
1370
- s_ans.xp,
1371
- s_ans.xp_adjustment,
1372
- s.xp_bonus,
1373
- s_ans.graded_count,
1374
- s.student_id,
1375
- s.assessment_id
1376
- FROM
1377
- submissions AS s
1378
- LEFT JOIN (
1379
- SELECT
1380
- ans.submission_id,
1381
- SUM(ans.xp) AS xp,
1382
- SUM(ans.xp_adjustment) AS xp_adjustment,
1383
- COUNT(ans.id) FILTER (
1384
- WHERE
1385
- ans.grader_id IS NOT NULL
1386
- ) AS graded_count
1387
- FROM
1388
- answers AS ans
1389
- GROUP BY
1390
- ans.submission_id
1391
- ) AS s_ans ON s_ans.submission_id = s.id
1392
- WHERE
1393
- s.assessment_id IN (
1394
- SELECT
1395
- id
1396
- FROM
1397
- assessments
1398
- WHERE
1399
- assessments.course_id = #{ course_id }
1400
- ) #{ group_filter } ;
1401
- """ ) do
1402
- { :ok , % { columns: columns , rows: result } } ->
1403
- result
1404
- |> Enum . map (
1405
- & ( columns
1406
- |> Enum . map ( fn c -> String . to_atom ( c ) end )
1407
- |> Enum . zip ( & 1 )
1408
- |> Enum . into ( % { } ) )
1409
- )
1410
- end
1358
+ submission_answers_query =
1359
+ from ( ans in Answer ,
1360
+ group_by: ans . submission_id ,
1361
+ select: % {
1362
+ submission_id: ans . submission_id ,
1363
+ xp: sum ( ans . xp ) ,
1364
+ xp_adjustment: sum ( ans . xp_adjustment ) ,
1365
+ graded_count: filter ( count ( ans . id ) , not is_nil ( ans . grader_id ) )
1366
+ }
1367
+ )
1368
+
1369
+ question_answers_query =
1370
+ from ( q in Question ,
1371
+ group_by: q . assessment_id ,
1372
+ join: a in Assessment ,
1373
+ on: q . assessment_id == a . id ,
1374
+ select: % {
1375
+ assessment_id: q . assessment_id ,
1376
+ question_count: count ( q . id )
1377
+ }
1378
+ )
1379
+
1380
+ query =
1381
+ from ( s in Submission ,
1382
+ left_join: ans in subquery ( submission_answers_query ) ,
1383
+ on: ans . submission_id == s . id ,
1384
+ as: :ans ,
1385
+ left_join: asst in subquery ( question_answers_query ) ,
1386
+ on: asst . assessment_id == s . assessment_id ,
1387
+ as: :asst ,
1388
+ where: ^ build_user_filter ( params ) ,
1389
+ where: s . assessment_id in subquery ( build_assessment_filter ( params , course_id ) ) ,
1390
+ where: s . assessment_id in subquery ( build_assessment_config_filter ( params ) ) ,
1391
+ where: ^ build_submission_filter ( params ) ,
1392
+ where: ^ build_course_registration_filter ( params , grader ) ,
1393
+ order_by: [ desc: s . inserted_at ] ,
1394
+ limit: ^ elem ( Integer . parse ( Map . get ( params , "pageSize" , "10" ) ) , 0 ) ,
1395
+ offset: ^ elem ( Integer . parse ( Map . get ( params , "offset" , "0" ) ) , 0 ) ,
1396
+ select: % {
1397
+ id: s . id ,
1398
+ status: s . status ,
1399
+ xp_bonus: s . xp_bonus ,
1400
+ unsubmitted_at: s . unsubmitted_at ,
1401
+ unsubmitted_by_id: s . unsubmitted_by_id ,
1402
+ student_id: s . student_id ,
1403
+ assessment_id: s . assessment_id ,
1404
+ xp: ans . xp ,
1405
+ xp_adjustment: ans . xp_adjustment ,
1406
+ graded_count: ans . graded_count ,
1407
+ question_count: asst . question_count
1408
+ }
1409
+ )
1410
+
1411
+ submissions = Repo . all ( query )
1412
+
1413
+ count_query =
1414
+ from ( s in Submission ,
1415
+ left_join: ans in subquery ( submission_answers_query ) ,
1416
+ on: ans . submission_id == s . id ,
1417
+ as: :ans ,
1418
+ left_join: asst in subquery ( question_answers_query ) ,
1419
+ on: asst . assessment_id == s . assessment_id ,
1420
+ as: :asst ,
1421
+ where: s . assessment_id in subquery ( build_assessment_filter ( params , course_id ) ) ,
1422
+ where: s . assessment_id in subquery ( build_assessment_config_filter ( params ) ) ,
1423
+ where: ^ build_user_filter ( params ) ,
1424
+ where: ^ build_submission_filter ( params ) ,
1425
+ where: ^ build_course_registration_filter ( params , grader ) ,
1426
+ select: count ( s . id )
1427
+ )
1428
+
1429
+ count = Repo . one ( count_query )
1430
+
1431
+ { :ok , % { count: count , data: generate_grading_summary_view_model ( submissions , course_id ) } }
1432
+ end
1411
1433
1412
- { :ok , generate_grading_summary_view_model ( submissions , course_id ) }
1434
+ defp build_assessment_filter ( params , course_id ) do
1435
+ assessments_filters =
1436
+ Enum . reduce ( params , dynamic ( true ) , fn
1437
+ { "title" , value } , dynamic ->
1438
+ dynamic ( [ assessment ] , ^ dynamic and ilike ( assessment . title , ^ "%#{ value } %" ) )
1439
+
1440
+ { _ , _ } , dynamic ->
1441
+ dynamic
1442
+ end )
1443
+
1444
+ from ( a in Assessment ,
1445
+ where: a . course_id == ^ course_id ,
1446
+ where: ^ assessments_filters ,
1447
+ select: a . id
1448
+ )
1449
+ end
1450
+
1451
+ defp build_submission_filter ( params ) do
1452
+ Enum . reduce ( params , dynamic ( true ) , fn
1453
+ { "status" , value } , dynamic ->
1454
+ dynamic ( [ submission ] , ^ dynamic and submission . status == ^ value )
1455
+
1456
+ { "notFullyGraded" , "true" } , dynamic ->
1457
+ dynamic ( [ ans: ans , asst: asst ] , ^ dynamic and asst . question_count > ans . graded_count )
1458
+
1459
+ { _ , _ } , dynamic ->
1460
+ dynamic
1461
+ end )
1462
+ end
1463
+
1464
+ defp build_course_registration_filter ( params , grader ) do
1465
+ Enum . reduce ( params , dynamic ( true ) , fn
1466
+ { "group" , "true" } , dynamic ->
1467
+ dynamic (
1468
+ [ submission ] ,
1469
+ ( ^ dynamic and
1470
+ submission . student_id in subquery (
1471
+ from ( cr in CourseRegistration ,
1472
+ join: g in Group ,
1473
+ on: cr . group_id == g . id ,
1474
+ where: g . leader_id == ^ grader . id ,
1475
+ select: cr . id
1476
+ )
1477
+ ) ) or submission . student_id == ^ grader . id
1478
+ )
1479
+
1480
+ { "groupName" , value } , dynamic ->
1481
+ dynamic (
1482
+ [ submission ] ,
1483
+ ^ dynamic and
1484
+ submission . student_id in subquery (
1485
+ from ( cr in CourseRegistration ,
1486
+ join: g in Group ,
1487
+ on: cr . group_id == g . id ,
1488
+ where: g . name == ^ value ,
1489
+ select: cr . id
1490
+ )
1491
+ )
1492
+ )
1493
+
1494
+ { _ , _ } , dynamic ->
1495
+ dynamic
1496
+ end )
1497
+ end
1498
+
1499
+ defp build_user_filter ( params ) do
1500
+ Enum . reduce ( params , dynamic ( true ) , fn
1501
+ { "name" , value } , dynamic ->
1502
+ dynamic (
1503
+ [ submission ] ,
1504
+ ^ dynamic and
1505
+ submission . student_id in subquery (
1506
+ from ( user in User ,
1507
+ where: ilike ( user . name , ^ "%#{ value } %" ) ,
1508
+ select: user . id
1509
+ )
1510
+ )
1511
+ )
1512
+
1513
+ { "username" , value } , dynamic ->
1514
+ dynamic (
1515
+ [ submission ] ,
1516
+ ^ dynamic and
1517
+ submission . student_id in subquery (
1518
+ from ( user in User ,
1519
+ where: ilike ( user . username , ^ "%#{ value } %" ) ,
1520
+ select: user . id
1521
+ )
1522
+ )
1523
+ )
1524
+
1525
+ { _ , _ } , dynamic ->
1526
+ dynamic
1527
+ end )
1528
+ end
1529
+
1530
+ defp build_assessment_config_filter ( params ) do
1531
+ assessment_config_filters =
1532
+ Enum . reduce ( params , dynamic ( true ) , fn
1533
+ { "type" , value } , dynamic ->
1534
+ dynamic ( [ assessment_config: config ] , ^ dynamic and config . type == ^ value )
1535
+
1536
+ { "isManuallyGraded" , value } , dynamic ->
1537
+ dynamic ( [ assessment_config: config ] , ^ dynamic and config . is_manually_graded == ^ value )
1538
+
1539
+ { _ , _ } , dynamic ->
1540
+ dynamic
1541
+ end )
1542
+
1543
+ from ( a in Assessment ,
1544
+ inner_join: config in AssessmentConfig ,
1545
+ on: a . config_id == config . id ,
1546
+ as: :assessment_config ,
1547
+ where: ^ assessment_config_filters ,
1548
+ select: a . id
1549
+ )
1413
1550
end
1414
1551
1415
1552
defp generate_grading_summary_view_model ( submissions , course_id ) do
0 commit comments