diff --git a/lib/oli/analytics/by_activity.ex b/lib/oli/analytics/by_activity.ex
index 28d0557c82c..217f09ad328 100644
--- a/lib/oli/analytics/by_activity.ex
+++ b/lib/oli/analytics/by_activity.ex
@@ -18,7 +18,7 @@ defmodule Oli.Analytics.ByActivity do
defp get_base_query(project_slug, filtered_sections) do
subquery =
if filtered_sections != [] do
- DeliveryResolver.revisions_filter_by_section_ids(
+ DeliveryResolver.revisions_by_section_ids(
filtered_sections,
ResourceType.id_for_activity()
)
@@ -39,7 +39,6 @@ defmodule Oli.Analytics.ByActivity do
number_of_attempts: analytics.number_of_attempts,
relative_difficulty: analytics.relative_difficulty
},
- preload: [:resource_type],
- distinct: [activity]
+ preload: [:resource_type]
end
end
diff --git a/lib/oli/analytics/by_objective.ex b/lib/oli/analytics/by_objective.ex
index 256ac807445..1492b375fb7 100644
--- a/lib/oli/analytics/by_objective.ex
+++ b/lib/oli/analytics/by_objective.ex
@@ -20,7 +20,7 @@ defmodule Oli.Analytics.ByObjective do
defp get_base_query(project_slug, activity_objectives, filtered_sections) do
subquery =
if filtered_sections != [] do
- DeliveryResolver.revisions_filter_by_section_ids(
+ DeliveryResolver.revisions_by_section_ids(
filtered_sections,
ResourceType.id_for_objective()
)
diff --git a/lib/oli/analytics/by_page.ex b/lib/oli/analytics/by_page.ex
index 18ffcace7cf..e8a424bcc99 100644
--- a/lib/oli/analytics/by_page.ex
+++ b/lib/oli/analytics/by_page.ex
@@ -22,7 +22,7 @@ defmodule Oli.Analytics.ByPage do
defp get_base_query(project_slug, activity_pages, filtered_sections) do
subquery =
if filtered_sections != [] do
- DeliveryResolver.revisions_filter_by_section_ids(
+ DeliveryResolver.revisions_by_section_ids(
filtered_sections,
ResourceType.id_for_page()
)
@@ -35,9 +35,9 @@ defmodule Oli.Analytics.ByPage do
subquery_activity =
if filtered_sections != [] do
- DeliveryResolver.revisions_filter_by_section_ids(
+ DeliveryResolver.revisions_by_section_ids(
filtered_sections,
- ResourceType.id_for_page()
+ ResourceType.id_for_activity()
)
else
Publishing.query_unpublished_revisions_by_type(
@@ -62,8 +62,7 @@ defmodule Oli.Analytics.ByPage do
number_of_attempts: analytics.number_of_attempts,
relative_difficulty: analytics.relative_difficulty
},
- preload: [:resource_type],
- distinct: [activity]
+ preload: [:resource_type]
)
end
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/delivery/sections.ex b/lib/oli/delivery/sections.ex
index fee608f0f52..76ab3c52b59 100644
--- a/lib/oli/delivery/sections.ex
+++ b/lib/oli/delivery/sections.ex
@@ -2335,7 +2335,8 @@ defmodule Oli.Delivery.Sections do
# create any remaining section resources which are not in the hierarchy
create_nonstructural_section_resources(section.id, [publication_id],
skip_resource_ids: processed_ids,
- required_survey_resource_id: survey_id
+ required_survey_resource_id: survey_id,
+ reference_activity_ids: []
)
root_section_resource_id = section_resources_by_resource_id[root_resource_id].id
@@ -2823,10 +2824,15 @@ defmodule Oli.Delivery.Sections do
|> select([p], p.required_survey_resource_id)
|> Repo.one()
- create_nonstructural_section_resources(section_id, publication_ids,
- skip_resource_ids: processed_resource_ids,
- required_survey_resource_id: survey_id
- )
+ reference_activity_ids = build_reference_activity_ids(hierarchy.children)
+
+ if length(processed_resource_ids) != 1 do
+ create_nonstructural_section_resources(section_id, publication_ids,
+ skip_resource_ids: processed_resource_ids,
+ required_survey_resource_id: survey_id,
+ reference_activity_ids: reference_activity_ids
+ )
+ end
# Rebuild section previous next index
PreviousNextIndex.rebuild(section, hierarchy)
@@ -2838,6 +2844,62 @@ defmodule Oli.Delivery.Sections do
end)
end
+ defp build_reference_activity_ids([]), do: []
+
+ defp build_reference_activity_ids(children_activities) do
+ tuple_of_children_activities =
+ process_activity_ids(children_activities)
+
+ tuple_of_children_activities
+ |> Tuple.to_list()
+ |> Enum.concat()
+ end
+
+ defp process_activity_ids(children_activities) do
+ children_activities
+ |> Enum.reduce({[], []}, fn r, {children_unit_activities_ids, children_activities_ids} ->
+ new_children_unit_activities_ids = build_child_activity_ids_for_units(r.children)
+
+ if Map.has_key?(r.revision, :content) do
+ new_children_activities_ids = build_child_activity_ids(r.revision.content)
+
+ {children_unit_activities_ids ++ new_children_unit_activities_ids,
+ children_activities_ids ++ new_children_activities_ids}
+ else
+ {children_unit_activities_ids ++ new_children_unit_activities_ids,
+ children_activities_ids ++ []}
+ end
+ end)
+ end
+
+ defp build_child_activity_ids_for_units([]), do: []
+
+ defp build_child_activity_ids_for_units(children) do
+ children
+ |> Enum.map(fn c ->
+ if Map.has_key?(c.revision, :content) do
+ build_child_activity_ids(c.revision.content)
+ else
+ []
+ end
+ end)
+ |> List.flatten()
+ end
+
+ defp build_child_activity_ids(%{"model" => nil}), do: []
+
+ defp build_child_activity_ids(%{"model" => model}) do
+ model
+ |> Enum.reduce([], fn item, activity_ids ->
+ case item["activity_id"] do
+ nil -> activity_ids
+ activity_id -> [activity_id | activity_ids]
+ end
+ end)
+ end
+
+ defp build_child_activity_ids(_), do: []
+
def get_contained_pages(%Section{id: section_id}) do
from(cp in ContainedPage,
where: cp.section_id == ^section_id
@@ -3630,10 +3692,11 @@ defmodule Oli.Delivery.Sections do
# any that belong to the resource ids in skip_resource_ids
defp create_nonstructural_section_resources(section_id, publication_ids,
skip_resource_ids: skip_resource_ids,
- required_survey_resource_id: required_survey_resource_id
+ required_survey_resource_id: required_survey_resource_id,
+ reference_activity_ids: reference_activity_ids
) do
published_resources_by_resource_id =
- MinimalHierarchy.published_resources_map(publication_ids)
+ build_published_resources_by_resource_id(publication_ids, reference_activity_ids)
now = DateTime.utc_now() |> DateTime.truncate(:second)
@@ -3677,6 +3740,14 @@ defmodule Oli.Delivery.Sections do
Database.batch_insert_all(SectionResource, section_resource_rows)
end
+ defp build_published_resources_by_resource_id(publication_ids, []),
+ do: MinimalHierarchy.published_resources_map(publication_ids)
+
+ defp build_published_resources_by_resource_id(publication_ids, list_children_section_ids),
+ do:
+ MinimalHierarchy.published_resources_map(publication_ids)
+ |> Map.take(list_children_section_ids)
+
def is_structural?(%Revision{resource_type_id: resource_type_id}) do
container = ResourceType.id_for_container()
diff --git a/lib/oli/publishing/delivery_resolver.ex b/lib/oli/publishing/delivery_resolver.ex
index c2b575626c9..aa2b97189a7 100644
--- a/lib/oli/publishing/delivery_resolver.ex
+++ b/lib/oli/publishing/delivery_resolver.ex
@@ -263,7 +263,7 @@ defmodule Oli.Publishing.DeliveryResolver do
|> emit([:oli, :resolvers, :delivery], :duration)
end
- def revisions_filter_by_section_ids(section_ids, resource_type_id) do
+ def revisions_by_section_ids(section_ids, resource_type_id) do
from(sr in SectionResource,
join: s in Section,
on: s.id == sr.section_id,
@@ -277,7 +277,8 @@ defmodule Oli.Publishing.DeliveryResolver do
join: rev in Revision,
on: rev.id == pr.revision_id,
where: rev.resource_type_id == ^resource_type_id and rev.deleted == false,
- select: rev
+ select: rev,
+ distinct: [rev]
)
end
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/utils/db_seeder.ex b/lib/oli/utils/db_seeder.ex
index 05acb982a8a..b4803f74cca 100644
--- a/lib/oli/utils/db_seeder.ex
+++ b/lib/oli/utils/db_seeder.ex
@@ -198,14 +198,34 @@ defmodule Oli.Seeder do
create_published_resource(publication, container_resource, container_revision)
+ %{resource: _activity_resource, revision: activity_revision} =
+ create_activity(
+ %{
+ activity_type_id: Activities.get_registration_by_slug("oli_short_answer").id,
+ content: %{}
+ },
+ publication,
+ project,
+ author
+ )
+
%{resource: page1, revision: revision1} =
create_page("Page one", publication, project, author)
%{resource: page2, revision: revision2} =
create_page("Page two", publication, project, author, create_sample_content())
+ %{resource: page3, revision: revision3} =
+ create_page(
+ "Page three",
+ publication,
+ project,
+ author,
+ create_activity_content(activity_revision.resource_id)
+ )
+
container_revision =
- attach_pages_to([page1, page2], container_resource, container_revision, publication)
+ attach_pages_to([page1, page2, page3], container_resource, container_revision, publication)
{:ok, pub1} = Publishing.publish_project(project, "some changes", author.id)
@@ -229,8 +249,10 @@ defmodule Oli.Seeder do
|> Map.put(:container, %{resource: container_resource, revision: container_revision})
|> Map.put(:page1, page1)
|> Map.put(:page2, page2)
+ |> Map.put(:page3, page3)
|> Map.put(:revision1, revision1)
|> Map.put(:revision2, revision2)
+ |> Map.put(:revision3, revision3)
|> Map.put(:section, section)
end
@@ -1290,6 +1312,19 @@ defmodule Oli.Seeder do
}
end
+ def create_activity_content(activity_resource_id) do
+ %{
+ "model" => [
+ %{
+ "type" => "activity-reference",
+ "activity_id" => activity_resource_id,
+ "custom" => %{}
+ }
+ ],
+ "advancedDelivery" => false
+ }
+ end
+
def create_sample_content() do
%{
"model" => [
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"""
+
+
+
+
+
+ <.focus_wrap
+ id={"#{@id}-container"}
+ phx-mounted={@show && show_modal(@id)}
+ phx-window-keydown={hide_modal(@on_cancel, @id)}
+ phx-key="escape"
+ phx-click-away={hide_modal(@on_cancel, @id)}
+ class="hidden p-8 sm:p-16 lg:p-20 xl:p-28 relative bg-white dark:bg-black shadow-lg shadow-zinc-700/10 ring-1 ring-zinc-700/10 transition"
+ >
+
+
+
+
+ Close modal
+
+
+
+
+
+ <%= render_slot(@title) %>
+
+
+ <%= render_slot(@subtitle) %>
+
+
+
+
+
+ <%= render_slot(@inner_block) %>
+
+
+
+
+ <.button
+ :for={cancel <- @cancel}
+ phx-click={hide_modal(@on_cancel, @id)}
+ class="bg-transparent text-blue-500 hover:underline hover:bg-transparent"
+ >
+ <%= render_slot(cancel) %>
+
+
+ <.button
+ :for={confirm <- @confirm}
+ id={"#{@id}-confirm"}
+ phx-click={@on_confirm}
+ phx-disable-with
+ class="py-2 px-3"
+ variant={:primary}
+ >
+ <%= render_slot(confirm) %>
+
+
+
+
+
+
+
+
+ """
+ end
+
## JS Commands
def show_modal(js \\ %JS{}, id) when is_binary(id) do
diff --git a/lib/oli_web/icons.ex b/lib/oli_web/icons.ex
index 8840fecf4ea..bf014d1fa2d 100644
--- a/lib/oli_web/icons.ex
+++ b/lib/oli_web/icons.ex
@@ -916,5 +916,106 @@ defmodule OliWeb.Icons do
"""
end
+ attr :proficiency, :string
+
+ def proficiency(%{proficiency: "Not enough data"} = assigns) do
+ ~H"""
+
+
+
+ """
+ end
+
+ def proficiency(%{proficiency: "Low"} = assigns) do
+ ~H"""
+
+
+
+
+ """
+ end
+
+ def proficiency(%{proficiency: "Medium"} = assigns) do
+ ~H"""
+
+
+
+
+ """
+ end
+
+ def proficiency(%{proficiency: "High"} = assigns) do
+ ~H"""
+
+
+
+ """
+ end
+
########## Studend Delivery Icons (end) ##########
end
diff --git a/lib/oli_web/live/delivery/student/lesson_live.ex b/lib/oli_web/live/delivery/student/lesson_live.ex
index 26aedd9ccab..077e83b4e4d 100644
--- a/lib/oli_web/live/delivery/student/lesson_live.ex
+++ b/lib/oli_web/live/delivery/student/lesson_live.ex
@@ -9,8 +9,10 @@ defmodule OliWeb.Delivery.Student.LessonLive do
alias Oli.Delivery.Attempts.Core.ResourceAttempt
alias Oli.Delivery.Attempts.PageLifecycle
alias Oli.Delivery.Attempts.PageLifecycle.FinalizationSummary
+ alias Oli.Delivery.Metrics
alias Oli.Delivery.Page.PageContext
alias Oli.Delivery.{Sections, Settings}
+ alias Oli.Resources
alias Oli.Resources.Collaboration
alias Oli.Resources.Collaboration.CollabSpaceConfig
alias OliWeb.Common.FormatDateTime
@@ -27,7 +29,6 @@ defmodule OliWeb.Delivery.Student.LessonLive do
def mount(_params, _session, %{assigns: %{view: :practice_page}} = socket) do
%{current_user: current_user, section: section} = socket.assigns
-
is_instructor = Sections.has_instructor_role?(current_user, section.slug)
# when updating to Liveview 0.20 we should replace this with assign_async/3
@@ -49,7 +50,8 @@ defmodule OliWeb.Delivery.Student.LessonLive do
socket
|> assign_html_and_scripts()
|> annotations_assigns(socket.assigns.page_context, is_instructor)
- |> assign(is_instructor: is_instructor)}
+ |> assign(is_instructor: is_instructor)
+ |> assign_objectives()}
end
def mount(
@@ -62,7 +64,8 @@ defmodule OliWeb.Delivery.Student.LessonLive do
{:ok,
socket
|> assign_html_and_scripts()
- |> assign(begin_attempt?: false)}
+ |> assign(begin_attempt?: false)
+ |> assign_objectives()}
end
def mount(
@@ -93,7 +96,8 @@ defmodule OliWeb.Delivery.Student.LessonLive do
{:ok,
socket
|> assign_scripts()
- |> assign(begin_attempt?: false)}
+ |> assign(begin_attempt?: false)
+ |> assign_objectives()}
end
def handle_event("begin_attempt", %{"password" => password}, socket)
@@ -633,6 +637,7 @@ defmodule OliWeb.Delivery.Student.LessonLive do
page_context={@page_context}
ctx={@ctx}
index={@current_page["index"]}
+ objectives={@objectives}
container_label={Utils.get_container_label(@current_page["id"], @section)}
/>
@@ -695,6 +700,7 @@ defmodule OliWeb.Delivery.Student.LessonLive do
<.page_header
page_context={@page_context}
ctx={@ctx}
+ objectives={@objectives}
index={@current_page["index"]}
container_label={Utils.get_container_label(@current_page["id"], @section)}
/>
@@ -726,6 +732,7 @@ defmodule OliWeb.Delivery.Student.LessonLive do
<.page_header
page_context={@page_context}
ctx={@ctx}
+ objectives={@objectives}
index={@current_page["index"]}
container_label={Utils.get_container_label(@current_page["id"], @section)}
/>
@@ -794,6 +801,7 @@ defmodule OliWeb.Delivery.Student.LessonLive do
<.page_header
page_context={@page_context}
ctx={@ctx}
+ objectives={@objectives}
index={@current_page["index"]}
container_label={Utils.get_container_label(@current_page["id"], @section)}
/>
@@ -847,14 +855,15 @@ defmodule OliWeb.Delivery.Student.LessonLive do
<.password_attempt_modal />
-
+
<.page_header
page_context={@page_context}
ctx={@ctx}
+ objectives={@objectives}
index={@current_page["index"]}
container_label={Utils.get_container_label(@current_page["id"], @section)}
/>
-
+
<.attempts_summary
page_context={@page_context}
@@ -1158,6 +1167,40 @@ defmodule OliWeb.Delivery.Student.LessonLive do
)
end
+ defp assign_objectives(socket) do
+ %{page_context: %{page: page}, current_user: current_user, section: section} =
+ socket.assigns
+
+ objectives =
+ case page.objectives["attached"] do
+ objective_ids when objective_ids in [nil, []] ->
+ []
+
+ objective_resource_ids ->
+ student_proficiency_per_learning_objective =
+ Metrics.proficiency_for_student_per_learning_objective(
+ section,
+ current_user.id
+ )
+
+ Resources.get_revisions_by_resource_id(objective_resource_ids)
+ |> Enum.map(fn rev ->
+ %{
+ resource_id: rev.resource_id,
+ title: rev.title,
+ proficiency:
+ Map.get(
+ student_proficiency_per_learning_objective,
+ rev.resource_id,
+ "Not enough data"
+ )
+ }
+ end)
+ end
+
+ assign(socket, objectives: objectives)
+ end
+
defp get_max_attempts(%{effective_settings: %{max_attempts: 0}} = _page_context),
do: "unlimited"
diff --git a/lib/oli_web/live/delivery/student/review_live.ex b/lib/oli_web/live/delivery/student/review_live.ex
index 10ed81cf4e5..431170b5d91 100644
--- a/lib/oli_web/live/delivery/student/review_live.ex
+++ b/lib/oli_web/live/delivery/student/review_live.ex
@@ -9,7 +9,9 @@ defmodule OliWeb.Delivery.Student.ReviewLive do
alias Oli.Delivery.Attempts.PageLifecycle
alias Oli.Delivery.Page.PageContext
+ alias Oli.Delivery.Metrics
alias Oli.Publishing.DeliveryResolver, as: Resolver
+ alias Oli.Resources
alias OliWeb.Delivery.Student.Utils
def mount(
@@ -31,6 +33,7 @@ defmodule OliWeb.Delivery.Student.ReviewLive do
|> assign(page_context: page_context)
|> assign(page_revision: page_revision)
|> assign_html_and_scripts()
+ |> assign_objectives()
else
socket
|> put_flash(:error, "You are not allowed to review this attempt.")
@@ -40,6 +43,40 @@ defmodule OliWeb.Delivery.Student.ReviewLive do
{:ok, socket}
end
+ defp assign_objectives(socket) do
+ %{page_context: %{page: page}, current_user: current_user, section: section} =
+ socket.assigns
+
+ objectives =
+ case page.objectives["attached"] do
+ objective_ids when objective_ids in [nil, []] ->
+ []
+
+ objective_resource_ids ->
+ student_proficiency_per_learning_objective =
+ Metrics.proficiency_for_student_per_learning_objective(
+ section,
+ current_user.id
+ )
+
+ Resources.get_revisions_by_resource_id(objective_resource_ids)
+ |> Enum.map(fn rev ->
+ %{
+ resource_id: rev.resource_id,
+ title: rev.title,
+ proficiency:
+ Map.get(
+ student_proficiency_per_learning_objective,
+ rev.resource_id,
+ "Not enough data"
+ )
+ }
+ end)
+ end
+
+ assign(socket, objectives: objectives)
+ end
+
defp review_allowed?(page_context),
do: page_context.effective_settings.review_submission == :allow
@@ -59,6 +96,7 @@ defmodule OliWeb.Delivery.Student.ReviewLive do
page_context={@page_context}
ctx={@ctx}
index={@current_page["index"]}
+ objectives={@objectives}
container_label={Utils.get_container_label(@current_page["id"], @section)}
/>
diff --git a/lib/oli_web/live/delivery/student/utils.ex b/lib/oli_web/live/delivery/student/utils.ex
index 3e51dca9677..30a737cdbeb 100644
--- a/lib/oli_web/live/delivery/student/utils.ex
+++ b/lib/oli_web/live/delivery/student/utils.ex
@@ -11,18 +11,21 @@ defmodule OliWeb.Delivery.Student.Utils do
alias Oli.Delivery.Sections
alias Oli.Rendering.Page
alias OliWeb.Common.FormatDateTime
+ alias OliWeb.Components.Modal
alias OliWeb.Icons
alias Oli.Publishing.DeliveryResolver, as: Resolver
+ alias Phoenix.LiveView.JS
attr :page_context, Oli.Delivery.Page.PageContext
attr :ctx, OliWeb.Common.SessionContext
+ attr :objectives, :list
attr :index, :string
attr :container_label, :string
attr :has_assignments?, :boolean
def page_header(assigns) do
~H"""
-