Skip to content

Commit

Permalink
feat: Add module that retrieves system-status relevant alerts
Browse files Browse the repository at this point in the history
  • Loading branch information
joshlarson committed Jan 10, 2025
1 parent 3c32654 commit f15b577
Show file tree
Hide file tree
Showing 6 changed files with 278 additions and 0 deletions.
23 changes: 23 additions & 0 deletions lib/dotcom/system_status.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
defmodule Dotcom.SystemStatus do
alias Dotcom.SystemStatus

def subway_alerts_for_today() do
subway_alerts_for_today(Timex.now())
end

def subway_alerts_for_today(now) do
[
"Red",
"Orange",
"Blue",
"Green-B",
"Green-C",
"Green-D",
"Green-E",
"Mattapan"
]
|> Alerts.Repo.by_route_ids(now)
|> SystemStatus.Alerts.for_today(now)
|> SystemStatus.Alerts.filter_relevant()
end
end
29 changes: 29 additions & 0 deletions lib/dotcom/system_status/alerts.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
defmodule Dotcom.SystemStatus.Alerts do
@relevant_effects [:delay, :shuttle, :suspension, :station_closure]

defp has_started?(active_period_start, now) do
now |> Timex.end_of_day() |> Timex.after?(active_period_start)
end

defp has_not_ended?(nil, _now) do
true
end

defp has_not_ended?(active_period_end, now) do
now |> Timex.before?(active_period_end)
end

def active_today?(alert, now) do
Enum.any?(alert.active_period, fn {active_period_start, active_period_end} ->
has_started?(active_period_start, now) && has_not_ended?(active_period_end, now)
end)
end

def for_today(alerts, now) do
Enum.filter(alerts, &active_today?(&1, now))
end

def filter_relevant(alerts) do
alerts |> Enum.filter(fn %{effect: effect} -> effect in @relevant_effects end)
end
end
5 changes: 5 additions & 0 deletions lib/dotcom_web/live/admin.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ defmodule DotcomWeb.Live.Admin do
url: Helpers.live_path(socket, DotcomWeb.Live.TripPlanner),
title: "Trip Planner Preview",
description: "WIP on the trip planner rewrite."
},
%{
url: Helpers.live_path(socket, DotcomWeb.Live.SystemStatus),
title: "System Status Widget Preview",
description: "WIP on the system status widget."
}
]
)}
Expand Down
35 changes: 35 additions & 0 deletions lib/dotcom_web/live/system_status.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
defmodule DotcomWeb.Live.SystemStatus do
@moduledoc """
A temporary LiveView for showing off the system status widget until we
put it into the homepage (and elsewhere).
"""

alias Dotcom.SystemStatus
use DotcomWeb, :live_view

def render(assigns) do
assigns =
assigns
|> assign(:alerts, SystemStatus.subway_alerts_for_today())

~H"""
<div class="flex flex-col gap-2">
<.alert :for={alert <- @alerts} alert={alert} />
</div>
"""
end

defp alert(assigns) do
~H"""
<details class="border border-gray-lighter p-2">
<summary>
<span class="font-bold">{@alert.severity} {@alert.effect}:</span> {@alert.header}
</summary>
<details>
<summary>Raw alert</summary>
<pre>{inspect(@alert, pretty: true)}</pre>
</details>
</details>
"""
end
end
9 changes: 9 additions & 0 deletions lib/dotcom_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,15 @@ defmodule DotcomWeb.Router do
end
end

scope "/preview", DotcomWeb do
import Phoenix.LiveView.Router
pipe_through([:browser, :browser_live, :basic_auth_readonly])

live_session :system_status, layout: {DotcomWeb.LayoutView, :preview} do
live "/system-status", Live.SystemStatus
end
end

scope "/api", DotcomWeb do
pipe_through([:secure, :browser])

Expand Down
177 changes: 177 additions & 0 deletions test/dotcom/system_status/alerts_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
defmodule Dotcom.SystemStatus.AlertsTest do
use ExUnit.Case, async: true

import Test.Support.Factories.Alerts.Alert

alias Dotcom.SystemStatus.Alerts

defp local_datetime(naive) do
DateTime.from_naive!(naive, "America/New_York")
end

describe "active_today?/2" do
test "returns true if the alert is currently active" do
assert Alerts.active_today?(
build(:alert,
active_period: [
{
local_datetime(~N[2025-01-09 12:00:00]),
local_datetime(~N[2025-01-09 20:00:00])
}
]
),
local_datetime(~N[2025-01-09 13:00:00])
)
end

test "returns false if the alert starts after end-of-service" do
refute Alerts.active_today?(
build(:alert,
active_period: [
{
local_datetime(~N[2025-01-10 12:00:00]),
local_datetime(~N[2025-01-10 20:00:00])
}
]
),
local_datetime(~N[2025-01-09 13:00:00])
)
end

test "returns true if the alert starts later, but before end-of-service" do
assert Alerts.active_today?(
build(:alert,
active_period: [
{
local_datetime(~N[2025-01-09 20:00:00]),
local_datetime(~N[2025-01-10 20:00:00])
}
]
),
local_datetime(~N[2025-01-09 13:00:00])
)
end

test "returns false if the alert has already ended" do
refute Alerts.active_today?(
build(:alert,
active_period: [
{
local_datetime(~N[2025-01-09 10:00:00]),
local_datetime(~N[2025-01-09 12:00:00])
}
]
),
local_datetime(~N[2025-01-09 13:00:00])
)
end

test "returns true if the alert has no end time" do
alert =
build(:alert,
active_period: [
{
local_datetime(~N[2025-01-09 10:00:00]),
nil
}
]
)

assert Alerts.active_today?(
alert,
local_datetime(~N[2025-01-09 13:00:00])
)
end

test "returns false if the alert has no end time but hasn't started yet" do
refute Alerts.active_today?(
build(:alert,
active_period: [
{
local_datetime(~N[2025-01-10 10:00:00]),
nil
}
]
),
local_datetime(~N[2025-01-09 13:00:00])
)
end

test "returns true if a later part of the alert's active period is active" do
assert Alerts.active_today?(
build(:alert,
active_period: [
{
local_datetime(~N[2025-01-08 10:00:00]),
local_datetime(~N[2025-01-08 12:00:00])
},
{
local_datetime(~N[2025-01-09 10:00:00]),
local_datetime(~N[2025-01-09 12:00:00])
}
]
),
local_datetime(~N[2025-01-09 11:00:00])
)
end
end

describe "for_today/2" do
test "includes alerts that are active today" do
alert1 =
build(:alert,
active_period: [
{
local_datetime(~N[2025-01-09 12:00:00]),
local_datetime(~N[2025-01-09 20:00:00])
}
]
)

alert2 =
build(:alert,
active_period: [
{
local_datetime(~N[2025-01-10 12:00:00]),
local_datetime(~N[2025-01-10 20:00:00])
}
]
)

assert Alerts.for_today(
[alert1, alert2],
local_datetime(~N[2025-01-09 13:00:00])
) == [alert1]
end
end

describe "filter_relevant/1" do
test "includes an alert if its effect is :delay" do
alert = build(:alert, effect: :delay)
assert Alerts.filter_relevant([alert]) == [alert]
end

test "includes an alert if its effect is :shuttle" do
alert = build(:alert, effect: :shuttle)
assert Alerts.filter_relevant([alert]) == [alert]
end

test "includes an alert if its effect is :suspension" do
alert = build(:alert, effect: :suspension)
assert Alerts.filter_relevant([alert]) == [alert]
end

test "includes an alert if its effect is :station_closure" do
alert = build(:alert, effect: :station_closure)
assert Alerts.filter_relevant([alert]) == [alert]
end

test "does not include alerts with other effects" do
assert Alerts.filter_relevant([
build(:alert, effect: :policy_change),
build(:alert, effect: :extra_service),
build(:alert, effect: :stop_closure)
]) == []
end
end
end

0 comments on commit f15b577

Please sign in to comment.