diff --git a/lib/cadet/assessments/assessment.ex b/lib/cadet/assessments/assessment.ex index dc0ecd6b7..de6d750af 100644 --- a/lib/cadet/assessments/assessment.ex +++ b/lib/cadet/assessments/assessment.ex @@ -33,6 +33,8 @@ defmodule Cadet.Assessments.Assessment do field(:reading, :string) field(:password, :string, default: nil) field(:max_team_size, :integer, default: 1) + field(:has_token_counter, :boolean, default: false) + field(:has_voting_features, :boolean, default: false) belongs_to(:config, AssessmentConfig) belongs_to(:course, Course) @@ -43,7 +45,7 @@ defmodule Cadet.Assessments.Assessment do @required_fields ~w(title open_at close_at number course_id config_id max_team_size)a @optional_fields ~w(reading summary_short summary_long - is_published story cover_picture access password)a + is_published story cover_picture access password has_token_counter has_voting_features)a @optional_file_fields ~w(mission_pdf)a def changeset(assessment, params) do diff --git a/lib/cadet/courses/assessment_config.ex b/lib/cadet/courses/assessment_config.ex index ec2455b69..a9ea941ed 100644 --- a/lib/cadet/courses/assessment_config.ex +++ b/lib/cadet/courses/assessment_config.ex @@ -13,6 +13,7 @@ defmodule Cadet.Courses.AssessmentConfig do field(:show_grading_summary, :boolean, default: true) field(:is_manually_graded, :boolean, default: true) field(:has_token_counter, :boolean, default: false) + field(:has_voting_features, :boolean, default: false) # used by frontend to determine display styles field(:early_submission_xp, :integer, default: 0) field(:hours_before_early_xp_decay, :integer, default: 0) @@ -24,7 +25,7 @@ defmodule Cadet.Courses.AssessmentConfig do @required_fields ~w(course_id)a @optional_fields ~w(order type early_submission_xp - hours_before_early_xp_decay show_grading_summary is_manually_graded has_token_counter)a + hours_before_early_xp_decay show_grading_summary is_manually_graded has_voting_features has_token_counter)a def changeset(assessment_config, params) do assessment_config diff --git a/lib/cadet/jobs/xml_parser.ex b/lib/cadet/jobs/xml_parser.ex index fb1742571..09640b565 100644 --- a/lib/cadet/jobs/xml_parser.ex +++ b/lib/cadet/jobs/xml_parser.ex @@ -5,9 +5,10 @@ defmodule Cadet.Updater.XMLParser do use Cadet, [:display] + import Ecto.Query import SweetXml - alias Cadet.Assessments + alias Cadet.{Repo, Courses.AssessmentConfig, Assessments} require Logger @@ -80,6 +81,11 @@ defmodule Cadet.Updater.XMLParser do close_at = Timex.shift(open_at, days: 7) + assessment_config = + AssessmentConfig + |> where(id: ^assessment_config_id) + |> Repo.one() + assessment_params = xml |> xpath( @@ -99,6 +105,8 @@ defmodule Cadet.Updater.XMLParser do |> Map.put(:close_at, close_at) |> Map.put(:course_id, course_id) |> Map.put(:config_id, assessment_config_id) + |> Map.put(:has_token_counter, assessment_config.has_token_counter) + |> Map.put(:has_voting_features, assessment_config.has_voting_features) |> (&if(&1.access === "public", do: Map.put(&1, :password, nil), else: &1 diff --git a/lib/cadet_web/admin_controllers/admin_assessments_controller.ex b/lib/cadet_web/admin_controllers/admin_assessments_controller.ex index 83ca0b032..5e27abefe 100644 --- a/lib/cadet_web/admin_controllers/admin_assessments_controller.ex +++ b/lib/cadet_web/admin_controllers/admin_assessments_controller.ex @@ -83,6 +83,8 @@ defmodule CadetWeb.AdminAssessmentsController do close_at = params |> Map.get("closeAt") is_published = params |> Map.get("isPublished") max_team_size = params |> Map.get("maxTeamSize") + has_token_counter = params |> Map.get("hasTokenCounter") + has_voting_features = params |> Map.get("hasVotingFeatures") updated_assessment = if is_nil(is_published) do @@ -98,6 +100,20 @@ defmodule CadetWeb.AdminAssessmentsController do Map.put(updated_assessment, :max_team_size, max_team_size) end + updated_assessment = + if is_nil(has_token_counter) do + updated_assessment + else + Map.put(updated_assessment, :has_token_counter, has_token_counter) + end + + updated_assessment = + if is_nil(has_voting_features) do + updated_assessment + else + Map.put(updated_assessment, :has_voting_features, has_voting_features) + end + with {:ok, assessment} <- check_dates(open_at, close_at, updated_assessment), {:ok, _nil} <- Assessments.update_assessment(assessment_id, assessment) do text(conn, "OK") diff --git a/lib/cadet_web/admin_views/admin_assessments_view.ex b/lib/cadet_web/admin_views/admin_assessments_view.ex index 477ea1b38..d2d100892 100644 --- a/lib/cadet_web/admin_views/admin_assessments_view.ex +++ b/lib/cadet_web/admin_views/admin_assessments_view.ex @@ -28,7 +28,9 @@ defmodule CadetWeb.AdminAssessmentsView do isPublished: :is_published, questionCount: :question_count, gradedCount: &(&1.graded_count || 0), - maxTeamSize: :max_team_size + maxTeamSize: :max_team_size, + hasVotingFeatures: :has_voting_features, + hasTokenCounter: :has_token_counter }) end @@ -44,6 +46,7 @@ defmodule CadetWeb.AdminAssessmentsView do number: :number, reading: :reading, longSummary: :summary_long, + hasTokenCounter: :has_token_counter, missionPDF: &Cadet.Assessments.Upload.url({&1.mission_pdf, &1}), questions: &Enum.map(&1.questions, fn question -> diff --git a/lib/cadet_web/admin_views/admin_courses_view.ex b/lib/cadet_web/admin_views/admin_courses_view.ex index 4ce97f3c0..2804eb664 100644 --- a/lib/cadet_web/admin_views/admin_courses_view.ex +++ b/lib/cadet_web/admin_views/admin_courses_view.ex @@ -12,6 +12,7 @@ defmodule CadetWeb.AdminCoursesView do displayInDashboard: :show_grading_summary, isManuallyGraded: :is_manually_graded, earlySubmissionXp: :early_submission_xp, + hasVotingFeatures: :has_voting_features, hasTokenCounter: :has_token_counter, hoursBeforeEarlyXpDecay: :hours_before_early_xp_decay }) diff --git a/lib/cadet_web/controllers/assessments_controller.ex b/lib/cadet_web/controllers/assessments_controller.ex index 72a2e610a..ce42aae49 100644 --- a/lib/cadet_web/controllers/assessments_controller.ex +++ b/lib/cadet_web/controllers/assessments_controller.ex @@ -174,6 +174,8 @@ defmodule CadetWeb.AssessmentsController do required: true ) + hasTokenCounter(:boolean, "Does the assessment have Token Counter enabled?") + maxXp( :integer, "The maximum XP for this assessment", @@ -216,6 +218,7 @@ defmodule CadetWeb.AssessmentsController do story(:string, "The story that should be shown for this assessment") reading(:string, "The reading for this assessment") longSummary(:string, "Long summary", required: true) + hasTokenCounter(:boolean, "Does the assessment have Token Counter enabled?") missionPDF(:string, "The URL to the assessment pdf") questions(Schema.ref(:Questions), "The list of questions for this assessment") diff --git a/lib/cadet_web/views/assessments_view.ex b/lib/cadet_web/views/assessments_view.ex index 359f42454..12688040a 100644 --- a/lib/cadet_web/views/assessments_view.ex +++ b/lib/cadet_web/views/assessments_view.ex @@ -29,7 +29,9 @@ defmodule CadetWeb.AssessmentsView do isPublished: :is_published, questionCount: :question_count, gradedCount: &(&1.graded_count || 0), - maxTeamSize: :max_team_size + maxTeamSize: :max_team_size, + hasVotingFeatures: :has_voting_features, + hasTokenCounter: :has_token_counter }) end @@ -45,6 +47,7 @@ defmodule CadetWeb.AssessmentsView do number: :number, reading: :reading, longSummary: :summary_long, + hasTokenCounter: :has_token_counter, missionPDF: &Cadet.Assessments.Upload.url({&1.mission_pdf, &1}), questions: &Enum.map(&1.questions, fn question -> diff --git a/lib/cadet_web/views/user_view.ex b/lib/cadet_web/views/user_view.ex index 35de6c92b..84b5b5392 100644 --- a/lib/cadet_web/views/user_view.ex +++ b/lib/cadet_web/views/user_view.ex @@ -125,6 +125,7 @@ defmodule CadetWeb.UserView do type: :type, displayInDashboard: :show_grading_summary, isManuallyGraded: :is_manually_graded, + hasVotingFeatures: :has_voting_features, hasTokenCounter: :has_token_counter, earlySubmissionXp: :early_submission_xp, hoursBeforeEarlyXpDecay: :hours_before_early_xp_decay diff --git a/priv/repo/migrations/20240320154407_add_has_token_counter_toggle_to_assessment.exs b/priv/repo/migrations/20240320154407_add_has_token_counter_toggle_to_assessment.exs new file mode 100644 index 000000000..6bb8daaed --- /dev/null +++ b/priv/repo/migrations/20240320154407_add_has_token_counter_toggle_to_assessment.exs @@ -0,0 +1,17 @@ +defmodule Cadet.Repo.Migrations.AddHasTokenCounterToggleToAssessment do + use Ecto.Migration + + def up do + alter table(:assessments) do + add(:has_token_counter, :boolean, null: false, default: false) + add(:has_voting_features, :boolean, null: false, default: false) + end + end + + def down do + alter table(:assessments) do + remove(:has_token_counter) + remove(:has_voting_features) + end + end +end diff --git a/priv/repo/migrations/20240321141522_add_has_voting_features_toggle_to_assessment_config.exs b/priv/repo/migrations/20240321141522_add_has_voting_features_toggle_to_assessment_config.exs new file mode 100644 index 000000000..4fe02d730 --- /dev/null +++ b/priv/repo/migrations/20240321141522_add_has_voting_features_toggle_to_assessment_config.exs @@ -0,0 +1,15 @@ +defmodule Cadet.Repo.Migrations.AddHasVotingFeaturesToggleToAssessmentConfig do + use Ecto.Migration + + def up do + alter table(:assessment_configs) do + add(:has_voting_features, :boolean, null: false, default: false) + end + end + + def down do + alter table(:assessment_configs) do + remove(:has_voting_features) + end + end +end 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 a7c91ce4e..6c1b1a4bc 100644 --- a/test/cadet_web/admin_controllers/admin_assessments_controller_test.exs +++ b/test/cadet_web/admin_controllers/admin_assessments_controller_test.exs @@ -90,7 +90,9 @@ defmodule CadetWeb.AdminAssessmentsControllerTest do "isPublished" => &1.is_published, "gradedCount" => 0, "questionCount" => 9, - "xp" => (800 + 500 + 100) * 3 + "xp" => (800 + 500 + 100) * 3, + "hasVotingFeatures" => &1.has_voting_features, + "hasTokenCounter" => &1.has_token_counter } ) @@ -137,7 +139,9 @@ defmodule CadetWeb.AdminAssessmentsControllerTest do "isPublished" => &1.is_published, "gradedCount" => 0, "questionCount" => 9, - "xp" => 0 + "xp" => 0, + "hasVotingFeatures" => &1.has_voting_features, + "hasTokenCounter" => &1.has_token_counter } ) @@ -669,6 +673,76 @@ defmodule CadetWeb.AdminAssessmentsControllerTest do assert response(conn, 200) == "OK" assert [assessment.open_at, assessment.close_at] == [new_open_at, close_at] end + + @tag authenticate: :staff + test "successful, set hasTokenCounter and hasVotingFeatures to true", %{conn: conn} do + test_cr = conn.assigns.test_cr + course = test_cr.course + config = insert(:assessment_config, %{course: course}) + + assessment = + insert(:assessment, %{ + course: course, + config: config, + has_token_counter: false, + has_voting_features: false + }) + + new_has_token_counter = true + new_has_voting_features = true + + new_assessment_setting = %{ + hasTokenCounter: new_has_token_counter, + hasVotingFeatures: new_has_voting_features + } + + conn = + conn + |> post(build_url(course.id, assessment.id), new_assessment_setting) + + assessment = Repo.get(Assessment, assessment.id) + assert response(conn, 200) == "OK" + + assert [assessment.has_token_counter, assessment.has_voting_features] == [ + new_has_token_counter, + new_has_voting_features + ] + end + + @tag authenticate: :staff + test "successful, set hasTokenCounter and hasVotingFeatures to false", %{conn: conn} do + test_cr = conn.assigns.test_cr + course = test_cr.course + config = insert(:assessment_config, %{course: course}) + + assessment = + insert(:assessment, %{ + course: course, + config: config, + has_token_counter: true, + has_voting_features: true + }) + + new_has_token_counter = false + new_has_voting_features = false + + new_assessment_setting = %{ + hasTokenCounter: new_has_token_counter, + hasVotingFeatures: new_has_voting_features + } + + conn = + conn + |> post(build_url(course.id, assessment.id), new_assessment_setting) + + assessment = Repo.get(Assessment, assessment.id) + assert response(conn, 200) == "OK" + + assert [assessment.has_token_counter, assessment.has_voting_features] == [ + new_has_token_counter, + new_has_voting_features + ] + end end defp build_url(course_id), do: "/v2/courses/#{course_id}/admin/assessments/" diff --git a/test/cadet_web/admin_controllers/admin_courses_controller_test.exs b/test/cadet_web/admin_controllers/admin_courses_controller_test.exs index 492d4f8a0..c9a3096c2 100644 --- a/test/cadet_web/admin_controllers/admin_courses_controller_test.exs +++ b/test/cadet_web/admin_controllers/admin_courses_controller_test.exs @@ -158,6 +158,7 @@ defmodule CadetWeb.AdminCoursesControllerTest do order: 2, type: "Mission2", course: course, + has_voting_features: true, has_token_counter: true }) @@ -174,6 +175,7 @@ defmodule CadetWeb.AdminCoursesControllerTest do "isManuallyGraded" => true, "type" => "Mission1", "assessmentConfigId" => config1.id, + "hasVotingFeatures" => false, "hasTokenCounter" => false }, %{ @@ -183,6 +185,7 @@ defmodule CadetWeb.AdminCoursesControllerTest do "isManuallyGraded" => false, "type" => "Mission2", "assessmentConfigId" => config2.id, + "hasVotingFeatures" => true, "hasTokenCounter" => true }, %{ @@ -192,6 +195,7 @@ defmodule CadetWeb.AdminCoursesControllerTest do "isManuallyGraded" => true, "type" => "Mission3", "assessmentConfigId" => config3.id, + "hasVotingFeatures" => false, "hasTokenCounter" => false } ] diff --git a/test/cadet_web/controllers/assessments_controller_test.exs b/test/cadet_web/controllers/assessments_controller_test.exs index d5fb65427..9c22b2c9c 100644 --- a/test/cadet_web/controllers/assessments_controller_test.exs +++ b/test/cadet_web/controllers/assessments_controller_test.exs @@ -79,7 +79,9 @@ defmodule CadetWeb.AssessmentsControllerTest do "private" => false, "isPublished" => &1.is_published, "gradedCount" => 0, - "questionCount" => 9 + "questionCount" => 9, + "hasVotingFeatures" => &1.has_voting_features, + "hasTokenCounter" => &1.has_token_counter } ) @@ -163,7 +165,9 @@ defmodule CadetWeb.AssessmentsControllerTest do "private" => false, "isPublished" => &1.is_published, "gradedCount" => 0, - "questionCount" => 9 + "questionCount" => 9, + "hasVotingFeatures" => &1.has_voting_features, + "hasTokenCounter" => &1.has_token_counter } ) @@ -274,6 +278,8 @@ defmodule CadetWeb.AssessmentsControllerTest do "private" => false, "gradedCount" => 0, "questionCount" => 9, + "hasVotingFeatures" => &1.has_voting_features, + "hasTokenCounter" => &1.has_token_counter, "isPublished" => if &1.config.type == hd(configs).type do false @@ -308,6 +314,7 @@ defmodule CadetWeb.AssessmentsControllerTest do "number" => assessment.number, "reading" => assessment.reading, "longSummary" => assessment.summary_long, + "hasTokenCounter" => assessment.has_token_counter, "missionPDF" => Cadet.Assessments.Upload.url({assessment.mission_pdf, assessment}) } diff --git a/test/cadet_web/controllers/user_controller_test.exs b/test/cadet_web/controllers/user_controller_test.exs index c16a9b1fc..1b6465af5 100644 --- a/test/cadet_web/controllers/user_controller_test.exs +++ b/test/cadet_web/controllers/user_controller_test.exs @@ -123,6 +123,7 @@ defmodule CadetWeb.UserControllerTest do "assessmentConfigId" => config1.id, "earlySubmissionXp" => 200, "hoursBeforeEarlyXpDecay" => 48, + "hasVotingFeatures" => false, "hasTokenCounter" => false }, %{ @@ -132,6 +133,7 @@ defmodule CadetWeb.UserControllerTest do "assessmentConfigId" => config2.id, "earlySubmissionXp" => 200, "hoursBeforeEarlyXpDecay" => 48, + "hasVotingFeatures" => false, "hasTokenCounter" => false }, %{ @@ -141,6 +143,7 @@ defmodule CadetWeb.UserControllerTest do "assessmentConfigId" => config3.id, "earlySubmissionXp" => 200, "hoursBeforeEarlyXpDecay" => 48, + "hasVotingFeatures" => false, "hasTokenCounter" => false } ]