diff --git a/lib/cadet/assessments/assessments.ex b/lib/cadet/assessments/assessments.ex index 3c6eb0548..ced5441e1 100644 --- a/lib/cadet/assessments/assessments.ex +++ b/lib/cadet/assessments/assessments.ex @@ -1906,7 +1906,9 @@ defmodule Cadet.Assessments do "group" => "false", "isFullyGraded" => "false", "pageSize" => "10", - "offset" => "0" + "offset" => "0", + "sortBy" => "", + "sortDirection" => "" } ) do submission_answers_query = @@ -1927,7 +1929,9 @@ defmodule Cadet.Assessments do on: q.assessment_id == a.id, select: %{ assessment_id: q.assessment_id, - question_count: count(q.id) + question_count: count(q.id), + title: max(a.title), + config_id: max(a.config_id) } ) @@ -1939,12 +1943,23 @@ defmodule Cadet.Assessments do left_join: asst in subquery(question_answers_query), on: asst.assessment_id == s.assessment_id, as: :asst, + left_join: user in User, + on: user.id == s.student_id, + as: :user, + left_join: cr in CourseRegistration, + on: user.id == cr.user_id, + as: :cr, + left_join: group in Group, + on: cr.group_id == group.id, + as: :group, + inner_join: config in AssessmentConfig, + on: asst.config_id == config.id, + as: :config, where: ^build_user_filter(params), where: s.assessment_id in subquery(build_assessment_filter(params, course_id)), where: s.assessment_id in subquery(build_assessment_config_filter(params)), where: ^build_submission_filter(params), where: ^build_course_registration_filter(params, grader), - order_by: [desc: s.inserted_at], limit: ^elem(Integer.parse(Map.get(params, "pageSize", "10")), 0), offset: ^elem(Integer.parse(Map.get(params, "offset", "0")), 0), select: %{ @@ -1964,6 +1979,10 @@ defmodule Cadet.Assessments do } ) + query = + sort_submission(query, Map.get(params, "sortBy", ""), Map.get(params, "sortDirection", "")) + + query = from([s, ans, asst, user, cr, group] in query, order_by: [desc: s.inserted_at]) submissions = Repo.all(query) count_query = @@ -1987,6 +2006,114 @@ defmodule Cadet.Assessments do {:ok, %{count: count, data: generate_grading_summary_view_model(submissions, course_id)}} end + # Given a query from submissions_by_grader_for_index, + # sorts it by the relevant field and direction + # sort_by is a string of either "", "assessmentName", "assessmentType", "studentName", + # "studentUsername", "groupName", "progressStatus", "xp" + # sort_direction is a string of either "", "sort-asc", "sort-desc" + defp sort_submission(query, sort_by, sort_direction) do + cond do + sort_direction == "sort-asc" -> + sort_submission_asc(query, sort_by) + + sort_direction == "sort-desc" -> + sort_submission_desc(query, sort_by) + + true -> + query + end + end + + defp sort_submission_asc(query, sort_by) do + cond do + sort_by == "assessmentName" -> + from([s, ans, asst, user, cr, group, config] in query, + order_by: fragment("upper(?)", asst.title) + ) + + sort_by == "assessmentType" -> + from([s, ans, asst, user, cr, group, config] in query, order_by: asst.config_id) + + sort_by == "studentName" -> + from([s, ans, asst, user, cr, group, config] in query, + order_by: fragment("upper(?)", user.name) + ) + + sort_by == "studentUsername" -> + from([s, ans, asst, user, cr, group, config] in query, + order_by: fragment("upper(?)", user.username) + ) + + sort_by == "groupName" -> + from([s, ans, asst, user, cr, group, config] in query, + order_by: fragment("upper(?)", group.name) + ) + + sort_by == "progressStatus" -> + from([s, ans, asst, user, cr, group, config] in query, + order_by: [ + asc: config.is_manually_graded, + asc: s.status, + asc: ans.graded_count - asst.question_count, + asc: s.is_grading_published + ] + ) + + sort_by == "xp" -> + from([s, ans, asst, user, cr, group, config] in query, + order_by: ans.xp + ans.xp_adjustment + ) + + true -> + query + end + end + + defp sort_submission_desc(query, sort_by) do + cond do + sort_by == "assessmentName" -> + from([s, ans, asst, user, cr, group, config] in query, + order_by: [desc: fragment("upper(?)", asst.title)] + ) + + sort_by == "assessmentType" -> + from([s, ans, asst, user, cr, group, config] in query, order_by: [desc: asst.config_id]) + + sort_by == "studentName" -> + from([s, ans, asst, user, cr, group, config] in query, + order_by: [desc: fragment("upper(?)", user.name)] + ) + + sort_by == "studentUsername" -> + from([s, ans, asst, user, cr, group, config] in query, + order_by: [desc: fragment("upper(?)", user.username)] + ) + + sort_by == "groupName" -> + from([s, ans, asst, user, cr, group, config] in query, + order_by: [desc: fragment("upper(?)", group.name)] + ) + + sort_by == "progressStatus" -> + from([s, ans, asst, user, cr, group, config] in query, + order_by: [ + desc: config.is_manually_graded, + desc: s.status, + desc: ans.graded_count - asst.question_count, + desc: s.is_grading_published + ] + ) + + sort_by == "xp" -> + from([s, ans, asst, user, cr, group, config] in query, + order_by: [desc: ans.xp + ans.xp_adjustment] + ) + + true -> + query + end + end + defp build_assessment_filter(params, course_id) do assessments_filters = Enum.reduce(params, dynamic(true), fn diff --git a/test/cadet/assessments/assessments_test.exs b/test/cadet/assessments/assessments_test.exs index a95b7382a..750861657 100644 --- a/test/cadet/assessments/assessments_test.exs +++ b/test/cadet/assessments/assessments_test.exs @@ -2815,6 +2815,188 @@ defmodule Cadet.AssessmentsTest do assert Enum.find(assessments_from_res, fn a -> a.id == s.assessment_id end) != nil end) end + + test "sorting by assessment title ascending", %{ + course_regs: %{avenger1_cr: avenger}, + assessments: _assessments, + total_submissions: total_submissions + } do + {_, res} = + Assessments.submissions_by_grader_for_index(avenger, %{ + "pageSize" => total_submissions, + "sortBy" => "assessmentName", + "sortDirection" => "sort-asc" + }) + + submissions_from_res = res[:data][:submissions] + assessments_from_res = res[:data][:assessments] + + submissions_by_title = + Enum.map( + submissions_from_res, + fn s -> + Enum.find(assessments_from_res, fn a -> + s.assessment_id == a.id + end) + end + ) + + Enum.reduce( + submissions_by_title, + fn x, y -> + assert x.title >= y.title + y + end + ) + end + + test "sorting by assessment title descending", %{ + course_regs: %{avenger1_cr: avenger}, + assessments: _assessments, + total_submissions: total_submissions + } do + {_, res} = + Assessments.submissions_by_grader_for_index(avenger, %{ + "pageSize" => total_submissions, + "sortBy" => "assessmentName", + "sortDirection" => "sort-desc" + }) + + submissions_from_res = res[:data][:submissions] + assessments_from_res = res[:data][:assessments] + + submissions_by_title = + Enum.map( + submissions_from_res, + fn s -> + Enum.find(assessments_from_res, fn a -> + s.assessment_id == a.id + end) + end + ) + + Enum.reduce( + submissions_by_title, + fn x, y -> + assert x.title <= y.title + y + end + ) + end + + test "sorting by assessment type ascending", %{ + course_regs: %{avenger1_cr: avenger}, + assessments: _assessments, + total_submissions: total_submissions + } do + {_, res} = + Assessments.submissions_by_grader_for_index(avenger, %{ + "pageSize" => total_submissions, + "sortBy" => "assessmentType", + "sortDirection" => "sort-asc" + }) + + submissions_from_res = res[:data][:submissions] + assessments_from_res = res[:data][:assessments] + + submissions_by_assessments_type = + Enum.map( + submissions_from_res, + fn s -> + Enum.find(assessments_from_res, fn a -> + s.assessment_id == a.id + end) + end + ) + + Enum.reduce( + submissions_by_assessments_type, + fn x, y -> + assert x.config_id >= y.config_id + y + end + ) + end + + test "sorting by assessment type descending", %{ + course_regs: %{avenger1_cr: avenger}, + assessments: _assessments, + total_submissions: total_submissions + } do + {_, res} = + Assessments.submissions_by_grader_for_index(avenger, %{ + "pageSize" => total_submissions, + "sortBy" => "assessmentType", + "sortDirection" => "sort-desc" + }) + + submissions_from_res = res[:data][:submissions] + assessments_from_res = res[:data][:assessments] + + submissions_by_assessments_type = + Enum.map( + submissions_from_res, + fn s -> + Enum.find(assessments_from_res, fn a -> + s.assessment_id == a.id + end) + end + ) + + Enum.reduce( + submissions_by_assessments_type, + fn x, y -> + assert x.config_id <= y.config_id + y + end + ) + end + + test "sorting by xp ascending", %{ + course_regs: %{avenger1_cr: avenger}, + assessments: _assessments, + total_submissions: total_submissions + } do + {_, res} = + Assessments.submissions_by_grader_for_index(avenger, %{ + "pageSize" => total_submissions, + "sortBy" => "xp", + "sortDirection" => "sort-asc" + }) + + submissions_from_res = res[:data][:submissions] + + Enum.reduce( + submissions_from_res, + fn x, y -> + assert x.xp >= y.xp + y + end + ) + end + + test "sorting by xp descending", %{ + course_regs: %{avenger1_cr: avenger}, + assessments: _assessments, + total_submissions: total_submissions + } do + {_, res} = + Assessments.submissions_by_grader_for_index(avenger, %{ + "pageSize" => total_submissions, + "sortBy" => "xp", + "sortDirection" => "sort-desc" + }) + + submissions_from_res = res[:data][:submissions] + + Enum.reduce( + submissions_from_res, + fn x, y -> + assert x.xp <= y.xp + y + end + ) + end end describe "is_fully_autograded? function" do