diff --git a/lib/cadet/assessments/assessments.ex b/lib/cadet/assessments/assessments.ex index ca5595e51..414fde364 100644 --- a/lib/cadet/assessments/assessments.ex +++ b/lib/cadet/assessments/assessments.ex @@ -555,6 +555,58 @@ defmodule Cadet.Assessments do Question.changeset(%Question{}, params_with_assessment_id) end + def reassign_voting(assessment_id, is_reassigning_voting) do + if is_reassigning_voting do + if is_voting_published(assessment_id) do + Submission + |> where(assessment_id: ^assessment_id) + |> delete_submission_association(assessment_id) + + Question + |> where(assessment_id: ^assessment_id) + |> Repo.all() + |> Enum.each(fn q -> + delete_submission_votes_association(q) + end) + end + + voting_assigned_question_ids = + SubmissionVotes + |> select([v], v.question_id) + |> Repo.all() + + unpublished_voting_questions = + Question + |> where(type: :voting) + |> where([q], q.id not in ^voting_assigned_question_ids) + |> where(assessment_id: ^assessment_id) + |> join(:inner, [q], asst in assoc(q, :assessment)) + |> select([q, asst], %{course_id: asst.course_id, question: q.question, id: q.id}) + |> Repo.all() + + for q <- unpublished_voting_questions do + insert_voting(q.course_id, q.question["contest_number"], q.id) + end + + {:ok, "voting assigned"} + else + {:ok, "no change to voting"} + end + end + + def is_voting_published(assessment_id) do + voting_assigned_question_ids = + SubmissionVotes + |> select([v], v.question_id) + |> Repo.all() + + Question + |> where(type: :voting) + |> where(assessment_id: ^assessment_id) + |> where([q], q.id in ^voting_assigned_question_ids) + |> Repo.exists?() + end + def update_final_contest_entries do # 1435 = 1 day - 5 minutes if Log.log_execution("update_final_contest_entries", Duration.from_minutes(1435)) do diff --git a/lib/cadet_web/admin_controllers/admin_assessments_controller.ex b/lib/cadet_web/admin_controllers/admin_assessments_controller.ex index 5e27abefe..29e8baee7 100644 --- a/lib/cadet_web/admin_controllers/admin_assessments_controller.ex +++ b/lib/cadet_web/admin_controllers/admin_assessments_controller.ex @@ -85,6 +85,7 @@ defmodule CadetWeb.AdminAssessmentsController do max_team_size = params |> Map.get("maxTeamSize") has_token_counter = params |> Map.get("hasTokenCounter") has_voting_features = params |> Map.get("hasVotingFeatures") + assign_entries_for_voting = params |> Map.get("assignEntriesForVoting") updated_assessment = if is_nil(is_published) do @@ -114,8 +115,16 @@ defmodule CadetWeb.AdminAssessmentsController do Map.put(updated_assessment, :has_voting_features, has_voting_features) end + is_reassigning_voting = + if is_nil(assign_entries_for_voting) do + false + else + assign_entries_for_voting + end + with {:ok, assessment} <- check_dates(open_at, close_at, updated_assessment), - {:ok, _nil} <- Assessments.update_assessment(assessment_id, assessment) do + {:ok, _nil} <- Assessments.update_assessment(assessment_id, assessment), + {:ok, _nil} <- Assessments.reassign_voting(assessment_id, is_reassigning_voting) do text(conn, "OK") else {:error, {status, message}} -> diff --git a/lib/cadet_web/admin_views/admin_assessments_view.ex b/lib/cadet_web/admin_views/admin_assessments_view.ex index 73de0be88..d08b758ae 100644 --- a/lib/cadet_web/admin_views/admin_assessments_view.ex +++ b/lib/cadet_web/admin_views/admin_assessments_view.ex @@ -2,6 +2,9 @@ defmodule CadetWeb.AdminAssessmentsView do use CadetWeb, :view use Timex import CadetWeb.AssessmentsHelpers + import Ecto.Query + alias Cadet.Assessments.{Question, SubmissionVotes} + alias Cadet.Repo def render("index.json", %{assessments: assessments}) do render_many(assessments, CadetWeb.AdminAssessmentsView, "overview.json", as: :assessment) @@ -31,7 +34,8 @@ defmodule CadetWeb.AdminAssessmentsView do earlySubmissionXp: & &1.config.early_submission_xp, maxTeamSize: :max_team_size, hasVotingFeatures: :has_voting_features, - hasTokenCounter: :has_token_counter + hasTokenCounter: :has_token_counter, + isVotingPublished: &is_voting_assigned(&1.id) }) end @@ -65,4 +69,17 @@ defmodule CadetWeb.AdminAssessmentsView do defp password_protected?(nil), do: false defp password_protected?(_), do: true + + defp is_voting_assigned(assessment_id) do + voting_assigned_question_ids = + SubmissionVotes + |> select([v], v.question_id) + |> Repo.all() + + Question + |> where(type: :voting) + |> where(assessment_id: ^assessment_id) + |> where([q], q.id in ^voting_assigned_question_ids) + |> Repo.exists?() + end end diff --git a/lib/cadet_web/views/assessments_view.ex b/lib/cadet_web/views/assessments_view.ex index 32e14b484..67442faa5 100644 --- a/lib/cadet_web/views/assessments_view.ex +++ b/lib/cadet_web/views/assessments_view.ex @@ -1,6 +1,9 @@ defmodule CadetWeb.AssessmentsView do use CadetWeb, :view use Timex + import Ecto.Query + alias Cadet.Assessments.{Question, SubmissionVotes} + alias Cadet.Repo import CadetWeb.AssessmentsHelpers @@ -32,7 +35,8 @@ defmodule CadetWeb.AssessmentsView do earlySubmissionXp: & &1.config.early_submission_xp, maxTeamSize: :max_team_size, hasVotingFeatures: :has_voting_features, - hasTokenCounter: :has_token_counter + hasTokenCounter: :has_token_counter, + isVotingPublished: &is_voting_assigned(&1.id) }) end @@ -66,4 +70,17 @@ defmodule CadetWeb.AssessmentsView do defp password_protected?(nil), do: false defp password_protected?(_), do: true + + defp is_voting_assigned(assessment_id) do + voting_assigned_question_ids = + SubmissionVotes + |> select([v], v.question_id) + |> Repo.all() + + Question + |> where(type: :voting) + |> where(assessment_id: ^assessment_id) + |> where([q], q.id in ^voting_assigned_question_ids) + |> Repo.exists?() + end end diff --git a/test/cadet/assessments/assessments_test.exs b/test/cadet/assessments/assessments_test.exs index 6b1793d0b..96a98cbc1 100644 --- a/test/cadet/assessments/assessments_test.exs +++ b/test/cadet/assessments/assessments_test.exs @@ -558,6 +558,77 @@ defmodule Cadet.AssessmentsTest do assert SubmissionVotes |> where(question_id: ^question.id) |> Repo.all() |> length() == 0 end + test "function that reassign voting after voting is assigned" do + course = insert(:course) + config = insert(:assessment_config) + # contest assessment that has closed + closed_contest_assessment = + insert(:assessment, + is_published: true, + open_at: Timex.shift(Timex.now(), days: -5), + close_at: Timex.shift(Timex.now(), hours: -1), + course: course, + config: config + ) + + contest_question = insert(:programming_question, assessment: closed_contest_assessment) + voting_assessment = insert(:assessment, %{course: course}) + + question = + insert(:voting_question, %{ + assessment: voting_assessment, + question: + build(:voting_question_content, contest_number: closed_contest_assessment.number) + }) + + students = + insert_list(6, :course_registration, %{ + role: :student, + course: course + }) + + Enum.map(students, fn student -> + submission = + insert(:submission, + student: student, + assessment: contest_question.assessment, + status: "submitted" + ) + + insert(:answer, + answer: %{code: "return 2;"}, + submission: submission, + question: contest_question + ) + end) + + unattempted_student = insert(:course_registration, %{role: :student, course: course}) + + # unattempted submission will automatically be submitted after the assessment closes. + unattempted_submission = + insert(:submission, + student: unattempted_student, + assessment: contest_question.assessment, + status: "submitted" + ) + + insert(:answer, + answer: %{ + code: "// question was left blank by student" + }, + submission: unattempted_submission, + question: contest_question + ) + + Assessments.insert_voting(course.id, contest_question.assessment.number, question.id) + Assessments.reassign_voting(voting_assessment.id, true) + + # students with own contest submissions will vote for 5 entries + # students without own contest submissin will vote for 6 entries + assert SubmissionVotes |> where(question_id: ^question.id) |> Repo.all() |> length() == + 6 * 5 + 6 + end + test "function that checks for closed contests and releases entries into voting pool" do course = insert(:course) config = insert(:assessment_config) diff --git a/test/cadet_web/admin_controllers/admin_assessments_controller_test.exs b/test/cadet_web/admin_controllers/admin_assessments_controller_test.exs index 1b93d670e..f1a3ff0c3 100644 --- a/test/cadet_web/admin_controllers/admin_assessments_controller_test.exs +++ b/test/cadet_web/admin_controllers/admin_assessments_controller_test.exs @@ -5,7 +5,7 @@ defmodule CadetWeb.AdminAssessmentsControllerTest do import Ecto.Query import ExUnit.CaptureLog - alias Cadet.Repo + alias Cadet.{Assessments, Repo} alias Cadet.Accounts.CourseRegistration alias Cadet.Assessments.{Assessment, Submission} alias Cadet.Test.XMLGenerator @@ -93,7 +93,8 @@ defmodule CadetWeb.AdminAssessmentsControllerTest do "xp" => (800 + 500 + 100) * 3, "earlySubmissionXp" => &1.config.early_submission_xp, "hasVotingFeatures" => &1.has_voting_features, - "hasTokenCounter" => &1.has_token_counter + "hasTokenCounter" => &1.has_token_counter, + "isVotingPublished" => Assessments.is_voting_published(&1.id) } ) @@ -143,7 +144,8 @@ defmodule CadetWeb.AdminAssessmentsControllerTest do "xp" => 0, "earlySubmissionXp" => &1.config.early_submission_xp, "hasVotingFeatures" => &1.has_voting_features, - "hasTokenCounter" => &1.has_token_counter + "hasTokenCounter" => &1.has_token_counter, + "isVotingPublished" => Assessments.is_voting_published(&1.id) } ) diff --git a/test/cadet_web/controllers/assessments_controller_test.exs b/test/cadet_web/controllers/assessments_controller_test.exs index 15a80fb92..83f52445e 100644 --- a/test/cadet_web/controllers/assessments_controller_test.exs +++ b/test/cadet_web/controllers/assessments_controller_test.exs @@ -82,7 +82,8 @@ defmodule CadetWeb.AssessmentsControllerTest do "questionCount" => 9, "earlySubmissionXp" => &1.config.early_submission_xp, "hasVotingFeatures" => &1.has_voting_features, - "hasTokenCounter" => &1.has_token_counter + "hasTokenCounter" => &1.has_token_counter, + "isVotingPublished" => Assessments.is_voting_published(&1.id) } ) @@ -169,7 +170,8 @@ defmodule CadetWeb.AssessmentsControllerTest do "questionCount" => 9, "earlySubmissionXp" => &1.config.early_submission_xp, "hasVotingFeatures" => &1.has_voting_features, - "hasTokenCounter" => &1.has_token_counter + "hasTokenCounter" => &1.has_token_counter, + "isVotingPublished" => Assessments.is_voting_published(&1.id) } ) @@ -282,6 +284,7 @@ defmodule CadetWeb.AssessmentsControllerTest do "questionCount" => 9, "hasVotingFeatures" => &1.has_voting_features, "hasTokenCounter" => &1.has_token_counter, + "isVotingPublished" => Assessments.is_voting_published(&1.id), "earlySubmissionXp" => &1.config.early_submission_xp, "isPublished" => if &1.config.type == hd(configs).type do