From 52216f7a35a801e8945da4c6c88506ee546d95c8 Mon Sep 17 00:00:00 2001 From: Shuming Date: Thu, 28 Mar 2019 14:11:55 +0800 Subject: [PATCH 1/7] bumped tzdata from 0.5.19 to 0.5.20 --- mix.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.lock b/mix.lock index ab5dceecc..a34353059 100644 --- a/mix.lock +++ b/mix.lock @@ -76,7 +76,7 @@ "sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm"}, "timex": {:hex, :timex, "3.5.0", "b0a23167da02d0fe4f1a4e104d1f929a00d348502b52432c05de875d0b9cffa5", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"}, "timex_ecto": {:hex, :timex_ecto, "3.3.0", "d5bdef09928e7a60f10a0baa47ce653f29b43d6fee87b30b236b216d0e36b98d", [:mix], [{:ecto, "~> 2.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm"}, - "tzdata": {:hex, :tzdata, "0.5.19", "7962a3997bf06303b7d1772988ede22260f3dae1bf897408ebdac2b4435f4e6a", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, + "tzdata": {:hex, :tzdata, "0.5.20", "304b9e98a02840fb32a43ec111ffbe517863c8566eb04a061f1c4dbb90b4d84c", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, "xml_builder": {:hex, :xml_builder, "2.1.1", "2d6d665f09cf1319e3e1c46035755271b414d99ad8615d0bd6f337623e0c885b", [:mix], [], "hexpm"}, } From 72ea797d0f27208ab09ba779b42a28807087055a Mon Sep 17 00:00:00 2001 From: Shuming Date: Mon, 27 May 2019 15:44:51 +0800 Subject: [PATCH 2/7] Added autograding_results to grading_view --- lib/cadet_web/controllers/grading_controller.ex | 12 ++++++++++++ lib/cadet_web/views/assessments_view.ex | 2 +- lib/cadet_web/views/grading_view.ex | 5 ++++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/cadet_web/controllers/grading_controller.ex b/lib/cadet_web/controllers/grading_controller.ex index 6fe2efde1..4f86db14f 100644 --- a/lib/cadet_web/controllers/grading_controller.ex +++ b/lib/cadet_web/controllers/grading_controller.ex @@ -249,6 +249,18 @@ defmodule CadetWeb.GradingController do xpAdjustment(:integer, "xp adjustment given") grader(Schema.ref(:GraderInfo)) gradedAt(:string, "Last graded at", format: "date-time", required: false) + + autogradingStatus( + :string, + "One of none/processing/success/failed" + ) + + autogradingResults( + Schema.new do + type(:array) + items(Schema.ref(:AutogradingResult)) + end + ) end end, Grading: diff --git a/lib/cadet_web/views/assessments_view.ex b/lib/cadet_web/views/assessments_view.ex index 00b25bfb7..2c46528eb 100644 --- a/lib/cadet_web/views/assessments_view.ex +++ b/lib/cadet_web/views/assessments_view.ex @@ -134,7 +134,7 @@ defmodule CadetWeb.AssessmentsView do }) end - defp build_results(%{results: results}) do + def build_results(%{results: results}) do case results do nil -> nil _ -> &Enum.map(&1.autograding_results, fn result -> build_result(result) end) diff --git a/lib/cadet_web/views/grading_view.ex b/lib/cadet_web/views/grading_view.ex index ece58dc0f..1c822fc70 100644 --- a/lib/cadet_web/views/grading_view.ex +++ b/lib/cadet_web/views/grading_view.ex @@ -54,7 +54,10 @@ defmodule CadetWeb.GradingView do adjustment: :adjustment, comment: :comment, xp: :xp, - xpAdjustment: :xp_adjustment + xpAdjustment: :xp_adjustment, + autogradingStatus: :autograding_status, + autogradingResults: + CadetWeb.AssessmentsView.build_results(%{results: answer.autograding_results}) }) end end From 7ebf1026dc3e55abcc6e1fb46e2b3c1ba7b102b8 Mon Sep 17 00:00:00 2001 From: Shuming Date: Tue, 28 May 2019 15:03:33 +0800 Subject: [PATCH 3/7] Updated test --- .../controllers/grading_controller_test.exs | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/test/cadet_web/controllers/grading_controller_test.exs b/test/cadet_web/controllers/grading_controller_test.exs index 86abe4415..35ef6623a 100644 --- a/test/cadet_web/controllers/grading_controller_test.exs +++ b/test/cadet_web/controllers/grading_controller_test.exs @@ -263,7 +263,9 @@ defmodule CadetWeb.GradingControllerTest do "name" => grader.name, "id" => grader.id }, - "gradedAt" => format_datetime(&1.updated_at) + "gradedAt" => format_datetime(&1.updated_at), + "autogradingStatus" => Atom.to_string(&1.autograding_status), + "autogradingResults" => &1.autograding_results }, "student" => %{ "name" => &1.submission.student.name, @@ -308,7 +310,9 @@ defmodule CadetWeb.GradingControllerTest do "name" => grader.name, "id" => grader.id }, - "gradedAt" => format_datetime(&1.updated_at) + "gradedAt" => format_datetime(&1.updated_at), + "autogradingStatus" => Atom.to_string(&1.autograding_status), + "autogradingResults" => &1.autograding_results }, "student" => %{ "name" => &1.submission.student.name, @@ -378,7 +382,9 @@ defmodule CadetWeb.GradingControllerTest do "name" => grader.name, "id" => grader.id }, - "gradedAt" => format_datetime(&1.updated_at) + "gradedAt" => format_datetime(&1.updated_at), + "autogradingStatus" => Atom.to_string(&1.autograding_status), + "autogradingResults" => &1.autograding_results }, "student" => %{ "name" => &1.submission.student.name, @@ -423,7 +429,9 @@ defmodule CadetWeb.GradingControllerTest do "name" => grader.name, "id" => grader.id }, - "gradedAt" => format_datetime(&1.updated_at) + "gradedAt" => format_datetime(&1.updated_at), + "autogradingStatus" => Atom.to_string(&1.autograding_status), + "autogradingResults" => &1.autograding_results }, "student" => %{ "name" => &1.submission.student.name, @@ -675,7 +683,9 @@ defmodule CadetWeb.GradingControllerTest do "name" => grader.name, "id" => grader.id }, - "gradedAt" => format_datetime(&1.updated_at) + "gradedAt" => format_datetime(&1.updated_at), + "autogradingStatus" => Atom.to_string(&1.autograding_status), + "autogradingResults" => &1.autograding_results }, "student" => %{ "name" => &1.submission.student.name, @@ -720,7 +730,9 @@ defmodule CadetWeb.GradingControllerTest do "name" => grader.name, "id" => grader.id }, - "gradedAt" => format_datetime(&1.updated_at) + "gradedAt" => format_datetime(&1.updated_at), + "autogradingStatus" => Atom.to_string(&1.autograding_status), + "autogradingResults" => &1.autograding_results }, "student" => %{ "name" => &1.submission.student.name, From 72e1548fc2fb6c03091626bf6a1c3c651dda27ef Mon Sep 17 00:00:00 2001 From: Shuming Date: Thu, 30 May 2019 13:27:10 +0800 Subject: [PATCH 4/7] Moved autogradingResults from :grade to :question --- .../controllers/grading_controller.ex | 12 ----- lib/cadet_web/views/assessments_view.ex | 4 +- lib/cadet_web/views/grading_view.ex | 25 ++++++---- .../controllers/grading_controller_test.exs | 48 +++++++++---------- 4 files changed, 41 insertions(+), 48 deletions(-) diff --git a/lib/cadet_web/controllers/grading_controller.ex b/lib/cadet_web/controllers/grading_controller.ex index 4f86db14f..6fe2efde1 100644 --- a/lib/cadet_web/controllers/grading_controller.ex +++ b/lib/cadet_web/controllers/grading_controller.ex @@ -249,18 +249,6 @@ defmodule CadetWeb.GradingController do xpAdjustment(:integer, "xp adjustment given") grader(Schema.ref(:GraderInfo)) gradedAt(:string, "Last graded at", format: "date-time", required: false) - - autogradingStatus( - :string, - "One of none/processing/success/failed" - ) - - autogradingResults( - Schema.new do - type(:array) - items(Schema.ref(:AutogradingResult)) - end - ) end end, Grading: diff --git a/lib/cadet_web/views/assessments_view.ex b/lib/cadet_web/views/assessments_view.ex index 2c46528eb..96e906795 100644 --- a/lib/cadet_web/views/assessments_view.ex +++ b/lib/cadet_web/views/assessments_view.ex @@ -134,14 +134,14 @@ defmodule CadetWeb.AssessmentsView do }) end - def build_results(%{results: results}) do + defp build_results(%{results: results}) do case results do nil -> nil _ -> &Enum.map(&1.autograding_results, fn result -> build_result(result) end) end end - defp build_result(result) do + def build_result(result) do transform_map_for_view(result, %{ resultType: "resultType", expected: "expected", diff --git a/lib/cadet_web/views/grading_view.ex b/lib/cadet_web/views/grading_view.ex index 1c822fc70..1b0be46b2 100644 --- a/lib/cadet_web/views/grading_view.ex +++ b/lib/cadet_web/views/grading_view.ex @@ -35,17 +35,25 @@ defmodule CadetWeb.GradingView do def render("grading_info.json", %{answer: answer}) do transform_map_for_view(answer, %{ student: &transform_map_for_view(&1.submission.student, [:name, :id]), - question: - &Map.put( - CadetWeb.AssessmentsView.build_question(%{question: &1.question}), - :answer, - &1.answer["code"] || &1.answer["choice_id"] - ), + question: &build_grading_question/1, solution: &(&1.question.question["solution"] || ""), grade: &build_grade/1 }) end + defp build_grading_question(answer) do + results = + case answer.autograding_results do + nil -> nil + results -> Enum.map(results, &CadetWeb.AssessmentsView.build_result/1) + end + + CadetWeb.AssessmentsView.build_question(%{question: answer.question}) + |> Map.put(:answer, answer.answer["code"] || answer.answer["choice_id"]) + |> Map.put(:autogradingStatus, answer.autograding_status) + |> Map.put(:autogradingResults, results) + end + defp build_grade(answer = %{grader: grader}) do transform_map_for_view(answer, %{ grader: grader_builder(grader), @@ -54,10 +62,7 @@ defmodule CadetWeb.GradingView do adjustment: :adjustment, comment: :comment, xp: :xp, - xpAdjustment: :xp_adjustment, - autogradingStatus: :autograding_status, - autogradingResults: - CadetWeb.AssessmentsView.build_results(%{results: answer.autograding_results}) + xpAdjustment: :xp_adjustment }) end end diff --git a/test/cadet_web/controllers/grading_controller_test.exs b/test/cadet_web/controllers/grading_controller_test.exs index 35ef6623a..e3de15cc6 100644 --- a/test/cadet_web/controllers/grading_controller_test.exs +++ b/test/cadet_web/controllers/grading_controller_test.exs @@ -250,7 +250,9 @@ defmodule CadetWeb.GradingControllerTest do "maxGrade" => &1.question.max_grade, "maxXp" => &1.question.max_xp, "content" => &1.question.question.content, - "answer" => &1.answer.code + "answer" => &1.answer.code, + "autogradingStatus" => Atom.to_string(&1.autograding_status), + "autogradingResults" => &1.autograding_results }, "solution" => &1.question.question.solution, "grade" => %{ @@ -263,9 +265,7 @@ defmodule CadetWeb.GradingControllerTest do "name" => grader.name, "id" => grader.id }, - "gradedAt" => format_datetime(&1.updated_at), - "autogradingStatus" => Atom.to_string(&1.autograding_status), - "autogradingResults" => &1.autograding_results + "gradedAt" => format_datetime(&1.updated_at) }, "student" => %{ "name" => &1.submission.student.name, @@ -297,7 +297,9 @@ defmodule CadetWeb.GradingControllerTest do "hint" => choice.hint, "id" => choice.choice_id } - end + end, + "autogradingStatus" => Atom.to_string(&1.autograding_status), + "autogradingResults" => &1.autograding_results }, "solution" => "", "grade" => %{ @@ -310,9 +312,7 @@ defmodule CadetWeb.GradingControllerTest do "name" => grader.name, "id" => grader.id }, - "gradedAt" => format_datetime(&1.updated_at), - "autogradingStatus" => Atom.to_string(&1.autograding_status), - "autogradingResults" => &1.autograding_results + "gradedAt" => format_datetime(&1.updated_at) }, "student" => %{ "name" => &1.submission.student.name, @@ -369,7 +369,9 @@ defmodule CadetWeb.GradingControllerTest do "maxGrade" => &1.question.max_grade, "maxXp" => &1.question.max_xp, "content" => &1.question.question.content, - "answer" => &1.answer.code + "answer" => &1.answer.code, + "autogradingStatus" => Atom.to_string(&1.autograding_status), + "autogradingResults" => &1.autograding_results }, "solution" => &1.question.question.solution, "grade" => %{ @@ -382,9 +384,7 @@ defmodule CadetWeb.GradingControllerTest do "name" => grader.name, "id" => grader.id }, - "gradedAt" => format_datetime(&1.updated_at), - "autogradingStatus" => Atom.to_string(&1.autograding_status), - "autogradingResults" => &1.autograding_results + "gradedAt" => format_datetime(&1.updated_at) }, "student" => %{ "name" => &1.submission.student.name, @@ -416,7 +416,9 @@ defmodule CadetWeb.GradingControllerTest do "hint" => choice.hint, "id" => choice.choice_id } - end + end, + "autogradingStatus" => Atom.to_string(&1.autograding_status), + "autogradingResults" => &1.autograding_results }, "solution" => "", "grade" => %{ @@ -429,9 +431,7 @@ defmodule CadetWeb.GradingControllerTest do "name" => grader.name, "id" => grader.id }, - "gradedAt" => format_datetime(&1.updated_at), - "autogradingStatus" => Atom.to_string(&1.autograding_status), - "autogradingResults" => &1.autograding_results + "gradedAt" => format_datetime(&1.updated_at) }, "student" => %{ "name" => &1.submission.student.name, @@ -670,7 +670,9 @@ defmodule CadetWeb.GradingControllerTest do "maxGrade" => &1.question.max_grade, "maxXp" => &1.question.max_xp, "content" => &1.question.question.content, - "answer" => &1.answer.code + "answer" => &1.answer.code, + "autogradingStatus" => Atom.to_string(&1.autograding_status), + "autogradingResults" => &1.autograding_results }, "solution" => &1.question.question.solution, "grade" => %{ @@ -683,9 +685,7 @@ defmodule CadetWeb.GradingControllerTest do "name" => grader.name, "id" => grader.id }, - "gradedAt" => format_datetime(&1.updated_at), - "autogradingStatus" => Atom.to_string(&1.autograding_status), - "autogradingResults" => &1.autograding_results + "gradedAt" => format_datetime(&1.updated_at) }, "student" => %{ "name" => &1.submission.student.name, @@ -717,7 +717,9 @@ defmodule CadetWeb.GradingControllerTest do "hint" => choice.hint, "id" => choice.choice_id } - end + end, + "autogradingStatus" => Atom.to_string(&1.autograding_status), + "autogradingResults" => &1.autograding_results }, "solution" => "", "grade" => %{ @@ -730,9 +732,7 @@ defmodule CadetWeb.GradingControllerTest do "name" => grader.name, "id" => grader.id }, - "gradedAt" => format_datetime(&1.updated_at), - "autogradingStatus" => Atom.to_string(&1.autograding_status), - "autogradingResults" => &1.autograding_results + "gradedAt" => format_datetime(&1.updated_at) }, "student" => %{ "name" => &1.submission.student.name, From 7335b6b4ddc14b073b55dcdb3cf0b28f5f1aa9ac Mon Sep 17 00:00:00 2001 From: Shuming Date: Thu, 30 May 2019 13:38:44 +0800 Subject: [PATCH 5/7] Applied refactoring suggestion --- lib/cadet_web/views/grading_view.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/cadet_web/views/grading_view.ex b/lib/cadet_web/views/grading_view.ex index 1b0be46b2..32fc73dea 100644 --- a/lib/cadet_web/views/grading_view.ex +++ b/lib/cadet_web/views/grading_view.ex @@ -48,7 +48,8 @@ defmodule CadetWeb.GradingView do results -> Enum.map(results, &CadetWeb.AssessmentsView.build_result/1) end - CadetWeb.AssessmentsView.build_question(%{question: answer.question}) + %{question: answer.question} + |> CadetWeb.AssessmentsView.build_question() |> Map.put(:answer, answer.answer["code"] || answer.answer["choice_id"]) |> Map.put(:autogradingStatus, answer.autograding_status) |> Map.put(:autogradingResults, results) From 242ab6253733cf6a4680e39a7e98d11a97202793 Mon Sep 17 00:00:00 2001 From: Shuming Date: Wed, 5 Jun 2019 01:00:44 +0800 Subject: [PATCH 6/7] Refactored grading_view --- lib/cadet_web/views/grading_view.ex | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/cadet_web/views/grading_view.ex b/lib/cadet_web/views/grading_view.ex index 32fc73dea..dde01eb79 100644 --- a/lib/cadet_web/views/grading_view.ex +++ b/lib/cadet_web/views/grading_view.ex @@ -42,11 +42,7 @@ defmodule CadetWeb.GradingView do end defp build_grading_question(answer) do - results = - case answer.autograding_results do - nil -> nil - results -> Enum.map(results, &CadetWeb.AssessmentsView.build_result/1) - end + results = build_autograding_results(answer.autograding_results) %{question: answer.question} |> CadetWeb.AssessmentsView.build_question() @@ -55,6 +51,12 @@ defmodule CadetWeb.GradingView do |> Map.put(:autogradingResults, results) end + defp build_autograding_results(nil), do: nil + + defp build_autograding_results(results) do + Enum.map(results, &CadetWeb.AssessmentsView.build_result/1) + end + defp build_grade(answer = %{grader: grader}) do transform_map_for_view(answer, %{ grader: grader_builder(grader), From 14467975ec39af5d6dd8a904bc9f9f2cbea34e5b Mon Sep 17 00:00:00 2001 From: Shuming Date: Wed, 5 Jun 2019 15:07:03 +0800 Subject: [PATCH 7/7] Isolate helper functions into new module --- lib/cadet_web/views/assessments_helpers.ex | 166 +++++++++++++++++++++ lib/cadet_web/views/assessments_view.ex | 159 +------------------- lib/cadet_web/views/grading_view.ex | 6 +- 3 files changed, 171 insertions(+), 160 deletions(-) create mode 100644 lib/cadet_web/views/assessments_helpers.ex diff --git a/lib/cadet_web/views/assessments_helpers.ex b/lib/cadet_web/views/assessments_helpers.ex new file mode 100644 index 000000000..26e34ca47 --- /dev/null +++ b/lib/cadet_web/views/assessments_helpers.ex @@ -0,0 +1,166 @@ +defmodule CadetWeb.AssessmentsHelpers do + @moduledoc """ + Helper functions for Assessments and Grading + """ + + import CadetWeb.ViewHelpers + + @graded_assessment_types ~w(mission sidequest contest)a + + defp build_library(%{library: library}) do + transform_map_for_view(library, %{ + chapter: :chapter, + globals: :globals, + external: &build_external_library(%{external_library: &1.external}) + }) + end + + defp build_external_library(%{external_library: external_library}) do + transform_map_for_view(external_library, [:name, :symbols]) + end + + def build_question(%{question: question}) do + Map.merge( + build_generic_question_fields(%{question: question}), + build_question_content_by_type(%{question: question}) + ) + end + + def build_question_with_answer_and_solution_if_ungraded(%{ + question: question, + assessment: assessment + }) do + components = [ + build_question(%{question: question}), + build_answer_fields_by_question_type(%{question: question}), + build_solution_if_ungraded_by_type(%{question: question, assessment: assessment}) + ] + + components + |> Enum.filter(& &1) + |> Enum.reduce(%{}, &Map.merge/2) + end + + defp build_generic_question_fields(%{question: question}) do + transform_map_for_view(question, %{ + id: :id, + type: :type, + library: &build_library(%{library: &1.library}), + maxXp: :max_xp, + maxGrade: :max_grade + }) + end + + defp build_solution_if_ungraded_by_type(%{ + question: %{question: question, type: question_type}, + assessment: %{type: assessment_type} + }) do + if assessment_type not in @graded_assessment_types do + solution_getter = + case question_type do + :programming -> &Map.get(&1, "solution") + :mcq -> &find_correct_choice(&1["choices"]) + end + + transform_map_for_view(question, %{solution: solution_getter}) + end + end + + defp answer_builder_for(:programming), do: & &1.answer["code"] + defp answer_builder_for(:mcq), do: & &1.answer["choice_id"] + + defp build_answer_fields_by_question_type(%{ + question: %{answer: answer, type: question_type} + }) do + # No need to check if answer exists since empty answer would be a + # `%Answer{..., answer: nil}` and nil["anything"] = nil + + %{grader: grader} = answer + + transform_map_for_view(answer, %{ + answer: answer_builder_for(question_type), + comment: :comment, + grader: grader_builder(grader), + gradedAt: graded_at_builder(grader), + xp: &((&1.xp || 0) + (&1.xp_adjustment || 0)), + grade: &((&1.grade || 0) + (&1.adjustment || 0)), + autogradingStatus: :autograding_status, + autogradingResults: build_results(%{results: answer.autograding_results}) + }) + end + + defp build_results(%{results: results}) do + case results do + nil -> nil + _ -> &Enum.map(&1.autograding_results, fn result -> build_result(result) end) + end + end + + def build_result(result) do + transform_map_for_view(result, %{ + resultType: "resultType", + expected: "expected", + actual: "actual", + errorType: "errorType", + errors: build_errors(result["errors"]) + }) + end + + defp build_errors(errors) do + case errors do + nil -> nil + _ -> &Enum.map(&1["errors"], fn error -> build_error(error) end) + end + end + + defp build_error(error) do + transform_map_for_view(error, %{ + errorType: "errorType", + line: "line", + location: "location", + errorLine: "errorLine", + errorExplanation: "errorExplanation" + }) + end + + defp build_choice(choice) do + transform_map_for_view(choice, %{ + id: "choice_id", + content: "content", + hint: "hint" + }) + end + + defp build_testcase(testcase) do + transform_map_for_view(testcase, %{ + answer: "answer", + score: "score", + program: "program" + }) + end + + defp build_question_content_by_type(%{question: %{question: question, type: question_type}}) do + case question_type do + :programming -> + transform_map_for_view(question, %{ + content: "content", + prepend: "prepend", + solutionTemplate: "template", + postpend: "postpend", + testcases: &Enum.map(&1["public"], fn testcase -> build_testcase(testcase) end) + }) + + :mcq -> + transform_map_for_view(question, %{ + content: "content", + choices: &Enum.map(&1["choices"], fn choice -> build_choice(choice) end) + }) + end + end + + defp find_correct_choice(choices) do + choices + |> Enum.find(&Map.get(&1, "is_correct")) + |> Map.get("choice_id") + end +end diff --git a/lib/cadet_web/views/assessments_view.ex b/lib/cadet_web/views/assessments_view.ex index 96e906795..c825f72b7 100644 --- a/lib/cadet_web/views/assessments_view.ex +++ b/lib/cadet_web/views/assessments_view.ex @@ -2,7 +2,7 @@ defmodule CadetWeb.AssessmentsView do use CadetWeb, :view use Timex - @graded_assessment_types ~w(mission sidequest contest)a + import CadetWeb.AssessmentsHelpers def render("index.json", %{assessments: assessments}) do render_many(assessments, CadetWeb.AssessmentsView, "overview.json", as: :assessment) @@ -51,161 +51,4 @@ defmodule CadetWeb.AssessmentsView do } ) end - - defp build_library(%{library: library}) do - transform_map_for_view(library, %{ - chapter: :chapter, - globals: :globals, - external: &build_external_library(%{external_library: &1.external}) - }) - end - - def build_question(%{question: question}) do - Map.merge( - build_generic_question_fields(%{question: question}), - build_question_content_by_type(%{question: question}) - ) - end - - defp build_external_library(%{external_library: external_library}) do - transform_map_for_view(external_library, [:name, :symbols]) - end - - defp build_question_with_answer_and_solution_if_ungraded(%{ - question: question, - assessment: assessment - }) do - components = [ - build_question(%{question: question}), - build_answer_fields_by_question_type(%{question: question}), - build_solution_if_ungraded_by_type(%{question: question, assessment: assessment}) - ] - - components - |> Enum.filter(& &1) - |> Enum.reduce(%{}, &Map.merge/2) - end - - defp build_generic_question_fields(%{question: question}) do - transform_map_for_view(question, %{ - id: :id, - type: :type, - library: &build_library(%{library: &1.library}), - maxXp: :max_xp, - maxGrade: :max_grade - }) - end - - defp build_solution_if_ungraded_by_type(%{ - question: %{question: question, type: question_type}, - assessment: %{type: assessment_type} - }) do - if assessment_type not in @graded_assessment_types do - solution_getter = - case question_type do - :programming -> &Map.get(&1, "solution") - :mcq -> &find_correct_choice(&1["choices"]) - end - - transform_map_for_view(question, %{solution: solution_getter}) - end - end - - defp answer_builder_for(:programming), do: & &1.answer["code"] - defp answer_builder_for(:mcq), do: & &1.answer["choice_id"] - - defp build_answer_fields_by_question_type(%{ - question: %{answer: answer, type: question_type} - }) do - # No need to check if answer exists since empty answer would be a - # `%Answer{..., answer: nil}` and nil["anything"] = nil - - %{grader: grader} = answer - - transform_map_for_view(answer, %{ - answer: answer_builder_for(question_type), - comment: :comment, - grader: grader_builder(grader), - gradedAt: graded_at_builder(grader), - xp: &((&1.xp || 0) + (&1.xp_adjustment || 0)), - grade: &((&1.grade || 0) + (&1.adjustment || 0)), - autogradingStatus: :autograding_status, - autogradingResults: build_results(%{results: answer.autograding_results}) - }) - end - - defp build_results(%{results: results}) do - case results do - nil -> nil - _ -> &Enum.map(&1.autograding_results, fn result -> build_result(result) end) - end - end - - def build_result(result) do - transform_map_for_view(result, %{ - resultType: "resultType", - expected: "expected", - actual: "actual", - errorType: "errorType", - errors: build_errors(result["errors"]) - }) - end - - defp build_errors(errors) do - case errors do - nil -> nil - _ -> &Enum.map(&1["errors"], fn error -> build_error(error) end) - end - end - - defp build_error(error) do - transform_map_for_view(error, %{ - errorType: "errorType", - line: "line", - location: "location", - errorLine: "errorLine", - errorExplanation: "errorExplanation" - }) - end - - defp build_choice(choice) do - transform_map_for_view(choice, %{ - id: "choice_id", - content: "content", - hint: "hint" - }) - end - - defp build_testcase(testcase) do - transform_map_for_view(testcase, %{ - answer: "answer", - score: "score", - program: "program" - }) - end - - defp build_question_content_by_type(%{question: %{question: question, type: question_type}}) do - case question_type do - :programming -> - transform_map_for_view(question, %{ - content: "content", - prepend: "prepend", - solutionTemplate: "template", - postpend: "postpend", - testcases: &Enum.map(&1["public"], fn testcase -> build_testcase(testcase) end) - }) - - :mcq -> - transform_map_for_view(question, %{ - content: "content", - choices: &Enum.map(&1["choices"], fn choice -> build_choice(choice) end) - }) - end - end - - defp find_correct_choice(choices) do - choices - |> Enum.find(&Map.get(&1, "is_correct")) - |> Map.get("choice_id") - end end diff --git a/lib/cadet_web/views/grading_view.ex b/lib/cadet_web/views/grading_view.ex index dde01eb79..a503e3128 100644 --- a/lib/cadet_web/views/grading_view.ex +++ b/lib/cadet_web/views/grading_view.ex @@ -1,6 +1,8 @@ defmodule CadetWeb.GradingView do use CadetWeb, :view + import CadetWeb.AssessmentsHelpers + def render("index.json", %{submissions: submissions}) do render_many(submissions, CadetWeb.GradingView, "submission.json", as: :submission) end @@ -45,7 +47,7 @@ defmodule CadetWeb.GradingView do results = build_autograding_results(answer.autograding_results) %{question: answer.question} - |> CadetWeb.AssessmentsView.build_question() + |> build_question() |> Map.put(:answer, answer.answer["code"] || answer.answer["choice_id"]) |> Map.put(:autogradingStatus, answer.autograding_status) |> Map.put(:autogradingResults, results) @@ -54,7 +56,7 @@ defmodule CadetWeb.GradingView do defp build_autograding_results(nil), do: nil defp build_autograding_results(results) do - Enum.map(results, &CadetWeb.AssessmentsView.build_result/1) + Enum.map(results, &build_result/1) end defp build_grade(answer = %{grader: grader}) do