Skip to content

Commit

Permalink
Add Task Assignment Log (#327)
Browse files Browse the repository at this point in the history
* Add TaskAssignmentLog

* Add assign_task and unassign_task

* Defensive coding

* Log task assignment in rider signuop

* Log events when dispatcher assigns

* Move taskassignmentlog to its own context
  • Loading branch information
mveytsman authored Apr 22, 2024
1 parent 3da2b15 commit ffc1cee
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 9 deletions.
46 changes: 45 additions & 1 deletion lib/bike_brigade/delivery.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ defmodule BikeBrigade.Delivery do
import Geo.PostGIS, only: [st_distance: 2]
alias BikeBrigade.Riders.Rider

alias BikeBrigade.History
alias BikeBrigade.Messaging
alias BikeBrigade.Delivery.{Task, CampaignRider}
alias BikeBrigade.Delivery.{Task, CampaignRider, TaskAssignmentLog}

import BikeBrigade.Utils, only: [task_count: 1, humanized_task_count: 1]

Expand Down Expand Up @@ -113,6 +114,48 @@ defmodule BikeBrigade.Delivery do
|> broadcast(:task_updated)
end

@doc """
Assign a task to a given rider (tracking the user that made the assignment)
"""

def assign_task(%Task{} = task, rider_id, user_id, opts \\ []) when is_list(opts) do
Repo.transaction(fn ->
{:ok, _log} =
History.create_task_assignment_log(%{
task_id: task.id,
rider_id: rider_id,
user_id: user_id,
action: :assigned
})

{:ok, task} =
update_task(task, %{assigned_rider_id: rider_id}, opts)

task
end)
end

@doc """
Unassign a task (tracking the user that made the unassignment)
"""

def unassign_task(%Task{assigned_rider_id: assigned_rider_id} = task, user_id, opts \\ [])
when not is_nil(assigned_rider_id) and is_list(opts) do
Repo.transaction(fn ->
{:ok, _log} =
History.create_task_assignment_log(%{
task_id: task.id,
rider_id: assigned_rider_id,
user_id: user_id,
action: :unassigned
})

{:ok, task} = update_task(task, %{assigned_rider_id: nil}, opts)

task
end)
end

@doc """
Deletes a task.
Expand Down Expand Up @@ -1012,4 +1055,5 @@ defmodule BikeBrigade.Delivery do
def change_opportunity(%Opportunity{} = opportunity, attrs \\ %{}) do
Opportunity.changeset(opportunity, attrs)
end

end
21 changes: 21 additions & 0 deletions lib/bike_brigade/history.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
defmodule BikeBrigade.History do
import Ecto.Query, warn: false
alias BikeBrigade.Repo

alias BikeBrigade.History.TaskAssignmentLog

@doc """
List all task assignment logs
Currently this table is used for analytics and not exposed in the app.
"""
def list_task_assignment_logs() do
Repo.all(TaskAssignmentLog)
end

def create_task_assignment_log(attrs \\ %{}) do
%TaskAssignmentLog{timestamp: DateTime.utc_now()}
|> TaskAssignmentLog.changeset(attrs)
|> Repo.insert()
end
end
33 changes: 33 additions & 0 deletions lib/bike_brigade/history/task_assignment_log.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
defmodule BikeBrigade.History.TaskAssignmentLog do
use BikeBrigade.Schema

import Ecto.Changeset

alias BikeBrigade.Delivery.Task
alias BikeBrigade.Riders.Rider
alias BikeBrigade.Accounts.User

schema "task_assignment_logs" do
belongs_to :task, Task
belongs_to :rider, Rider
belongs_to :user, User

field :timestamp, :utc_datetime_usec
field :action, Ecto.Enum, values: [:assigned, :unassigned]

timestamps()
end

def changeset(struct, params \\ %{}) do
struct
|> cast(params, [
:task_id,
:rider_id,
:user_id,
:timestamp,
:action
])
|> validate_required([:task_id, :rider_id, :user_id, :timestamp, :action])
|> validate_inclusion(:action, [:assigned, :unassigned])
end
end
4 changes: 2 additions & 2 deletions lib/bike_brigade_web/live/campaign_live/show.ex
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ defmodule BikeBrigadeWeb.CampaignLive.Show do
def handle_event("assign_task", %{"task_id" => task_id, "rider_id" => rider_id}, socket) do
{:ok, _task} =
get_task(socket, task_id)
|> Delivery.update_task(%{assigned_rider_id: rider_id})
|> Delivery.assign_task(rider_id, socket.assigns.current_user.id)

{:noreply, socket}
end
Expand All @@ -256,7 +256,7 @@ defmodule BikeBrigadeWeb.CampaignLive.Show do
if task.assigned_rider do
{:ok, _task} =
task
|> Delivery.update_task(%{assigned_rider_id: nil})
|> Delivery.unassign_task(socket.assigns.current_user.id)
end

{:noreply, socket}
Expand Down
4 changes: 2 additions & 2 deletions lib/bike_brigade_web/live/campaign_signup_live/show.ex
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ defmodule BikeBrigadeWeb.CampaignSignupLive.Show do
if task.assigned_rider do
{:ok, _task} =
task
|> Delivery.update_task(%{assigned_rider_id: nil})
|> Delivery.unassign_task(socket.assigns.current_user.id)
end

# If rider is no longer assigned to any tasks, remove them from the campaign
Expand Down Expand Up @@ -128,7 +128,7 @@ defmodule BikeBrigadeWeb.CampaignSignupLive.Show do

case Delivery.create_campaign_rider(attrs) do
{:ok, _cr} ->
{:ok, _task} = Delivery.update_task(task, %{assigned_rider_id: rider_id})
{:ok, _task} = Delivery.assign_task(task, rider_id, socket.assigns.current_user.id)
{:noreply, socket |> push_patch(to: ~p"/campaigns/signup/#{campaign}", replace: true)}

{:error, %Ecto.Changeset{} = changeset} ->
Expand Down
15 changes: 15 additions & 0 deletions priv/repo/migrations/20240416185206_create_task_assignment_log.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
defmodule BikeBrigade.Repo.Migrations.CreateTaskAssgignmentLog do
use Ecto.Migration

def change do
create table(:task_assignment_logs) do
add :task_id, references(:tasks)
add :rider_id, references(:riders)
add :user_id, references(:users)
add :timestamp, :utc_datetime_usec
add :action, :string

timestamps()
end
end
end
38 changes: 37 additions & 1 deletion test/bike_brigade/delivery_test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
defmodule BikeBrigade.DeliveryTest do
use BikeBrigade.DataCase

alias BikeBrigade.{LocalizedDateTime, Delivery, Delivery.Task}

alias BikeBrigade.{LocalizedDateTime, Delivery, Delivery.Task, History}

use Phoenix.VerifiedRoutes, endpoint: BikeBrigadeWeb.Endpoint, router: BikeBrigadeWeb.Router

Expand Down Expand Up @@ -64,6 +65,41 @@ defmodule BikeBrigade.DeliveryTest do
end
end

test "assign_task/3" do
campaign = fixture(:campaign)
rider = fixture(:rider)
user = fixture(:user)
task = fixture(:task, %{campaign: campaign})

assert {:ok, task} = Delivery.assign_task(task, rider.id, user.id)

assert task.assigned_rider_id == rider.id

assert [log] = History.list_task_assignment_logs()
assert log.task_id == task.id
assert log.rider_id == rider.id
assert log.user_id == user.id
assert log.action == :assigned
end

test "unassign_task/3" do
campaign = fixture(:campaign)
rider = fixture(:rider)
user = fixture(:user)
task = fixture(:task, %{campaign: campaign, assigned_rider_id: rider.id})

assert {:ok, task} = Delivery.unassign_task(task, user.id)

assert task.assigned_rider_id == nil

assert [log] = History.list_task_assignment_logs()
assert log.task_id == task.id
assert log.rider_id == rider.id
assert log.user_id == user.id
assert log.action == :unassigned
end


def item_name(%Task{task_items: [%{item: %{name: item_name}}]}), do: item_name

defp to_uri(location) do
Expand Down
56 changes: 54 additions & 2 deletions test/bike_brigade_web/live/campaign_live_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ defmodule BikeBrigadeWeb.CampaignLiveTest do

import Phoenix.LiveViewTest
alias BikeBrigadeWeb.CampaignHelpers
alias BikeBrigade.LocalizedDateTime

alias BikeBrigade.{Delivery, LocalizedDateTime, History}

describe "Index" do
setup [:create_campaign, :login]
Expand Down Expand Up @@ -171,7 +172,10 @@ defmodule BikeBrigadeWeb.CampaignLiveTest do
assert has_element?(view, ~s|[data-test-rider-window=1-11]|)
end

test "'Rider Messaging' button is not visible without riders.", %{conn: conn, campaign: campaign} do
test "'Rider Messaging' button is not visible without riders.", %{
conn: conn,
campaign: campaign
} do
{:ok, view, _html} = live(conn, ~p"/campaigns/#{campaign}")
refute view |> element("a", "Rider Messaging") |> has_element?()
end
Expand All @@ -187,6 +191,54 @@ defmodule BikeBrigadeWeb.CampaignLiveTest do
{:ok, view, _html} = live(conn, ~p"/campaigns/#{campaign}")
assert view |> element("a", "Rider Messaging") |> has_element?()
end

test "Can assign a rider to a task", ctx do
rider = hd(ctx.riders)
task = fixture(:task, %{campaign: ctx.campaign})
{:ok, view, _html} = live(ctx.conn, ~p"/campaigns/#{ctx.campaign}")

html = view |> element("a", task.dropoff_name) |> render_click()
assert html =~ "Unassigned"

html = view |> element("a", rider.name) |> render_click()
assert html =~ "No tasks"

# assign the task
view |> element("a", "Assign to #{rider.name}") |> render_click()

task = Delivery.get_task(task.id)
assert task.assigned_rider_id == rider.id

# Make sure we have a log
assert [log] = History.list_task_assignment_logs()
assert log.action == :assigned
assert log.task_id == task.id
assert log.rider_id == rider.id
assert log.user_id == ctx.user.id
end

test "Can unassign a rider from a task", ctx do
rider = hd(ctx.riders)
task = fixture(:task, %{campaign: ctx.campaign, assigned_rider_id: rider.id})
{:ok, view, _html} = live(ctx.conn, ~p"/campaigns/#{ctx.campaign}")

view |> element("a", task.dropoff_name) |> render_click()

assert view |> element("a", task.dropoff_name) |> render =~ "Assigned"

# unassign the task
view |> element("a", "Unassign") |> render_click()

task = Delivery.get_task(task.id)
assert task.assigned_rider_id == nil

# Make sure we have a log
assert [log] = History.list_task_assignment_logs()
assert log.action == :unassigned
assert log.task_id == task.id
assert log.rider_id == rider.id
assert log.user_id == ctx.user.id
end
end

# Still a work in progress
Expand Down
16 changes: 15 additions & 1 deletion test/bike_brigade_web/live/campaign_signup_live_test.exs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
defmodule BikeBrigadeWeb.CampaignSignupLiveTest do
use BikeBrigadeWeb.ConnCase, async: false
alias BikeBrigade.LocalizedDateTime
alias BikeBrigade.{LocalizedDateTime, History}

import Phoenix.LiveViewTest

Expand Down Expand Up @@ -173,6 +173,13 @@ defmodule BikeBrigadeWeb.CampaignSignupLiveTest do
refute html =~ "Unassign me"
html = live |> element("#signup-btn-desktop-sign-up-task-#{ctx.task.id}") |> render_click()
assert html =~ "Unassign me"

# Make sure we have a log
assert [log] = History.list_task_assignment_logs()
assert log.action == :assigned
assert log.task_id == ctx.task.id
assert log.rider_id == ctx.rider.id
assert log.user_id == ctx.user.id
end

test "Rider cannot signup for a task in the past", ctx do
Expand Down Expand Up @@ -224,6 +231,13 @@ defmodule BikeBrigadeWeb.CampaignSignupLiveTest do
assert html =~ "Unassign me"
element(live, "a#signup-btn-desktop-unassign-task-#{task.id}") |> render_click()
refute render(live) =~ "Unassign me"

# Make sure we have a log
assert [log] = History.list_task_assignment_logs()
assert log.action == :unassigned
assert log.task_id == task.id
assert log.rider_id == ctx.rider.id
assert log.user_id == ctx.user.id
end
end

Expand Down

0 comments on commit ffc1cee

Please sign in to comment.