@@ -1230,92 +1230,229 @@ defmodule Cadet.Assessments do
12301230
12311231 @ doc """
12321232 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.
12361234
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.
12401237
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."}}
12431245 """
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 (
12471256 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+ }
12501263 ) 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
13171339
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+ )
13191456 end
13201457
13211458 defp generate_grading_summary_view_model ( submissions , course_id ) do
0 commit comments