Skip to content

Commit

Permalink
[FEATURE] [MER-1911] Allow instructor to change a user's role (Simon-…
Browse files Browse the repository at this point in the history
…Initiative#3502)

* [MER-1911] Adds Actions tab

* [MER-1911] Adds context function

* [MER-1911] Adds tests

* [MER-1911] Changes from review

* [MER-1911] Changes toggle button to dropdown select

* [MER-1911] Preserves the select value if the role change is cancelled

* [MER-1911] Fix tests
  • Loading branch information
simonchoxx committed May 10, 2023
1 parent 75d7165 commit 3b9d58f
Show file tree
Hide file tree
Showing 7 changed files with 419 additions and 1 deletion.
25 changes: 25 additions & 0 deletions lib/oli/accounts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ defmodule Oli.Accounts do
alias Oli.Repo.{Paging, Sorting}
alias Oli.AccountLookupCache
alias PowEmailConfirmation.Ecto.Context, as: EmailConfirmationContext
alias Oli.Delivery.Sections.Enrollment
alias Lti_1p3.DataProviders.EctoProvider

def browse_users(
%Paging{limit: limit, offset: offset},
Expand Down Expand Up @@ -318,6 +320,29 @@ defmodule Oli.Accounts do
end
end

@doc """
Updates the context role for a specific enrollment.
"""

def update_user_context_role(enrollment, role) do
context_role = EctoProvider.Marshaler.to([role])

res =
enrollment
|> Repo.preload([:context_roles])
|> Enrollment.changeset(%{})
|> Ecto.Changeset.put_assoc(:context_roles, context_role)
|> Repo.update()

case res do
{:ok, %Enrollment{}} ->
res

error ->
error
end
end

@doc """
Links a User to Author account
Expand Down
12 changes: 12 additions & 0 deletions lib/oli/delivery/sections.ex
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,18 @@ defmodule Oli.Delivery.Sections do
)
end

@doc """
Get the user's role in a given section.
"""

def get_user_role_from_enrollment(enrollment) do
enrollment
|> Repo.preload(:context_roles)
|> Map.get(:context_roles)
|> List.first()
|> Map.get(:id)
end

@doc """
Determines if a user is a platform (institution) instructor.
"""
Expand Down
119 changes: 119 additions & 0 deletions lib/oli_web/components/delivery/actions/actions.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
defmodule OliWeb.Components.Delivery.Actions do
use Surface.LiveComponent
use OliWeb.Common.Modal

alias Lti_1p3.Tool.ContextRoles
alias Oli.Accounts
alias OliWeb.Common.Confirm
alias Phoenix.LiveView.JS

prop(enrollment_info, :map, required: true)
prop(section_slug, :string, required: true)
prop(user, :map, required: true)

data(enrollment, :map, default: %{})
data(user_role_data, :list, default: [])
data(user_role_id, :integer, default: nil)

@user_role_data [
%{id: 3, name: :instructor, title: "Instructor"},
%{id: 4, name: :student, title: "Student"}
]

def update(
%{user: user, section_slug: section_slug, enrollment_info: enrollment_info} = _assigns,
socket
) do
{:ok,
assign(socket,
enrollment: enrollment_info.enrollment,
section_slug: section_slug,
user: user,
user_role_id: enrollment_info.user_role_id,
user_role_data: @user_role_data
)}
end

def render(assigns) do
~F"""
<div class="mx-10 mb-10 bg-white shadow-sm">
<div class="flex flex-col sm:flex-row sm:items-end px-6 py-4 border instructor_dashboard_table">
<h4 class="pl-9 !py-2 torus-h4 mr-auto dark:!text-black">Actions</h4>
</div>
<div class="flex justify-between items-center px-14 py-8">
<div class="flex flex-col">
<span class="dark:text-black">Change enrolled user role</span>
<span class="text-xs text-gray-400 dark:text-gray-950">Select the role to change for the user in this section.</span>
</div>
<form phx-change="display_confirm_modal" phx-target={@myself}>
<select class="torus-select pr-32" name="filter_by_role_id">
{#for elem <- @user_role_data}
<option selected={elem.id == @user_role_id} value={elem.id}>{elem.title}</option>
{/for}
</select>
</form>
</div>
</div>
"""
end

def handle_event(
"change_user_role",
%{"filter_by_role_id" => filter_by_role_id},
socket
) do
context_role =
case String.to_integer(filter_by_role_id) do
3 -> ContextRoles.get_role(:context_instructor)
4 -> ContextRoles.get_role(:context_learner)
end

Accounts.update_user_context_role(
socket.assigns.enrollment,
context_role
)

{:noreply,
socket
|> assign(user_role_id: String.to_integer(filter_by_role_id))
|> hide_modal(modal_assigns: nil)}
end

def handle_event("display_confirm_modal", %{"filter_by_role_id" => filter_by_role_id}, socket) do
modal_assigns = %{
title: "Change role",
id: "change_role_modal",
ok:
JS.push("change_user_role",
target: socket.assigns.myself,
value: %{"filter_by_role_id" => filter_by_role_id}
),
cancel:
JS.push("cancel_confirm_modal",
target: socket.assigns.myself,
value: %{"previous_role_id" => socket.assigns.user_role_id}
)
}

%{given_name: given_name, family_name: family_name} = socket.assigns.user

modal = fn assigns ->
~F"""
<Confirm {...@modal_assigns}>
Are you sure you want to change user role to {given_name} {family_name}?
</Confirm>
"""
end

send(self(), {:show_modal, modal, modal_assigns})

{:noreply, assign(socket, user_role_id: filter_by_role_id)}
end

def handle_event("cancel_confirm_modal", %{"previous_role_id" => previous_role_id}, socket) do
send(self(), {:hide_modal})

{:noreply, assign(socket, user_role_id: previous_role_id)}
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ defmodule OliWeb.Delivery.StudentDashboard.Components.Helpers do
{"Learning Objectives", path_for(:learning_objectives, @section_slug, @student_id, @preview_mode), nil, is_active_tab?(:learning_objectives, @active_tab)},
{"Quiz Scores", path_for(:quizz_scores, @section_slug, @student_id, @preview_mode), nil, is_active_tab?(:quizz_scores, @active_tab)},
{"Progress", path_for(:progress, @section_slug, @student_id, @preview_mode), nil, is_active_tab?(:progress, @active_tab)},
{"Actions", path_for(:actions, @section_slug, @student_id, @preview_mode), nil, is_active_tab?(:actions, @active_tab)},
] do %>
<li class="nav-item" role="presentation">
<.link patch={href}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
defmodule OliWeb.Delivery.StudentDashboard.StudentDashboardLive do
use OliWeb, :live_view
use OliWeb.Common.Modal

import OliWeb.Common.Utils

alias OliWeb.Delivery.StudentDashboard.Components.Helpers
alias alias Oli.Delivery.Sections
alias Oli.Delivery.Sections
alias Oli.Delivery.Metrics
alias Oli.Grading.GradebookRow

Expand Down Expand Up @@ -87,6 +88,23 @@ defmodule OliWeb.Delivery.StudentDashboard.StudentDashboardLive do
{:noreply, socket}
end

@impl Phoenix.LiveView
def handle_params(%{"active_tab" => "actions"} = params, _, socket) do
enrollment = Sections.get_enrollment(socket.assigns.section.slug, socket.assigns.student.id)

socket =
socket
|> assign(params: params, active_tab: String.to_existing_atom(params["active_tab"]))
|> assign_new(:enrollment_info, fn ->
%{
enrollment: enrollment,
user_role_id: Sections.get_user_role_from_enrollment(enrollment)
}
end)

{:noreply, socket}
end

@impl Phoenix.LiveView
def handle_params(params, _, socket) do
{:noreply,
Expand All @@ -99,6 +117,7 @@ defmodule OliWeb.Delivery.StudentDashboard.StudentDashboardLive do
@impl Phoenix.LiveView
def render(assigns) do
~H"""
<%= render_modal(assigns) %>
<Helpers.section_details_header section_title={@section.title} student_name={@student.name}/>
<Helpers.student_details survey_responses={@survey_responses || []} student={@student} />
<Helpers.tabs active_tab={@active_tab} section_slug={@section.slug} student_id={@student.id} preview_mode={@preview_mode} />
Expand Down Expand Up @@ -160,6 +179,18 @@ defmodule OliWeb.Delivery.StudentDashboard.StudentDashboardLive do
"""
end

defp render_tab(%{active_tab: :actions} = assigns) do
~H"""
<.live_component
id="actions_table"
module={OliWeb.Components.Delivery.Actions}
user={@student}
section_slug={@section.slug}
enrollment_info={@enrollment_info}
/>
"""
end

@impl Phoenix.LiveView
def handle_event("breadcrumb-navigate", _unsigned_params, socket) do
if socket.assigns.preview_mode do
Expand Down Expand Up @@ -187,6 +218,21 @@ defmodule OliWeb.Delivery.StudentDashboard.StudentDashboardLive do
end
end

@impl Phoenix.LiveView
def handle_info({:hide_modal}, socket) do
{:noreply, hide_modal(socket)}
end

@impl Phoenix.LiveView
def handle_info({:show_modal, modal, modal_assigns}, socket) do
{:noreply,
show_modal(
socket,
modal,
modal_assigns: modal_assigns
)}
end

defp get_containers(section, student_id) do
{total_count, containers} = Sections.get_units_and_modules_containers(section.slug)

Expand Down
28 changes: 28 additions & 0 deletions test/oli/accounts_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ defmodule Oli.AccountsTest do
alias Oli.Accounts.{Author, AuthorPreferences, User, UserPreferences}
alias Oli.Groups
alias Oli.Groups.CommunityAccount
alias Oli.Delivery.Sections
alias Lti_1p3.Tool.ContextRoles

describe "authors" do
test "system role defaults to author", %{} do
Expand Down Expand Up @@ -351,6 +353,32 @@ defmodule Oli.AccountsTest do
assert {:ok, user} = Accounts.set_user_preference(user.id, :timezone, "America/Los_Angeles")
assert user.preferences.timezone == "America/Los_Angeles"
end

test "update_user_context_role/2 updates the context role for a specific enrollment" do
user = insert(:user)
section = insert(:section)

{:ok, enrollment} =
Sections.enroll(user.id, section.id, [
Lti_1p3.Tool.ContextRoles.get_role(:context_learner)
])

user_role_id = Sections.get_user_role_from_enrollment(enrollment)

assert user_role_id == 4

Accounts.update_user_context_role(
enrollment,
ContextRoles.get_role(:context_instructor)
)

enrollment = Sections.get_enrollment(section.slug, user.id)

user_role_id_changed = Sections.get_user_role_from_enrollment(enrollment)

refute user_role_id_changed == 4
assert user_role_id_changed == 3
end
end

describe "communities accounts" do
Expand Down
Loading

0 comments on commit 3b9d58f

Please sign in to comment.