From b89bb6c863893ccdd376bcb069e6e93044aac1e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Cirio?= Date: Tue, 21 May 2024 14:47:30 -0300 Subject: [PATCH 1/2] [FEATURE] [NG23-156] Create in context learning proficiency (#4821) * add proficiency icons * add proficiency_for_student_per_learning_objective/2 to Oli.Delivery.Metrics * list learning objectives with proficiency on page header * proficiency icon with tooltip * implement light mode * test new proficiency_for_student_per_learning_objective/2 * add more tests * assign student objectives metrics for review live * proficiency explanation modal in dark mode * proficiency explanation modal in light mode * add test --------- Co-authored-by: Darren Siegel --- lib/oli/delivery/metrics.ex | 71 ++++++++++ lib/oli/resources.ex | 11 ++ lib/oli_web/components/modal.ex | 122 +++++++++++++++- lib/oli_web/icons.ex | 101 ++++++++++++++ .../live/delivery/student/lesson_live.ex | 55 +++++++- .../live/delivery/student/review_live.ex | 38 +++++ lib/oli_web/live/delivery/student/utils.ex | 130 +++++++++++++++--- .../oli/analytics/summary/metrics_v2_test.exs | 48 ++++--- .../metrics/learning_proficiency_test.exs | 22 +++ .../delivery/student/lesson_live_test.exs | 121 +++++++++++++++- test/support/test_helpers.ex | 18 +++ 11 files changed, 694 insertions(+), 43 deletions(-) diff --git a/lib/oli/delivery/metrics.ex b/lib/oli/delivery/metrics.ex index 1901e70a7bb..6b42c8855d0 100644 --- a/lib/oli/delivery/metrics.ex +++ b/lib/oli/delivery/metrics.ex @@ -1158,6 +1158,77 @@ defmodule Oli.Delivery.Metrics do end) end + @doc """ + Calculates the learning proficiency ("High", "Medium", "Low", "Not enough data") + for a given student in a given section. + + It returns a map: + + %{objective_id_1 => "High", + ... + objective_id_n => "Low" + } + """ + @spec proficiency_for_student_per_learning_objective( + section :: %Oli.Delivery.Sections.Section{}, + student_id :: integer + ) :: map + def proficiency_for_student_per_learning_objective( + %Section{slug: section_slug, analytics_version: :v1}, + student_id + ) do + query = + from(sn in Snapshot, + join: s in Section, + on: sn.section_id == s.id, + where: + sn.attempt_number == 1 and sn.part_attempt_number == 1 and s.slug == ^section_slug and + sn.user_id == ^student_id, + group_by: sn.objective_id, + select: + {sn.objective_id, + fragment( + "CAST(COUNT(CASE WHEN ? THEN 1 END) as float) / CAST(COUNT(*) as float)", + sn.correct + )} + ) + + Repo.all(query) + |> Enum.into(%{}, fn {objective_id, proficiency} -> + {objective_id, proficiency_range(proficiency)} + end) + end + + def proficiency_for_student_per_learning_objective( + %Section{id: section_id, analytics_version: :v2}, + student_id + ) do + objective_type_id = Oli.Resources.ResourceType.id_for_objective() + + query = + from(summary in Oli.Analytics.Summary.ResourceSummary, + where: + summary.section_id == ^section_id and + summary.project_id == -1 and + summary.publication_id == -1 and + summary.user_id == ^student_id and + summary.resource_type_id == ^objective_type_id, + group_by: summary.resource_id, + select: + {summary.resource_id, + fragment( + "CAST(SUM(?) as float) / CAST(SUM(?) as float)", + summary.num_first_attempts_correct, + summary.num_first_attempts + )} + ) + + Repo.all(query) + |> Enum.into(%{}, fn {objective_id, proficiency} -> + {objective_id, proficiency_range(proficiency)} + end) + end + def proficiency_range(nil), do: "Not enough data" def proficiency_range(proficiency) when proficiency <= 0.5, do: "Low" def proficiency_range(proficiency) when proficiency <= 0.8, do: "Medium" diff --git a/lib/oli/resources.ex b/lib/oli/resources.ex index 1bb3d1db484..9557a652ef0 100644 --- a/lib/oli/resources.ex +++ b/lib/oli/resources.ex @@ -425,4 +425,15 @@ defmodule Oli.Resources do ) |> Repo.one() end + + @doc """ + Returns a list of revisions for the given resource ids. + """ + def get_revisions_by_resource_id(resource_ids) do + from(r in Revision, + where: r.resource_id in ^resource_ids, + select: r + ) + |> Repo.all() + end end diff --git a/lib/oli_web/components/modal.ex b/lib/oli_web/components/modal.ex index b6e50f6e437..b79da063ded 100644 --- a/lib/oli_web/components/modal.ex +++ b/lib/oli_web/components/modal.ex @@ -68,7 +68,7 @@ defmodule OliWeb.Components.Modal do phx-window-keydown={hide_modal(@on_cancel, @id)} phx-key="escape" phx-click-away={hide_modal(@on_cancel, @id)} - class="hidden relative rounded-lg bg-white dark:bg-body-dark shadow-lg shadow-zinc-700/10 ring-1 ring-zinc-700/10 transition" + class="hidden relative bg-white dark:bg-body-dark shadow-lg shadow-zinc-700/10 ring-1 ring-zinc-700/10 transition" >
@@ -148,6 +148,126 @@ defmodule OliWeb.Components.Modal do """ end + attr :id, :string, required: true + attr :class, :string, default: "" + attr :body_class, :string, default: "p-6 space-y-6" + attr :show, :boolean, default: false + attr :on_cancel, JS, default: %JS{} + attr :on_confirm, JS, default: %JS{} + + slot :inner_block, required: true + slot :title + slot :subtitle + slot :confirm + slot :cancel + + def student_delivery_modal(assigns) do + ~H""" +