diff --git a/assets/src/components/activities/DeliveryElement.ts b/assets/src/components/activities/DeliveryElement.ts index b547e7e2e26..d8530eafaec 100644 --- a/assets/src/components/activities/DeliveryElement.ts +++ b/assets/src/components/activities/DeliveryElement.ts @@ -64,6 +64,7 @@ export interface ActivityContext { renderPointMarkers: boolean; isAnnotationLevel: boolean; variables: any; + pageLinkParams: any; } /** diff --git a/assets/src/components/activities/DeliveryElementProvider.tsx b/assets/src/components/activities/DeliveryElementProvider.tsx index 6da6a77bcdf..7ca388dd3a3 100644 --- a/assets/src/components/activities/DeliveryElementProvider.tsx +++ b/assets/src/components/activities/DeliveryElementProvider.tsx @@ -37,6 +37,7 @@ export const DeliveryElementProvider: React.FC> = (pro resourceAttemptGuid: props.context.pageAttemptGuid, renderPointMarkers: props.context.renderPointMarkers, isAnnotationLevel: props.context.isAnnotationLevel, + pageLinkParams: props.context.pageLinkParams, }); return ( diff --git a/assets/src/components/activities/directed-discussion/discussion/MockDiscussionDeliveryProvider.tsx b/assets/src/components/activities/directed-discussion/discussion/MockDiscussionDeliveryProvider.tsx index 4a59a28f44f..d9d9da73e5b 100644 --- a/assets/src/components/activities/directed-discussion/discussion/MockDiscussionDeliveryProvider.tsx +++ b/assets/src/components/activities/directed-discussion/discussion/MockDiscussionDeliveryProvider.tsx @@ -46,6 +46,7 @@ export const MockDiscussionDeliveryProvider: React.FC<{ renderPointMarkers: false, isAnnotationLevel: false, variables: {}, + pageLinkParams: {}, }} onSaveActivity={nullHandler} onSavePart={nullHandler} diff --git a/assets/src/data/content/writers/context.ts b/assets/src/data/content/writers/context.ts index cadbd24e03f..77dc86bbda5 100644 --- a/assets/src/data/content/writers/context.ts +++ b/assets/src/data/content/writers/context.ts @@ -28,6 +28,7 @@ export interface WriterContext { }; renderPointMarkers?: boolean; isAnnotationLevel?: boolean; + pageLinkParams?: any; } export const defaultWriterContext = (params: Partial = {}): WriterContext => diff --git a/assets/src/data/content/writers/html.tsx b/assets/src/data/content/writers/html.tsx index 5ba9ca7a9b1..7d8c758cdd6 100644 --- a/assets/src/data/content/writers/html.tsx +++ b/assets/src/data/content/writers/html.tsx @@ -530,7 +530,10 @@ export class HtmlParser implements WriterImpl { let internalHref = href; if (context.sectionSlug) { const revisionSlug = href.replace(/^\/course\/link\//, ''); - internalHref = `/sections/${context.sectionSlug}/page/${revisionSlug}`; + const params = new URLSearchParams(context.pageLinkParams); + const queryString = params.toString(); + + internalHref = `/sections/${context.sectionSlug}/lesson/${revisionSlug}?${queryString}`; } else { internalHref = '#'; } diff --git a/assets/test/check_all_that_apply/cata_delivery_test.tsx b/assets/test/check_all_that_apply/cata_delivery_test.tsx index f3068879b6e..2abd139915e 100644 --- a/assets/test/check_all_that_apply/cata_delivery_test.tsx +++ b/assets/test/check_all_that_apply/cata_delivery_test.tsx @@ -33,6 +33,7 @@ describe('check all that apply delivery', () => { renderPointMarkers: false, isAnnotationLevel: false, variables: {}, + pageLinkParams: {}, }, preview: false, }; diff --git a/assets/test/multiple_choice/mc_delivery_test.tsx b/assets/test/multiple_choice/mc_delivery_test.tsx index ca9cdf94c42..f8d14c345b8 100644 --- a/assets/test/multiple_choice/mc_delivery_test.tsx +++ b/assets/test/multiple_choice/mc_delivery_test.tsx @@ -34,6 +34,7 @@ describe('multiple choice delivery', () => { renderPointMarkers: false, isAnnotationLevel: false, variables: {}, + pageLinkParams: {}, }, graded: false, preview: false, diff --git a/assets/test/ordering/ordering_delivery_test.tsx b/assets/test/ordering/ordering_delivery_test.tsx index e08703f1866..5df348d0de3 100644 --- a/assets/test/ordering/ordering_delivery_test.tsx +++ b/assets/test/ordering/ordering_delivery_test.tsx @@ -33,6 +33,7 @@ describe('ordering delivery', () => { renderPointMarkers: false, isAnnotationLevel: false, variables: {}, + pageLinkParams: {}, }, preview: false, }; diff --git a/assets/test/short_answer/short_answer_delivery_test.tsx b/assets/test/short_answer/short_answer_delivery_test.tsx index 4f4a307fc17..e89b728ee25 100644 --- a/assets/test/short_answer/short_answer_delivery_test.tsx +++ b/assets/test/short_answer/short_answer_delivery_test.tsx @@ -33,6 +33,7 @@ describe('multiple choice delivery', () => { renderPointMarkers: false, isAnnotationLevel: false, variables: {}, + pageLinkParams: {}, }, preview: false, }; diff --git a/assets/test/writer/writer_test.ts b/assets/test/writer/writer_test.ts index 1e6961e3b9b..2fb96b7c237 100644 --- a/assets/test/writer/writer_test.ts +++ b/assets/test/writer/writer_test.ts @@ -99,12 +99,23 @@ describe('parser', () => { }); it('renders internal link with context', () => { - render(parse(exampleContent, defaultWriterContext({ sectionSlug: 'some_section' }))); + render( + parse( + exampleContent, + defaultWriterContext({ + sectionSlug: 'some_section', + pageLinkParams: { + request_path: '/path/to/previous/page', + }, + }), + ), + ); expect( screen.getByText((content, element) => { return ( element?.tagName.toLowerCase() === 'a' && - element.getAttribute('href') === '/sections/some_section/page/page_two' && + element.getAttribute('href') === + '/sections/some_section/lesson/page_two?request_path=%2Fpath%2Fto%2Fprevious%2Fpage' && content === 'Page Two' ); }), diff --git a/lib/oli/analytics/xapi.ex b/lib/oli/analytics/xapi.ex index 0adafe18469..4e018818fd6 100644 --- a/lib/oli/analytics/xapi.ex +++ b/lib/oli/analytics/xapi.ex @@ -79,6 +79,7 @@ defmodule Oli.Analytics.XAPI do # the context for the video event plus the page_attempt_number and page # resource_id + # only get the latest attempt for the page if there are multiple attempts query = from p in Oli.Delivery.Attempts.Core.ResourceAttempt, join: a in Oli.Delivery.Attempts.Core.ResourceAccess, @@ -88,6 +89,8 @@ defmodule Oli.Analytics.XAPI do join: sr in Oli.Delivery.Sections.SectionResource, on: a.resource_id == sr.resource_id and a.section_id == sr.section_id, where: p.attempt_guid == ^page_attempt_guid, + order_by: [desc: p.attempt_number], + limit: 1, select: {p.attempt_number, a.resource_id, a.section_id, a.user_id, sr.project_id, spp.publication_id} diff --git a/lib/oli/authoring/course/project.ex b/lib/oli/authoring/course/project.ex index 6767dbd632b..b728ea13cc0 100644 --- a/lib/oli/authoring/course/project.ex +++ b/lib/oli/authoring/course/project.ex @@ -24,7 +24,7 @@ defmodule Oli.Authoring.Course.Project do field(:latest_analytics_snapshot_timestamp, :utc_datetime) field(:latest_datashop_snapshot_url, :string) field(:latest_datashop_snapshot_timestamp, :utc_datetime) - field(:analytics_version, Ecto.Enum, values: [:v1, :v2], default: :v1) + field(:analytics_version, Ecto.Enum, values: [:v1, :v2], default: :v2) field(:allow_transfer_payment_codes, :boolean, default: false) field(:welcome_title, :map, default: %{}) diff --git a/lib/oli/delivery/metrics.ex b/lib/oli/delivery/metrics.ex index a750f14ce6c..733ee6bf3f8 100644 --- a/lib/oli/delivery/metrics.ex +++ b/lib/oli/delivery/metrics.ex @@ -718,7 +718,7 @@ defmodule Oli.Delivery.Metrics do end) end - defp aggregate_raw_proficiency([]), do: proficiency_range(nil) + defp aggregate_raw_proficiency([]), do: proficiency_range(nil, 0) defp aggregate_raw_proficiency(raw_values) do {total_correct, total_count} = @@ -728,7 +728,7 @@ defmodule Oli.Delivery.Metrics do proficiency_value = if total_count == 0, do: 0, else: total_correct / total_count - proficiency_range(proficiency_value) + proficiency_range(proficiency_value, total_count) end def raw_proficiency_per_learning_objective(%Section{analytics_version: :v1, slug: section_slug}) do @@ -945,12 +945,12 @@ defmodule Oli.Delivery.Metrics do fragment( "CAST(COUNT(CASE WHEN ? THEN 1 END) as float) / CAST(COUNT(*) as float)", sn.correct - )} + ), fragment("CAST(COUNT(*) as float)")} ) Repo.all(query) - |> Enum.into(%{}, fn {student_id, proficiency} -> - {student_id, proficiency_range(proficiency)} + |> Enum.into(%{}, fn {student_id, proficiency, num_first_attempts} -> + {student_id, proficiency_range(proficiency, num_first_attempts)} end) end @@ -992,12 +992,12 @@ defmodule Oli.Delivery.Metrics do "CAST(SUM(?) as float) / NULLIF(CAST(SUM(?) as float), 0.0)", summary.num_first_attempts_correct, summary.num_first_attempts - )} + ), sum(summary.num_first_attempts)} ) Repo.all(query) - |> Enum.into(%{}, fn {student_id, proficiency} -> - {student_id, proficiency_range(proficiency)} + |> Enum.into(%{}, fn {student_id, proficiency, num_first_attempts} -> + {student_id, proficiency_range(proficiency, num_first_attempts)} end) end @@ -1088,12 +1088,12 @@ defmodule Oli.Delivery.Metrics do fragment( "CAST(COUNT(CASE WHEN ? THEN 1 END) as float) / CAST(COUNT(*) as float)", sn.correct - )} + ), fragment("CAST(COUNT(*) as float)")} ) Repo.all(query) - |> Enum.into(%{}, fn {resource_id, proficiency} -> - {resource_id, proficiency_range(proficiency)} + |> Enum.into(%{}, fn {resource_id, proficiency, num_first_attempts} -> + {resource_id, proficiency_range(proficiency, num_first_attempts)} end) end @@ -1117,13 +1117,14 @@ defmodule Oli.Delivery.Metrics do "CAST(? as float) / NULLIF(CAST(? as float), 0.0)", summary.num_first_attempts_correct, summary.num_first_attempts - ) + ), + summary.num_first_attempts } ) Repo.all(query) - |> Enum.into(%{}, fn {resource_id, proficiency} -> - {resource_id, proficiency_range(proficiency)} + |> Enum.into(%{}, fn {resource_id, proficiency, num_first_attempts} -> + {resource_id, proficiency_range(proficiency, num_first_attempts)} end) end @@ -1155,12 +1156,12 @@ defmodule Oli.Delivery.Metrics do fragment( "CAST(COUNT(CASE WHEN ? THEN 1 END) as float) / CAST(COUNT(*) as float)", sn.correct - )} + ), fragment("CAST(COUNT(*) as float)")} ) Repo.all(query) - |> Enum.into(%{}, fn {student_id, proficiency} -> - {student_id, proficiency_range(proficiency)} + |> Enum.into(%{}, fn {student_id, proficiency, num_first_attempts} -> + {student_id, proficiency_range(proficiency, num_first_attempts)} end) end @@ -1182,12 +1183,12 @@ defmodule Oli.Delivery.Metrics do "CAST(? as float) / NULLIF(CAST(? as float), 0.0)", summary.num_first_attempts_correct, summary.num_first_attempts - )} + ), summary.num_first_attempts} ) Repo.all(query) - |> Enum.into(%{}, fn {student_id, proficiency} -> - {student_id, proficiency_range(proficiency)} + |> Enum.into(%{}, fn {student_id, proficiency, num_first_attempts} -> + {student_id, proficiency_range(proficiency, num_first_attempts)} end) end @@ -1216,12 +1217,12 @@ defmodule Oli.Delivery.Metrics do fragment( "CAST(COUNT(CASE WHEN ? THEN 1 END) as float) / CAST(COUNT(*) as float)", sn.correct - )} + ), fragment("CAST(COUNT(*) as float)")} ) Repo.all(query) - |> Enum.into(%{}, fn {page_id, proficiency} -> - {page_id, proficiency_range(proficiency)} + |> Enum.into(%{}, fn {page_id, proficiency, num_first_attempts} -> + {page_id, proficiency_range(proficiency, num_first_attempts)} end) end @@ -1243,19 +1244,20 @@ defmodule Oli.Delivery.Metrics do "? / NULLIF(?, 0)", summary.num_first_attempts_correct, summary.num_first_attempts - )} + ), summary.num_first_attempts} ) Repo.all(query) - |> Enum.into(%{}, fn {page_id, proficiency} -> - {page_id, proficiency_range(proficiency)} + |> Enum.into(%{}, fn {page_id, proficiency, num_first_attempts} -> + {page_id, proficiency_range(proficiency, num_first_attempts)} 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" - def proficiency_range(_proficiency), do: "High" + def proficiency_range(_, num_first_attempts) when num_first_attempts < 3, do: "Not enough data" + def proficiency_range(nil, _num_first_attempts), do: "Not enough data" + def proficiency_range(proficiency, _num_first_attempts) when proficiency <= 0.5, do: "Low" + def proficiency_range(proficiency, _num_first_attempts) when proficiency <= 0.8, do: "Medium" + def proficiency_range(_proficiency, _num_first_attempts), do: "High" def progress_range(nil), do: "Not enough data" def progress_range(progress) when progress <= 0.5, do: "Low" @@ -1432,7 +1434,7 @@ defmodule Oli.Delivery.Metrics do _ -> correct / total end - {container_id, proficiency_range(proficiency)} + {container_id, proficiency_range(proficiency, total)} end) end diff --git a/lib/oli/delivery/paywall.ex b/lib/oli/delivery/paywall.ex index e324f75b77c..48c3acdf73c 100644 --- a/lib/oli/delivery/paywall.ex +++ b/lib/oli/delivery/paywall.ex @@ -137,7 +137,7 @@ defmodule Oli.Delivery.Paywall do false _ -> - case Date.compare(Date.utc_today(), Date.add(start_date, days)) do + case Date.compare(Oli.Date.utc_today(), Date.add(start_date, days)) do :lt -> true :eq -> true _ -> false @@ -145,7 +145,7 @@ defmodule Oli.Delivery.Paywall do end :relative_to_student -> - Date.compare(Date.utc_today(), Date.add(inserted_at, days)) == :lt + Date.compare(Oli.Date.utc_today(), Date.add(inserted_at, days)) == :lt end end @@ -157,12 +157,15 @@ defmodule Oli.Delivery.Paywall do case strategy do :relative_to_section -> case start_date do - nil -> 0 - _ -> -DateTime.diff(DateTime.utc_now(), DateTime.add(start_date, days * 24 * 60 * 60)) + nil -> + 0 + + _ -> + -DateTime.diff(Oli.DateTime.utc_now(), DateTime.add(start_date, days * 24 * 60 * 60)) end :relative_to_student -> - -DateTime.diff(DateTime.utc_now(), DateTime.add(inserted_at, days * 24 * 60 * 60)) + -DateTime.diff(Oli.DateTime.utc_now(), DateTime.add(inserted_at, days * 24 * 60 * 60)) end end @@ -195,7 +198,7 @@ defmodule Oli.Delivery.Paywall do end defp create_codes_for_section(%Section{id: id, amount: amount}, number_of_codes) do - now = DateTime.utc_now() + now = Oli.DateTime.utc_now() Repo.transaction(fn _ -> case unique_codes(number_of_codes) do @@ -368,7 +371,7 @@ defmodule Oli.Delivery.Paywall do enrollment_id: id, pending_user_id: user.id, pending_section_id: section.id, - application_date: DateTime.utc_now() + application_date: Oli.DateTime.utc_now() }) _ -> diff --git a/lib/oli/delivery/sections.ex b/lib/oli/delivery/sections.ex index c9c76171b82..ef8d91e89fd 100644 --- a/lib/oli/delivery/sections.ex +++ b/lib/oli/delivery/sections.ex @@ -4709,7 +4709,7 @@ defmodule Oli.Delivery.Sections do Map.merge(objective, %{ objective: objective.title, objective_resource_id: objective.resource_id, - student_proficiency_obj: calc.(correct, total) |> Metrics.proficiency_range(), + student_proficiency_obj: Metrics.proficiency_range(calc.(correct, total), total), subobjective: "", subobjective_resource_id: nil, student_proficiency_subobj: "" @@ -4729,10 +4729,11 @@ defmodule Oli.Delivery.Sections do objective: objective.title, objective_resource_id: objective.resource_id, student_proficiency_obj: - calc.(parent_correct, parent_total) |> Metrics.proficiency_range(), + Metrics.proficiency_range(calc.(parent_correct, parent_total), parent_total), subobjective: sub_objective.title, subobjective_resource_id: sub_objective.resource_id, - student_proficiency_subobj: calc.(correct, total) |> Metrics.proficiency_range() + student_proficiency_subobj: + Metrics.proficiency_range(calc.(correct, total), total) }) end) diff --git a/lib/oli/delivery/sections/section.ex b/lib/oli/delivery/sections/section.ex index 97cbde281e9..15886c86d4b 100644 --- a/lib/oli/delivery/sections/section.ex +++ b/lib/oli/delivery/sections/section.ex @@ -38,7 +38,7 @@ defmodule Oli.Delivery.Sections.Section do field(:open_and_free, :boolean, default: false) field(:requires_enrollment, :boolean, default: false) field(:has_experiments, :boolean, default: false) - field(:analytics_version, Ecto.Enum, values: [:v1, :v2], default: :v1) + field(:analytics_version, Ecto.Enum, values: [:v1, :v2], default: :v2) field(:status, Ecto.Enum, values: [:active, :deleted, :archived], default: :active) field(:invite_token, :string) @@ -159,6 +159,8 @@ defmodule Oli.Delivery.Sections.Section do field(:encouraging_subtitle, :string) + field(:agenda, :boolean, default: false) + timestamps(type: :utc_datetime) end @@ -219,7 +221,8 @@ defmodule Oli.Delivery.Sections.Section do :apply_major_updates, :assistant_enabled, :welcome_title, - :encouraging_subtitle + :encouraging_subtitle, + :agenda ]) |> cast_embed(:customizations, required: false) |> validate_required(@required_fields) diff --git a/lib/oli/grading.ex b/lib/oli/grading.ex index 81e0b9aea69..6dac19c34db 100644 --- a/lib/oli/grading.ex +++ b/lib/oli/grading.ex @@ -232,9 +232,13 @@ defmodule Oli.Grading do join: rev in Revision, on: rev.id == pr.revision_id, left_join: ra in ResourceAccess, - on: ra.resource_id == rev.resource_id and ra.user_id == ^student_id, + on: + ra.section_id == ^section_id and ra.resource_id == sr.resource_id and + ra.user_id == ^student_id, where: - sec.id == ^section_id and rev.deleted == false and + sec.id == ^section_id and + sr.section_id == ^section_id and + rev.deleted == false and rev.resource_type_id == ^resource_type_id and rev.graded == true, select: %GradebookScore{ diff --git a/lib/oli/rendering/activity/html.ex b/lib/oli/rendering/activity/html.ex index 270ef673f0c..a3ec40098a4 100644 --- a/lib/oli/rendering/activity/html.ex +++ b/lib/oli/rendering/activity/html.ex @@ -194,7 +194,8 @@ defmodule Oli.Rendering.Activity.Html do end, renderPointMarkers: render_opts.render_point_markers, isAnnotationLevel: true, - variables: variables + variables: variables, + pageLinkParams: Enum.into(context.page_link_params, %{}) } |> Poison.encode!() |> HtmlEntities.encode() diff --git a/lib/oli/rendering/content/html.ex b/lib/oli/rendering/content/html.ex index bbe46f3c5c1..cf0e19e4907 100644 --- a/lib/oli/rendering/content/html.ex +++ b/lib/oli/rendering/content/html.ex @@ -4,6 +4,9 @@ defmodule Oli.Rendering.Content.Html do Important: any changes to this file must be replicated in writers/html.ts for activity rendering. """ + + use OliWeb, :verified_routes + import Oli.Utils import Oli.Rendering.Utils @@ -710,7 +713,7 @@ defmodule Oli.Rendering.Content.Html do end defp internal_link( - %Context{section_slug: section_slug, mode: mode, project_slug: project_slug}, + %Context{section_slug: section_slug, mode: mode, project_slug: project_slug} = context, next, href, opts \\ [] @@ -733,7 +736,7 @@ defmodule Oli.Rendering.Content.Html do "/sections/#{section_slug}/preview/page/#{revision_slug_from_course_link(href)}" _ -> - "/sections/#{section_slug}/page/#{revision_slug_from_course_link(href)}" + ~p"/sections/#{section_slug}/lesson/#{revision_slug_from_course_link(href)}?#{context.page_link_params}" end end diff --git a/lib/oli/rendering/context.ex b/lib/oli/rendering/context.ex index 1f197123e1e..f09c50c888e 100644 --- a/lib/oli/rendering/context.ex +++ b/lib/oli/rendering/context.ex @@ -32,5 +32,6 @@ defmodule Oli.Rendering.Context do learning_language: nil, effective_settings: nil, is_liveview: false, - is_annotation_level: true + is_annotation_level: true, + page_link_params: [] end diff --git a/lib/oli/resources/explanation_strategy.ex b/lib/oli/resources/explanation_strategy.ex index 52bad468113..c0d061fcd68 100644 --- a/lib/oli/resources/explanation_strategy.ex +++ b/lib/oli/resources/explanation_strategy.ex @@ -15,6 +15,7 @@ defmodule Oli.Resources.ExplanationStrategy do embedded_schema do field :type, Ecto.Enum, values: [ + :none, :after_max_resource_attempts_exhausted, :after_set_num_attempts ] diff --git a/lib/oli/utils/db_seeder.ex b/lib/oli/utils/db_seeder.ex index 5c17ac78136..a7af3593a21 100644 --- a/lib/oli/utils/db_seeder.ex +++ b/lib/oli/utils/db_seeder.ex @@ -723,7 +723,8 @@ defmodule Oli.Seeder do open_and_free: true, context_id: UUID.uuid4(), institution_id: mappings.institution.id, - base_project_id: project.id + base_project_id: project.id, + analytics_version: :v1 }) |> then(fn {:ok, section} -> section end) |> Sections.create_section_resources(pub1) diff --git a/lib/oli_web/components/delivery/students/students.ex b/lib/oli_web/components/delivery/students/students.ex index 5fee62d3fed..4330667f5ce 100644 --- a/lib/oli_web/components/delivery/students/students.ex +++ b/lib/oli_web/components/delivery/students/students.ex @@ -231,7 +231,7 @@ defmodule OliWeb.Components.Delivery.Students do defp maybe_filter_by_card(students, :low_proficiency, filter_by) do Enum.filter(students, fn student -> - Metrics.proficiency_range(student.overall_proficiency) == "Low" || + student.overall_proficiency == "Low" || (is_nil(student.overall_proficiency) and is_learner_selected(student, filter_by)) end) end @@ -1292,8 +1292,8 @@ defmodule OliWeb.Components.Delivery.Students do end), low_proficiency: Enum.count(students, fn student -> - (Metrics.proficiency_range(student.overall_proficiency) == "Low" || - is_nil(student.overall_proficiency)) and is_learner_selected(student, filter_by) + student.overall_proficiency == "Low" || + (is_nil(student.overall_proficiency) and is_learner_selected(student, filter_by)) end), zero_interaction_in_a_week: Enum.count(students, fn student -> diff --git a/lib/oli_web/components/layouts/student_delivery.html.heex b/lib/oli_web/components/layouts/student_delivery.html.heex index 888af5b7c08..330764cf333 100644 --- a/lib/oli_web/components/layouts/student_delivery.html.heex +++ b/lib/oli_web/components/layouts/student_delivery.html.heex @@ -36,6 +36,25 @@ id: "dialogue-window" ) %> <% end %> + <%= @inner_content %> diff --git a/lib/oli_web/components/layouts/student_delivery_lesson.html.heex b/lib/oli_web/components/layouts/student_delivery_lesson.html.heex index 7328fb64684..a7d205fbc10 100644 --- a/lib/oli_web/components/layouts/student_delivery_lesson.html.heex +++ b/lib/oli_web/components/layouts/student_delivery_lesson.html.heex @@ -50,6 +50,26 @@ />
+ +
maybe_decode_explanation_strategy |> maybe_decode_intro_content end - defp maybe_decode_explanation_strategy(revision_params) do - case revision_params do - %{"explanation_strategy" => %{"type" => "none"}} -> - Map.put(revision_params, "explanation_strategy", nil) - - _ -> - revision_params - end - end - defp maybe_decode_intro_content(revision_params) do case revision_params do %{"intro_content" => intro_content} when intro_content in ["", nil] -> diff --git a/lib/oli_web/live/curriculum/entries/options_modal.ex b/lib/oli_web/live/curriculum/entries/options_modal.ex index 10ac718fb33..83bcc40cbec 100644 --- a/lib/oli_web/live/curriculum/entries/options_modal.ex +++ b/lib/oli_web/live/curriculum/entries/options_modal.ex @@ -384,19 +384,26 @@ defmodule OliWeb.Curriculum.OptionsModalContent do ) } /> - <%= case Map.get(@form[:explanation_strategy].value || %{}, :type) do %> - <% :after_set_num_attempts -> %> -
- <.input - name="revision[explanation_strategy][set_num_attempts]" - type="number" - class="form-control" - placeholder="# of Attempts" - field={es[:set_num_attempts]} - /> -
- <% _ -> %> - <% end %> +
+ <.input + :if={ + Ecto.Changeset.get_field( + @form.source, + :explanation_strategy + ) && + Ecto.Changeset.get_field( + @form.source, + :explanation_strategy + ).type == :after_set_num_attempts + } + name="revision[explanation_strategy][set_num_attempts]" + type="number" + class="form-control" + placeholder="# of Attempts" + min={1} + field={es[:set_num_attempts]} + /> +
diff --git a/lib/oli_web/live/delivery/student/index_live.ex b/lib/oli_web/live/delivery/student/index_live.ex index 3355be976a1..155b3f5e5b2 100644 --- a/lib/oli_web/live/delivery/student/index_live.ex +++ b/lib/oli_web/live/delivery/student/index_live.ex @@ -17,7 +17,9 @@ defmodule OliWeb.Delivery.Student.IndexLive do current_user_id = socket.assigns[:current_user].id schedule_for_current_week_and_next_week = - Sections.get_schedule_for_current_and_next_week(section, current_user_id) + if section.agenda, + do: Sections.get_schedule_for_current_and_next_week(section, current_user_id), + else: nil nearest_upcoming_lesson = section @@ -138,7 +140,10 @@ defmodule OliWeb.Delivery.Student.IndexLive do />
-
+
<.agenda section_slug={@section_slug} schedule_for_current_week_and_next_week={@schedule_for_current_week_and_next_week} diff --git a/lib/oli_web/live/delivery/student/utils.ex b/lib/oli_web/live/delivery/student/utils.ex index b28438b9379..4bbd9a2bcf3 100644 --- a/lib/oli_web/live/delivery/student/utils.ex +++ b/lib/oli_web/live/delivery/student/utils.ex @@ -429,7 +429,14 @@ defmodule OliWeb.Delivery.Student.Utils do # to apparently not be used by the page template: # project_slug: base_project_slug, # submitted_surveys: submitted_surveys, - resource_attempt: hd(page_context.resource_attempts) + resource_attempt: hd(page_context.resource_attempts), + page_link_params: + build_page_link_params( + assigns.section.slug, + assigns.page_context.page, + assigns.request_path, + assigns.selected_view + ) } attempt_content = get_attempt_content(page_context) @@ -519,12 +526,15 @@ defmodule OliWeb.Delivery.Student.Utils do """ @spec days_difference(DateTime.t(), SessionContext.t()) :: String.t() def days_difference(resource_end_date, context) do - localized_end_date = - resource_end_date - |> OliWeb.Common.FormatDateTime.maybe_localized_datetime(context) - |> DateTime.to_date() - - today = context.local_tz |> Oli.DateTime.now!() |> DateTime.to_date() + {localized_end_date, today} = + case FormatDateTime.maybe_localized_datetime(resource_end_date, context) do + {:not_localized, datetime} -> + {DateTime.to_date(datetime), Oli.DateTime.utc_now() |> DateTime.to_date()} + + localized_datetime -> + {DateTime.to_date(localized_datetime), + context.local_tz |> Oli.DateTime.now!() |> DateTime.to_date()} + end case Timex.diff(localized_end_date, today, :days) do 0 -> @@ -707,4 +717,17 @@ defmodule OliWeb.Delivery.Student.Utils do {f, _s} -> f end end + + defp build_page_link_params(section_slug, page, request_path, selected_view) do + current_page_path = + lesson_live_path(section_slug, page.slug, + request_path: request_path, + selected_view: selected_view + ) + + [ + request_path: current_page_path, + selected_view: selected_view + ] + end end diff --git a/lib/oli_web/live/grades/grades.ex b/lib/oli_web/live/grades/grades.ex index 188ab5e4f03..64c62f55017 100644 --- a/lib/oli_web/live/grades/grades.ex +++ b/lib/oli_web/live/grades/grades.ex @@ -4,7 +4,6 @@ defmodule OliWeb.Grades.GradesLive do alias Oli.Grading alias Lti_1p3.Tool.Services.AGS alias Lti_1p3.Tool.Services.AGS.LineItem - alias Lti_1p3.Tool.Services.NRPS alias Oli.Delivery.Attempts.Core, as: Attempts alias Oli.Delivery.Sections alias Oli.Delivery.Attempts.PageLifecycle.Broadcaster @@ -193,32 +192,35 @@ defmodule OliWeb.Grades.GradesLive do provider.fetch_access_token(registration, Grading.ags_scopes(), host()) end - defp fetch_students(access_token, section) do + defp fetch_students(_access_token, section) do # Query the db to find all enrolled students students = Sections.fetch_students(section.slug) - # If NRPS is enabled, request the latest view of the course membership - # and filter our enrolled students to that list. This step avoids us - # ever sending grade posts for students that have dropped the class. - # Those requests would simply fail, but this extra step eliminates making - # those requests altogether. - if section.nrps_enabled do - case NRPS.fetch_memberships(section.nrps_context_memberships_url, access_token) do - {:ok, memberships} -> - # get a set of the subs corresponding to Active students - subs = - Enum.filter(memberships, fn m -> m.status == "Active" end) - |> Enum.map(fn m -> m.user_id end) - |> MapSet.new() - - Enum.filter(students, fn s -> MapSet.member?(subs, s.sub) end) - - _ -> - students - end - else - students - end + # ## MER-3566 - Disable NRPS course membership filtering for now + # # If NRPS is enabled, request the latest view of the course membership + # # and filter our enrolled students to that list. This step avoids us + # # ever sending grade posts for students that have dropped the class. + # # Those requests would simply fail, but this extra step eliminates making + # # those requests altogether. + # if section.nrps_enabled do + # case NRPS.fetch_memberships(section.nrps_context_memberships_url, access_token) do + # {:ok, memberships} -> + # # get a set of the subs corresponding to Active students + # subs = + # Enum.filter(memberships, fn m -> m.status == "Active" end) + # |> Enum.map(fn m -> m.user_id end) + # |> MapSet.new() + + # Enum.filter(students, fn s -> MapSet.member?(subs, s.sub) end) + + # _ -> + # students + # end + # else + # students + # end + + students end # Sorts the given list of pages by the order within the hierarchy. diff --git a/lib/oli_web/live/new_course/new_course.ex b/lib/oli_web/live/new_course/new_course.ex index 797752154db..5d1b5b16464 100644 --- a/lib/oli_web/live/new_course/new_course.ex +++ b/lib/oli_web/live/new_course/new_course.ex @@ -269,7 +269,8 @@ defmodule OliWeb.Delivery.NewCourse do course_section_number: section_params.course_section_number, start_date: section_params.start_date, end_date: section_params.end_date, - preferred_scheduling_time: section_params.preferred_scheduling_time + preferred_scheduling_time: section_params.preferred_scheduling_time, + analytics_version: :v2 } ) do {:ok, section} -> diff --git a/lib/oli_web/live/sections/overview_view.ex b/lib/oli_web/live/sections/overview_view.ex index da58bea7df8..04352cec19f 100644 --- a/lib/oli_web/live/sections/overview_view.ex +++ b/lib/oli_web/live/sections/overview_view.ex @@ -293,6 +293,20 @@ defmodule OliWeb.Sections.OverviewView do } ) %> + +
+
+ Enable Agenda + <.toggle_switch + class="ml-4" + checked={@section.agenda} + on_toggle="toggle_agenda" + name="toggle_agenda" + /> +
+
+
+
- + """ end diff --git a/lib/oli_web/live_session_plugs/set_paywall_summary.ex b/lib/oli_web/live_session_plugs/set_paywall_summary.ex new file mode 100644 index 00000000000..77183bd3bb0 --- /dev/null +++ b/lib/oli_web/live_session_plugs/set_paywall_summary.ex @@ -0,0 +1,20 @@ +defmodule OliWeb.LiveSessionPlugs.SetPaywallSummary do + import Phoenix.Component, only: [assign: 2] + + alias Oli.Delivery.Paywall + + def on_mount( + :default, + _params, + _session, + %{assigns: %{current_user: current_user, section: section}} = socket + ) + when not is_nil(section) and not is_nil(current_user) do + {:cont, + assign(socket, + paywall_summary: Paywall.summarize_access(current_user, section) + )} + end + + def on_mount(:default, _params, _session, socket), do: {:cont, socket} +end diff --git a/lib/oli_web/router.ex b/lib/oli_web/router.ex index 53be1ebe6c3..5316510e395 100644 --- a/lib/oli_web/router.ex +++ b/lib/oli_web/router.ex @@ -1006,7 +1006,8 @@ defmodule OliWeb.Router do OliWeb.LiveSessionPlugs.SetPreviewMode, OliWeb.LiveSessionPlugs.SetSidebar, OliWeb.LiveSessionPlugs.RequireEnrollment, - OliWeb.LiveSessionPlugs.SetNotificationBadges + OliWeb.LiveSessionPlugs.SetNotificationBadges, + OliWeb.LiveSessionPlugs.SetPaywallSummary ] do live("/", Delivery.Student.IndexLive) live("/learn", Delivery.Student.LearnLive) @@ -1028,7 +1029,8 @@ defmodule OliWeb.Router do OliWeb.LiveSessionPlugs.SetBrand, OliWeb.LiveSessionPlugs.SetPreviewMode, OliWeb.LiveSessionPlugs.RequireEnrollment, - OliWeb.LiveSessionPlugs.SetRequestPath + OliWeb.LiveSessionPlugs.SetRequestPath, + OliWeb.LiveSessionPlugs.SetPaywallSummary ] do live("/", Delivery.Student.PrologueLive) end @@ -1047,7 +1049,8 @@ defmodule OliWeb.Router do OliWeb.LiveSessionPlugs.SetBrand, OliWeb.LiveSessionPlugs.SetPreviewMode, OliWeb.LiveSessionPlugs.RequireEnrollment, - OliWeb.LiveSessionPlugs.SetRequestPath + OliWeb.LiveSessionPlugs.SetRequestPath, + OliWeb.LiveSessionPlugs.SetPaywallSummary ] do live("/", Delivery.Student.LessonLive) live("/attempt/:attempt_guid/review", Delivery.Student.ReviewLive) @@ -1065,7 +1068,8 @@ defmodule OliWeb.Router do OliWeb.LiveSessionPlugs.SetBrand, OliWeb.LiveSessionPlugs.SetPreviewMode, OliWeb.LiveSessionPlugs.RequireEnrollment, - OliWeb.LiveSessionPlugs.SetRequestPath + OliWeb.LiveSessionPlugs.SetRequestPath, + OliWeb.LiveSessionPlugs.SetPaywallSummary ] do live("/", Delivery.Student.LessonLive, metadata: %{route_name: :adaptive_lesson}) end diff --git a/priv/repo/migrations/20240730205106_alter_analytics_version_default_value.exs b/priv/repo/migrations/20240730205106_alter_analytics_version_default_value.exs new file mode 100644 index 00000000000..c60018ac2e5 --- /dev/null +++ b/priv/repo/migrations/20240730205106_alter_analytics_version_default_value.exs @@ -0,0 +1,13 @@ +defmodule Oli.Repo.Migrations.AlterAnalyticsVersionDefaultValue do + use Ecto.Migration + + def change do + alter table(:sections) do + modify :analytics_version, :string, default: "v2" + end + + alter table(:projects) do + modify :analytics_version, :string, default: "v2" + end + end +end diff --git a/priv/repo/migrations/20240731170918_add_agenda_flag_to_section.exs b/priv/repo/migrations/20240731170918_add_agenda_flag_to_section.exs new file mode 100644 index 00000000000..d399f18cba1 --- /dev/null +++ b/priv/repo/migrations/20240731170918_add_agenda_flag_to_section.exs @@ -0,0 +1,9 @@ +defmodule Oli.Repo.Migrations.AddAgendaFlagToSection do + use Ecto.Migration + + def change do + alter table(:sections) do + add(:agenda, :boolean, default: false) + end + end +end diff --git a/test/oli/analytics/summary/metrics_v2_test.exs b/test/oli/analytics/summary/metrics_v2_test.exs index 246ddd2aa96..97b97aa77f9 100644 --- a/test/oli/analytics/summary/metrics_v2_test.exs +++ b/test/oli/analytics/summary/metrics_v2_test.exs @@ -132,6 +132,44 @@ defmodule Oli.Analytics.Summary.MetricsV2Test do assert %{^user1_id => "High", ^user2_id => "Low"} = results end + test "proficiency_for_student_per_learning_objective/3 shows Not enough data when num first attempts is < 3", + %{ + user1: user1, + user2: user2, + section: section, + o1: o1, + o2: o2, + o3: o3 + } do + objective_type_id = Oli.Resources.ResourceType.id_for_objective() + {:ok, section} = Oli.Delivery.Sections.update_section(section, %{analytics_version: :v2}) + + id = o1.resource.id + id2 = o2.resource.id + id3 = o3.resource.id + + [ + [-1, -1, section.id, user1.id, id, nil, objective_type_id, 2, 6, 1, 1, 0], + [-1, -1, section.id, user1.id, id2, nil, objective_type_id, 2, 6, 1, 3, 2], + [-1, -1, section.id, user1.id, id3, nil, objective_type_id, 2, 6, 1, 3, 3], + [-1, -1, section.id, user2.id, id, nil, objective_type_id, 2, 4, 0, 1, 1], + [-1, -1, section.id, user2.id, id2, nil, objective_type_id, 2, 4, 0, 4, 2] + ] + |> Enum.each(fn v -> add_resource_summary(v) end) + + results = + Metrics.proficiency_for_student_per_learning_objective( + [o1.revision, o2.revision, o3.revision], + user1.id, + section + ) + + assert Map.keys(results) |> Enum.count() == 3 + assert Map.get(results, id) == "Not enough data" + assert Map.get(results, id2) == "Medium" + assert Map.get(results, id3) == "High" + end + test "proficiency_for_student_per_learning_objective/3", %{ user1: user1, user2: user2, @@ -148,7 +186,7 @@ defmodule Oli.Analytics.Summary.MetricsV2Test do id3 = o3.resource.id [ - [-1, -1, section.id, user1.id, id, nil, objective_type_id, 2, 6, 1, 1, 0], + [-1, -1, section.id, user1.id, id, nil, objective_type_id, 2, 6, 1, 3, 1], [-1, -1, section.id, user1.id, id2, nil, objective_type_id, 2, 6, 1, 3, 2], [-1, -1, section.id, user1.id, id3, nil, objective_type_id, 2, 6, 1, 3, 3], [-1, -1, section.id, user2.id, id, nil, objective_type_id, 2, 4, 0, 1, 1], diff --git a/test/oli/delivery/metrics/learning_proficiency_test.exs b/test/oli/delivery/metrics/learning_proficiency_test.exs index e27654249cd..2b5b58e9131 100644 --- a/test/oli/delivery/metrics/learning_proficiency_test.exs +++ b/test/oli/delivery/metrics/learning_proficiency_test.exs @@ -20,6 +20,13 @@ defmodule Oli.Delivery.Metrics.LearningProficiencyTest do }) end + defp set_snapshots(section, resource, objective, user, result) do + # proficiency calculation requires at least 3 snapshots + set_snapshot(section, resource, objective, user, result) + set_snapshot(section, resource, objective, user, result) + set_snapshot(section, resource, objective, user, result) + end + defp create_project(_conn) do author = insert(:author) project = insert(:project, authors: [author]) @@ -397,25 +404,25 @@ defmodule Oli.Delivery.Metrics.LearningProficiencyTest do module_1: module_1, module_2: module_2 } do - set_snapshot(section, page_1.resource, page_1_obj.resource, student_1, true) - set_snapshot(section, page_1.resource, page_1_obj.resource, student_2, true) - set_snapshot(section, page_1.resource, page_1_obj.resource, student_3, true) - set_snapshot(section, page_1.resource, page_1_obj.resource, student_4, true) - - set_snapshot(section, page_2.resource, page_2_obj.resource, student_1, true) - set_snapshot(section, page_2.resource, page_2_obj.resource, student_2, false) - set_snapshot(section, page_2.resource, page_2_obj.resource, student_3, false) - set_snapshot(section, page_2.resource, page_2_obj.resource, student_4, false) - - set_snapshot(section, page_3.resource, page_3_obj.resource, student_1, false) - set_snapshot(section, page_3.resource, page_3_obj.resource, student_2, true) - set_snapshot(section, page_3.resource, page_3_obj.resource, student_3, false) - set_snapshot(section, page_3.resource, page_3_obj.resource, student_4, false) - - set_snapshot(section, page_4.resource, page_4_obj.resource, student_1, false) - set_snapshot(section, page_4.resource, page_4_obj.resource, student_2, true) - set_snapshot(section, page_4.resource, page_4_obj.resource, student_3, false) - set_snapshot(section, page_4.resource, page_4_obj.resource, student_4, false) + set_snapshots(section, page_1.resource, page_1_obj.resource, student_1, true) + set_snapshots(section, page_1.resource, page_1_obj.resource, student_2, true) + set_snapshots(section, page_1.resource, page_1_obj.resource, student_3, true) + set_snapshots(section, page_1.resource, page_1_obj.resource, student_4, true) + + set_snapshots(section, page_2.resource, page_2_obj.resource, student_1, true) + set_snapshots(section, page_2.resource, page_2_obj.resource, student_2, false) + set_snapshots(section, page_2.resource, page_2_obj.resource, student_3, false) + set_snapshots(section, page_2.resource, page_2_obj.resource, student_4, false) + + set_snapshots(section, page_3.resource, page_3_obj.resource, student_1, false) + set_snapshots(section, page_3.resource, page_3_obj.resource, student_2, true) + set_snapshots(section, page_3.resource, page_3_obj.resource, student_3, false) + set_snapshots(section, page_3.resource, page_3_obj.resource, student_4, false) + + set_snapshots(section, page_4.resource, page_4_obj.resource, student_1, false) + set_snapshots(section, page_4.resource, page_4_obj.resource, student_2, true) + set_snapshots(section, page_4.resource, page_4_obj.resource, student_3, false) + set_snapshots(section, page_4.resource, page_4_obj.resource, student_4, false) proficiency_per_container = Metrics.proficiency_per_container( @@ -445,21 +452,21 @@ defmodule Oli.Delivery.Metrics.LearningProficiencyTest do page_4_objective: page_4_obj, unit_1: unit_1 } do - set_snapshot(section, page_1.resource, page_1_obj.resource, student_1, true) - set_snapshot(section, page_1.resource, page_1_obj.resource, student_2, true) - set_snapshot(section, page_1.resource, page_1_obj.resource, student_3, true) + set_snapshots(section, page_1.resource, page_1_obj.resource, student_1, true) + set_snapshots(section, page_1.resource, page_1_obj.resource, student_2, true) + set_snapshots(section, page_1.resource, page_1_obj.resource, student_3, true) - set_snapshot(section, page_2.resource, page_2_obj.resource, student_1, true) - set_snapshot(section, page_2.resource, page_2_obj.resource, student_2, false) - set_snapshot(section, page_2.resource, page_2_obj.resource, student_3, false) + set_snapshots(section, page_2.resource, page_2_obj.resource, student_1, true) + set_snapshots(section, page_2.resource, page_2_obj.resource, student_2, false) + set_snapshots(section, page_2.resource, page_2_obj.resource, student_3, false) - set_snapshot(section, page_3.resource, page_3_obj.resource, student_1, true) - set_snapshot(section, page_3.resource, page_3_obj.resource, student_2, true) - set_snapshot(section, page_3.resource, page_3_obj.resource, student_3, false) + set_snapshots(section, page_3.resource, page_3_obj.resource, student_1, true) + set_snapshots(section, page_3.resource, page_3_obj.resource, student_2, true) + set_snapshots(section, page_3.resource, page_3_obj.resource, student_3, false) - set_snapshot(section, page_4.resource, page_4_obj.resource, student_1, true) - set_snapshot(section, page_4.resource, page_4_obj.resource, student_2, true) - set_snapshot(section, page_4.resource, page_4_obj.resource, student_3, false) + set_snapshots(section, page_4.resource, page_4_obj.resource, student_1, true) + set_snapshots(section, page_4.resource, page_4_obj.resource, student_2, true) + set_snapshots(section, page_4.resource, page_4_obj.resource, student_3, false) proficiency_per_student_across = Metrics.proficiency_per_student_across(section) @@ -493,10 +500,10 @@ defmodule Oli.Delivery.Metrics.LearningProficiencyTest do module_1: module_1, module_2: module_2 } do - set_snapshot(section, page_1.resource, page_1_obj.resource, student_1, true) - set_snapshot(section, page_2.resource, page_2_obj.resource, student_1, true) - set_snapshot(section, page_3.resource, page_3_obj.resource, student_1, false) - set_snapshot(section, page_4.resource, page_4_obj.resource, student_1, true) + set_snapshots(section, page_1.resource, page_1_obj.resource, student_1, true) + set_snapshots(section, page_2.resource, page_2_obj.resource, student_1, true) + set_snapshots(section, page_3.resource, page_3_obj.resource, student_1, false) + set_snapshots(section, page_4.resource, page_4_obj.resource, student_1, true) proficiency_for_student_1_per_container = Metrics.proficiency_for_student_per_container( @@ -520,8 +527,8 @@ defmodule Oli.Delivery.Metrics.LearningProficiencyTest do page_1_objective: page_1_obj, page_2_objective: page_2_obj } do - set_snapshot(section, page_1.resource, page_1_obj.resource, student_1, true) - set_snapshot(section, page_2.resource, page_2_obj.resource, student_1, false) + set_snapshots(section, page_1.resource, page_1_obj.resource, student_1, true) + set_snapshots(section, page_2.resource, page_2_obj.resource, student_1, false) page_1_proficiency = Metrics.proficiency_per_student_for_page(section, page_1.resource_id) @@ -548,15 +555,15 @@ defmodule Oli.Delivery.Metrics.LearningProficiencyTest do page_2_objective: page_2_obj, page_3_objective: page_3_obj } do - set_snapshot(section, page_1.resource, page_1_obj.resource, student_1, true) - set_snapshot(section, page_1.resource, page_1_obj.resource, student_2, true) - set_snapshot(section, page_1.resource, page_1_obj.resource, student_3, true) - set_snapshot(section, page_2.resource, page_2_obj.resource, student_1, false) - set_snapshot(section, page_2.resource, page_2_obj.resource, student_2, true) - set_snapshot(section, page_2.resource, page_2_obj.resource, student_3, true) - set_snapshot(section, page_3.resource, page_3_obj.resource, student_1, false) - set_snapshot(section, page_3.resource, page_3_obj.resource, student_2, false) - set_snapshot(section, page_3.resource, page_3_obj.resource, student_3, true) + set_snapshots(section, page_1.resource, page_1_obj.resource, student_1, true) + set_snapshots(section, page_1.resource, page_1_obj.resource, student_2, true) + set_snapshots(section, page_1.resource, page_1_obj.resource, student_3, true) + set_snapshots(section, page_2.resource, page_2_obj.resource, student_1, false) + set_snapshots(section, page_2.resource, page_2_obj.resource, student_2, true) + set_snapshots(section, page_2.resource, page_2_obj.resource, student_3, true) + set_snapshots(section, page_3.resource, page_3_obj.resource, student_1, false) + set_snapshots(section, page_3.resource, page_3_obj.resource, student_2, false) + set_snapshots(section, page_3.resource, page_3_obj.resource, student_3, true) proficiency_per_page = Metrics.proficiency_per_page(section, [ @@ -580,9 +587,9 @@ defmodule Oli.Delivery.Metrics.LearningProficiencyTest do page_2_objective: page_2_obj, page_3_objective: page_3_obj } do - set_snapshot(section, page_1.resource, page_1_obj.resource, student_1, true) - set_snapshot(section, page_2.resource, page_2_obj.resource, student_1, false) - set_snapshot(section, page_3.resource, page_3_obj.resource, student_1, false) + set_snapshots(section, page_1.resource, page_1_obj.resource, student_1, true) + set_snapshots(section, page_2.resource, page_2_obj.resource, student_1, false) + set_snapshots(section, page_3.resource, page_3_obj.resource, student_1, false) proficiency_for_student_per_page = Metrics.proficiency_for_student_per_page(section, student_1.id) @@ -610,12 +617,12 @@ defmodule Oli.Delivery.Metrics.LearningProficiencyTest do page_4_objective: page_4_obj, page_5_objective: page_5_obj } do - set_snapshot(section, page_1.resource, page_1_obj.resource, student_1, true) - set_snapshot(section, page_2.resource, page_2_obj.resource, student_1, false) - set_snapshot(section, page_3.resource, page_3_obj.resource, student_1, false) - set_snapshot(section, page_5.resource, subobjective_a.resource, student_1, true) - set_snapshot(section, page_5.resource, subobjective_b.resource, student_1, true) - set_snapshot(section, page_5.resource, subobjective_c.resource, student_1, true) + set_snapshots(section, page_1.resource, page_1_obj.resource, student_1, true) + set_snapshots(section, page_2.resource, page_2_obj.resource, student_1, false) + set_snapshots(section, page_3.resource, page_3_obj.resource, student_1, false) + set_snapshots(section, page_5.resource, subobjective_a.resource, student_1, true) + set_snapshots(section, page_5.resource, subobjective_b.resource, student_1, true) + set_snapshots(section, page_5.resource, subobjective_c.resource, student_1, true) proficiency_for_student_per_learning_objective = Metrics.proficiency_for_student_per_learning_objective( @@ -633,9 +640,9 @@ defmodule Oli.Delivery.Metrics.LearningProficiencyTest do assert proficiency_for_student_per_learning_objective[page_5_obj.resource_id] == "High" - set_snapshot(section, page_5.resource, subobjective_a.resource, student_2, true) - set_snapshot(section, page_5.resource, subobjective_b.resource, student_2, false) - set_snapshot(section, page_5.resource, subobjective_c.resource, student_2, true) + set_snapshots(section, page_5.resource, subobjective_a.resource, student_2, true) + set_snapshots(section, page_5.resource, subobjective_b.resource, student_2, false) + set_snapshots(section, page_5.resource, subobjective_c.resource, student_2, true) proficiency_for_student_2_per_learning_objective = Metrics.proficiency_for_student_per_learning_objective( @@ -651,9 +658,9 @@ defmodule Oli.Delivery.Metrics.LearningProficiencyTest do assert proficiency_for_student_2_per_learning_objective[page_5_obj.resource_id] == "Medium" - set_snapshot(section, page_5.resource, subobjective_a.resource, student_3, true) - set_snapshot(section, page_5.resource, subobjective_b.resource, student_3, false) - set_snapshot(section, page_5.resource, subobjective_c.resource, student_3, false) + set_snapshots(section, page_5.resource, subobjective_a.resource, student_3, true) + set_snapshots(section, page_5.resource, subobjective_b.resource, student_3, false) + set_snapshots(section, page_5.resource, subobjective_c.resource, student_3, false) proficiency_for_student_3_per_learning_objective = Metrics.proficiency_for_student_per_learning_objective( diff --git a/test/oli/delivery/paywall_test.exs b/test/oli/delivery/paywall_test.exs index 714e73ecd3e..4ecb85fa9f6 100644 --- a/test/oli/delivery/paywall_test.exs +++ b/test/oli/delivery/paywall_test.exs @@ -22,6 +22,7 @@ defmodule Oli.Delivery.PaywallTest do describe "summarize_access" do setup do + stub_real_current_time() map = Seeder.base_project_with_resource2() {:ok, _} = Publishing.publish_project(map.project, "some changes", map.author.id) @@ -180,6 +181,7 @@ defmodule Oli.Delivery.PaywallTest do describe "redeeming codes" do setup do + stub_real_current_time() map = Seeder.base_project_with_resource2() {:ok, _} = Publishing.publish_project(map.project, "some changes", map.author.id) diff --git a/test/oli/delivery_test.exs b/test/oli/delivery_test.exs index ea81e0e1db3..95c13ce0ee8 100644 --- a/test/oli/delivery_test.exs +++ b/test/oli/delivery_test.exs @@ -72,6 +72,7 @@ defmodule Oli.DeliveryTest do base_project: map.project, open_and_free: false, registration_open: false, + analytics_version: :v2, type: :blueprint, title: "Product 1", slug: "product_1" @@ -141,6 +142,7 @@ defmodule Oli.DeliveryTest do assert returned_section.institution_id == context.institution.id assert returned_section.base_project_id == context.publication.project_id assert returned_section.lti_1p3_deployment_id == context.deployment.id + assert returned_section.analytics_version == :v2 # User is enrolled as instructor instructors = Sections.instructors_per_section([returned_section.id]) @@ -228,11 +230,7 @@ defmodule Oli.DeliveryTest do ) assert {:ok, returned_section} = - Delivery.create_section( - "product:#{context.product.id}", - context.user, - lti_params - ) + Delivery.create_section("product:#{context.product.id}", context.user, lti_params) # Check section fields assert returned_section.type == :enrollable diff --git a/test/oli/rendering/content/html_test.exs b/test/oli/rendering/content/html_test.exs index 63cc142fdd5..fea2761ec90 100644 --- a/test/oli/rendering/content/html_test.exs +++ b/test/oli/rendering/content/html_test.exs @@ -32,7 +32,7 @@ defmodule Oli.Content.Content.HtmlTest do "

The American colonials proclaimed "no taxation without representation" assert rendered_html_string =~ - "Page Two" + "Page Two" assert rendered_html_string =~ "Stamp Act Congress" diff --git a/test/oli_web/controllers/api/payment_controller_test.exs b/test/oli_web/controllers/api/payment_controller_test.exs index 183932bd876..8c49cd14273 100644 --- a/test/oli_web/controllers/api/payment_controller_test.exs +++ b/test/oli_web/controllers/api/payment_controller_test.exs @@ -218,6 +218,8 @@ defmodule OliWeb.PaymentControllerTest do end defp setup_session(%{conn: conn}) do + stub_real_current_time() + map = Seeder.base_project_with_resource2() |> Seeder.create_product(%{title: "My 1st product", amount: Money.new(:USD, 100)}, :prod1) diff --git a/test/oli_web/live/delivery/instructor_dashboard/insights/students_tab_test.exs b/test/oli_web/live/delivery/instructor_dashboard/insights/students_tab_test.exs index 00e5fb5e18a..89d903fac90 100644 --- a/test/oli_web/live/delivery/instructor_dashboard/insights/students_tab_test.exs +++ b/test/oli_web/live/delivery/instructor_dashboard/insights/students_tab_test.exs @@ -174,6 +174,8 @@ defmodule OliWeb.Delivery.InstructorDashboard.StudentsTabTest do instructor: instructor, conn: conn } do + stub_real_current_time() + %{section: section, mod1_pages: mod1_pages, mod1_resource: mod1_resource} = Oli.Seeder.base_project_with_larger_hierarchy() @@ -583,6 +585,7 @@ defmodule OliWeb.Delivery.InstructorDashboard.StudentsTabTest do ## Filtering by Low Proficiency element(view, "div[phx-value-selected=\"low_proficiency\"]") |> render_click() + assert has_element?(view, "p", "None exist") ## Filtering by Zero Interaction in a week diff --git a/test/oli_web/live/delivery/instructor_dashboard/manage/manage_tab_test.exs b/test/oli_web/live/delivery/instructor_dashboard/manage/manage_tab_test.exs index 48763eeb8a9..0cc44105b48 100644 --- a/test/oli_web/live/delivery/instructor_dashboard/manage/manage_tab_test.exs +++ b/test/oli_web/live/delivery/instructor_dashboard/manage/manage_tab_test.exs @@ -68,5 +68,35 @@ defmodule OliWeb.Delivery.InstructorDashboard.ManageTabTest do # Collab Space Group gets rendered assert render(view) =~ "Collaborative Space" end + + test "can enable and disable agenda", %{ + instructor: instructor, + section: section, + conn: conn + } do + Sections.enroll(instructor.id, section.id, [ContextRoles.get_role(:context_instructor)]) + + {:ok, view, _html} = + live_isolated( + conn, + OliWeb.Sections.OverviewView, + session: %{ + "section_slug" => section.slug, + "current_user_id" => instructor.id + } + ) + + refute has_element?(view, "input[name=\"toggle_agenda\"][checked]") + + element(view, "form[phx-change=\"toggle_agenda\"]") + |> render_change(%{}) + + assert has_element?(view, "input[name=\"toggle_agenda\"][checked]") + + element(view, "form[phx-change=\"toggle_agenda\"]") + |> render_change(%{}) + + refute has_element?(view, "input[name=\"toggle_agenda\"][checked]") + end end end diff --git a/test/oli_web/live/delivery/student/index_live_test.exs b/test/oli_web/live/delivery/student/index_live_test.exs index 7b4580d0234..164dcef90d4 100644 --- a/test/oli_web/live/delivery/student/index_live_test.exs +++ b/test/oli_web/live/delivery/student/index_live_test.exs @@ -670,12 +670,52 @@ defmodule OliWeb.Delivery.Student.IndexLiveTest do test "can access when enrolled to course", %{conn: conn, section: section} do stub_current_time(~U[2023-11-04 20:00:00Z]) + Sections.update_section(section, %{ + agenda: true + }) + {:ok, view, _html} = live(conn, ~p"/sections/#{section.slug}") assert has_element?(view, "span", "The best course ever!") assert has_element?(view, "div", "Upcoming Agenda") end + test "renders paywall message when grace period is not over (or gets redirected when over)", + %{ + conn: conn, + section: section + } do + stub_current_time(~U[2024-10-15 20:00:00Z]) + + {:ok, product} = + Sections.update_section(section, %{ + type: :blueprint, + registration_open: true, + requires_payment: true, + amount: Money.new(:USD, 10), + has_grace_period: true, + grace_period_days: 18, + start_date: ~U[2024-10-15 20:00:00Z], + end_date: ~U[2024-11-30 20:00:00Z] + }) + + {:ok, view, _html} = live(conn, ~p"/sections/#{product.slug}") + + assert has_element?( + view, + "div[id=pay_early_message]", + "You have 18 more days remaining in your grace period access of this course" + ) + + # Grace period is over + stub_current_time(~U[2024-11-13 20:00:00Z]) + + redirect_path = "/sections/#{product.slug}/payment" + + {:error, {:redirect, %{to: ^redirect_path}}} = + live(conn, ~p"/sections/#{product.slug}") + end + # test this test "can see welcome title and encouraging subtitle when is set and the student just joined the course", %{ @@ -701,7 +741,8 @@ defmodule OliWeb.Delivery.Student.IndexLiveTest do Sections.update_section(section, %{ welcome_title: welcome_title, - encouraging_subtitle: encouraging_subtitle + encouraging_subtitle: encouraging_subtitle, + agenda: true }) {:ok, view, _html} = live(conn, ~p"/sections/#{section.slug}") @@ -972,6 +1013,42 @@ defmodule OliWeb.Delivery.Student.IndexLiveTest do assert has_element?(view, "div", "17%") end + test "can see upcoming agenda if this option is enabled", %{ + conn: conn, + section: section, + page_1: page_1, + page_2: page_2, + page_3: page_3, + page_4: page_4 + } do + Sections.update_section(section, %{ + agenda: true + }) + + stub_current_time(~U[2023-11-03 21:00:00Z]) + {:ok, view, _html} = live(conn, ~p"/sections/#{section.slug}") + + assert has_element?(view, "div", "Upcoming Agenda") + assert has_element?(view, "div", "This Week") + assert has_element?(view, "div", page_1.title) + assert has_element?(view, "div", page_2.title) + assert has_element?(view, "div", page_3.title) + assert has_element?(view, "div", page_4.title) + end + + test "can not see upcoming agenda if this option is disabled", %{ + conn: conn, + section: section, + page_1: page_1 + } do + stub_current_time(~U[2023-11-03 21:00:00Z]) + {:ok, view, _html} = live(conn, ~p"/sections/#{section.slug}") + + refute has_element?(view, "div", "Upcoming Agenda") + refute has_element?(view, "div", "This Week") + refute has_element?(view, "div", page_1.title) + end + test "do not show hidden pages in upcoming agenda", %{ conn: conn, section: section, diff --git a/test/oli_web/live/delivery/student/learn_live_test.exs b/test/oli_web/live/delivery/student/learn_live_test.exs index f1361730e78..c766d641636 100644 --- a/test/oli_web/live/delivery/student/learn_live_test.exs +++ b/test/oli_web/live/delivery/student/learn_live_test.exs @@ -792,6 +792,42 @@ defmodule OliWeb.Delivery.Student.ContentLiveTest do assert has_element?(view, "h3", "Implementing LiveView") end + test "renders paywall message when grace period is not over (or gets redirected when over)", + %{ + conn: conn, + section: section + } do + stub_current_time(~U[2024-10-15 20:00:00Z]) + + {:ok, product} = + Sections.update_section(section, %{ + type: :blueprint, + registration_open: true, + requires_payment: true, + amount: Money.new(:USD, 10), + has_grace_period: true, + grace_period_days: 18, + start_date: ~U[2024-10-15 20:00:00Z], + end_date: ~U[2024-11-30 20:00:00Z] + }) + + {:ok, view, _html} = live(conn, Utils.learn_live_path(product.slug)) + + assert has_element?( + view, + "div[id=pay_early_message]", + "You have 18 more days remaining in your grace period access of this course" + ) + + # Grace period is over + stub_current_time(~U[2024-11-13 20:00:00Z]) + + redirect_path = "/sections/#{product.slug}/payment" + + {:error, {:redirect, %{to: ^redirect_path}}} = + live(conn, Utils.learn_live_path(product.slug)) + end + test "can see unit intro as first slider card and play the video (if provided)", %{ conn: conn, section: section diff --git a/test/oli_web/live/delivery/student/lesson_live_test.exs b/test/oli_web/live/delivery/student/lesson_live_test.exs index 6185db397cb..1062abdae43 100644 --- a/test/oli_web/live/delivery/student/lesson_live_test.exs +++ b/test/oli_web/live/delivery/student/lesson_live_test.exs @@ -543,6 +543,47 @@ defmodule OliWeb.Delivery.Student.LessonLiveTest do ) end + test "renders paywall message when grace period is not over (or gets redirected when over)", + %{ + conn: conn, + user: user, + section: section, + page_1: page_1 + } do + Sections.enroll(user.id, section.id, [ContextRoles.get_role(:context_learner)]) + Sections.mark_section_visited_for_student(section, user) + + stub_current_time(~U[2024-10-15 20:00:00Z]) + + {:ok, product} = + Sections.update_section(section, %{ + type: :blueprint, + registration_open: true, + requires_payment: true, + amount: Money.new(:USD, 10), + has_grace_period: true, + grace_period_days: 18, + start_date: ~U[2024-10-15 20:00:00Z], + end_date: ~U[2024-11-30 20:00:00Z] + }) + + {:ok, view, _html} = live(conn, Utils.lesson_live_path(product.slug, page_1.slug)) + + assert has_element?( + view, + "div[id=pay_early_message]", + "You have 18 more days remaining in your grace period access of this course" + ) + + # Grace period is over + stub_current_time(~U[2024-11-13 20:00:00Z]) + + redirect_path = "/sections/#{product.slug}/payment" + + {:error, {:redirect, %{to: ^redirect_path}}} = + live(conn, Utils.lesson_live_path(product.slug, page_1.slug)) + end + test "can see practice page content", %{ conn: conn, user: user, diff --git a/test/oli_web/live/delivery/student/utils_test.exs b/test/oli_web/live/delivery/student/utils_test.exs index 33a1bf212f2..852291fba05 100644 --- a/test/oli_web/live/delivery/student/utils_test.exs +++ b/test/oli_web/live/delivery/student/utils_test.exs @@ -39,7 +39,7 @@ defmodule OliWeb.Delivery.Student.UtilsTest do describe "days_difference/1" do setup do - stub_current_time(DateTime.utc_now()) + stub_real_current_time() [ context: %OliWeb.Common.SessionContext{ @@ -92,6 +92,13 @@ defmodule OliWeb.Delivery.Student.UtilsTest do previous_day = ~U[2024-06-24 02:59:59Z] assert Utils.days_difference(previous_day, ctx) == "Past Due by a day" end + + test "still returns 'X days left' when the resource end date cannot be localized" do + ctx = nil + days_ahead = 7 + future_date = Oli.DateTime.utc_now() |> Timex.shift(days: days_ahead) + assert Utils.days_difference(future_date, ctx) == "#{days_ahead} days left" + end end describe "parse_score/1" do diff --git a/test/oli_web/live/new_course/course_details_test.exs b/test/oli_web/live/new_course/course_details_test.exs index 0237e0f1756..edad57f8091 100644 --- a/test/oli_web/live/new_course/course_details_test.exs +++ b/test/oli_web/live/new_course/course_details_test.exs @@ -1,5 +1,6 @@ defmodule OliWeb.NewCourse.CourseDetailsTest do use ExUnit.Case, async: true + alias Oli.Delivery.Sections use OliWeb.ConnCase import Ecto.Query, warn: false @@ -332,6 +333,39 @@ defmodule OliWeb.NewCourse.CourseDetailsTest do assert %{"info" => "Section successfully created."} == assert_redirect(view, ~p"/course") end + + test "creates a section with analytics_version :v2 ", %{conn: conn} = context do + # Factory has a default analytics_version == :v1 + %{section: section} = create_source(context) + + {:ok, view, _html} = live(conn, @live_view_lms_instructor_route) + + {:ok, section} = Sections.update_section(section, %{type: :blueprint}) + + select_source(:lms_instructor_conn, view, section) + + complete_course_name_form(view) + + view + |> element("#open_and_free_form") + |> render_hook("js_form_data_response", %{ + "section" => %{ + class_days: [:monday, :friday], + start_date: DateTime.add(DateTime.utc_now(), 2, :day), + end_date: DateTime.add(DateTime.utc_now(), 62, :day), + preferred_scheduling_time: ~T[23:59:59] + }, + "current_step" => 3 + }) + + # Wait until TaskSupervisor completes to create section + wait_for_completion() + + assert [:v2] == + Oli.Repo.all(Sections.Section) + |> Enum.filter(&(&1.id != section.id)) + |> Enum.map(& &1.analytics_version) + end end defp select_source(:admin, view, source) do diff --git a/test/oli_web/live/payments_live_test.exs b/test/oli_web/live/payments_live_test.exs index d0c5f1c1e99..9eae5ac7441 100644 --- a/test/oli_web/live/payments_live_test.exs +++ b/test/oli_web/live/payments_live_test.exs @@ -68,7 +68,7 @@ defmodule OliWeb.PaymentsLiveTest do end describe "payments" do - setup [:admin_conn, :create_product] + setup [:admin_conn, :create_product, :stub_real_current_time] test "loads correctly when there are no payments", %{conn: conn, product: product} do {:ok, view, _html} = live(conn, live_view_payments_route(product.slug)) diff --git a/test/oli_web/live/products_test.exs b/test/oli_web/live/products_test.exs index 05ad21647d4..296235a499b 100644 --- a/test/oli_web/live/products_test.exs +++ b/test/oli_web/live/products_test.exs @@ -32,6 +32,7 @@ defmodule OliWeb.ProductsLiveTest do product = insert(:section, type: :blueprint, requires_payment: true, amount: Money.new(:USD, 10)) + stub_real_current_time() Paywall.create_payment_codes(product.slug, 20) [product: product] diff --git a/test/oli_web/live/users/user_detail_view_test.exs b/test/oli_web/live/users/user_detail_view_test.exs index 7dc4f21edad..f61fe171418 100644 --- a/test/oli_web/live/users/user_detail_view_test.exs +++ b/test/oli_web/live/users/user_detail_view_test.exs @@ -267,7 +267,7 @@ defmodule OliWeb.Users.UsersDetailViewTest do end describe "Enrolled sections info" do - setup [:admin_conn, :enrolled_student_to_sections] + setup [:admin_conn, :enrolled_student_to_sections, :stub_real_current_time] test "shows enrolled sections section", %{ conn: conn, diff --git a/test/support/test_helpers.ex b/test/support/test_helpers.ex index b09196b59b9..bbc8ad580ee 100644 --- a/test/support/test_helpers.ex +++ b/test/support/test_helpers.ex @@ -49,6 +49,13 @@ defmodule Oli.TestHelpers do end end + def stub_real_current_time(%{conn: conn}) do + stub_real_current_time() + {:ok, conn: conn} + end + + def stub_real_current_time(), do: stub_current_time(DateTime.utc_now()) + def stub_current_time(utc_now) do Mox.stub(Oli.Test.DateTimeMock, :utc_now, fn -> utc_now end)