Skip to content

Commit

Permalink
feat: Add alert info to trip planner output (#2269)
Browse files Browse the repository at this point in the history
Co-authored-by: Cristen Jones <[email protected]>
  • Loading branch information
joshlarson and thecristen authored Dec 18, 2024
1 parent 4aabef8 commit c8a86bf
Show file tree
Hide file tree
Showing 10 changed files with 640 additions and 200 deletions.
585 changes: 422 additions & 163 deletions assets/package-lock.json

Large diffs are not rendered by default.

54 changes: 53 additions & 1 deletion lib/dotcom/trip_plan/alerts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ defmodule Dotcom.TripPlan.Alerts do
alias Alerts.InformedEntity, as: IE
alias Dotcom.TripPlan.{Itinerary, Leg, TransitDetail}

@spec from_itinerary(Itinerary.t()) :: [Alerts.Alert.t()]
def from_itinerary(itinerary) do
itinerary.start
|> Alerts.Repo.all()
|> Alerts.Match.match(
entities(itinerary),
itinerary.start
)
end

@doc "Filters a list of Alerts to those relevant to the Itinerary"
@spec filter_for_itinerary([Alert.t()], Itinerary.t()) :: [Alert.t()]
def filter_for_itinerary(alerts, itinerary) do
Expand All @@ -22,6 +32,16 @@ defmodule Dotcom.TripPlan.Alerts do
)
end

@doc "Filters a list of Alerts to those relevant to the Leg"
@spec filter_for_leg([Alert.t()], Leg.t()) :: [Alert.t()]
def filter_for_leg(alerts, leg) do
Alerts.Match.match(
alerts,
leg_entities(leg),
leg.start
)
end

defp intermediate_entities(itinerary) do
itinerary
|> Itinerary.intermediate_stop_ids()
Expand All @@ -36,8 +56,14 @@ defmodule Dotcom.TripPlan.Alerts do
end

defp leg_entities(%Leg{mode: mode} = leg) do
%{from: from, to: to} = Leg.stop_ids(leg)

mode_entities_with_stop_ids(mode, from ++ to)
end

defp mode_entities_with_stop_ids(mode, stop_ids) do
for entity <- mode_entities(mode),
stop_id <- Leg.stop_ids(leg) do
stop_id <- stop_ids do
%{entity | stop: stop_id}
end
end
Expand All @@ -64,4 +90,30 @@ defmodule Dotcom.TripPlan.Alerts do
defp mode_entities(_) do
[]
end

def by_mode_and_stops(alerts, leg) do
{route_alerts, stop_alerts} =
alerts
|> Enum.split_with(fn alert ->
alert.informed_entity.entities
|> Enum.all?(fn
%{stop: nil} -> true
_ -> false
end)
end)

%{from: from_stop_ids, to: to_stop_ids} = Leg.stop_ids(leg)

entities_from = mode_entities_with_stop_ids(leg.mode, from_stop_ids)
from = Alerts.Match.match(stop_alerts, entities_from)

entities_to = mode_entities_with_stop_ids(leg.mode, to_stop_ids)
to = Alerts.Match.match(stop_alerts, entities_to)

%{
route: route_alerts,
from: from,
to: to
}
end
end
16 changes: 12 additions & 4 deletions lib/dotcom/trip_plan/leg.ex
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,18 @@ defmodule Dotcom.TripPlan.Leg do
@doc "Returns the stop IDs for the leg"
@spec stop_ids(t) :: [Stops.Stop.id_t()]
def stop_ids(%__MODULE__{from: from, to: to}) do
for %NamedPosition{stop: stop} <- [from, to],
stop do
stop.id
end
%{
from:
for %NamedPosition{stop: stop} <- [from],
stop do
stop.id
end,
to:
for %NamedPosition{stop: stop} <- [to],
stop do
stop.id
end
}
end

@spec stop_is_silver_line_airport?([t], atom) :: boolean()
Expand Down
40 changes: 40 additions & 0 deletions lib/dotcom_web/components/trip_planner/alert_group.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
defmodule DotcomWeb.Components.TripPlanner.AlertGroup do
@moduledoc """
A component to display an alert for a route or location
"""

use DotcomWeb, :component

attr :alerts, :list, required: true
attr :class, :string, default: ""

def alert_group(assigns) do
~H"""
<%= if @alerts do %>
<div :for={alert <- @alerts} class={@class}>
<.alert alert={alert} />
</div>
<% end %>
"""
end

defp alert(assigns) do
~H"""
<details class="group/alert">
<summary class="flex items-center gap-1.5 mb-1">
<.icon name="triangle-exclamation" class="w-3 h-3" />
<span>
<span class="text-sm">{Phoenix.Naming.humanize(@alert.effect)}</span>
<span class="group-open/alert:hidden cursor-pointer btn-link text-xs">Show Details</span>
<span class="hidden group-open/alert:inline cursor-pointer btn-link text-xs">
Hide Details
</span>
</span>
</summary>
<div class="bg-white p-2 text-sm">
{@alert.header}
</div>
</details>
"""
end
end
13 changes: 6 additions & 7 deletions lib/dotcom_web/components/trip_planner/itinerary_detail.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ defmodule DotcomWeb.Components.TripPlanner.ItineraryDetail do
import DotcomWeb.Components.TripPlanner.WalkingLeg, only: [walking_leg: 1]

alias Dotcom.TripPlan.LegToSegmentHelper
alias Dotcom.TripPlan.Alerts

def itinerary_detail(assigns) do
itinerary_group =
Expand Down Expand Up @@ -74,16 +75,14 @@ defmodule DotcomWeb.Components.TripPlanner.ItineraryDetail do

defp specific_itinerary_detail(assigns) do
assigns =
assign(
assigns,
:segments,
LegToSegmentHelper.legs_to_segments(assigns.itinerary.legs)
)
assigns
|> assign_new(:alerts, fn -> Alerts.from_itinerary(assigns.itinerary) end)
|> assign(:segments, LegToSegmentHelper.legs_to_segments(assigns.itinerary.legs))

~H"""
<div class="mt-4">
<div :for={segment <- @segments}>
<.segment segment={segment} />
<.segment segment={segment} alerts={@alerts} />
</div>
</div>
"""
Expand Down Expand Up @@ -112,7 +111,7 @@ defmodule DotcomWeb.Components.TripPlanner.ItineraryDetail do
assigns = assign(assigns, :leg, leg)

~H"""
<.transit_leg leg={@leg} />
<.transit_leg leg={@leg} alerts={Alerts.filter_for_leg(@alerts, @leg)} />
"""
end
end
34 changes: 18 additions & 16 deletions lib/dotcom_web/components/trip_planner/place.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ defmodule DotcomWeb.Components.TripPlanner.Place do

use DotcomWeb, :component

import DotcomWeb.Components.TripPlanner.AlertGroup, only: [alert_group: 1]

alias Routes.Route
alias Stops.Stop

attr :place, :map, required: true
attr :time, :any, required: true
attr :route, :map, default: nil
attr :alerts, :list, default: []

def place(assigns) do
stop_url = stop_url(assigns.route, assigns.place.stop)
Expand All @@ -22,24 +25,23 @@ defmodule DotcomWeb.Components.TripPlanner.Place do
})

~H"""
<.dynamic_tag
tag_name={@tag_name}
href={@stop_url}
class="bg-gray-bordered-background px-3 py-2 rounded-lg grid grid-cols-[1.5rem_auto_1fr] items-center gap-2 w-full hover:no-underline text-black"
>
<div class="bg-gray-bordered-background px-3 py-2 rounded-lg grid grid-cols-[1.5rem_auto_1fr] items-center gap-x-2 w-full">
<.location_icon route={@route} class="h-6 w-6" />
<strong class="flex items-center gap-2">
{@place.name}
<.icon
:if={!is_nil(@place.stop) and Stop.accessible?(@place.stop)}
type="icon-svg"
name="icon-accessible-default"
class="h-5 w-5 ml-0.5 shrink-0"
aria-hidden="true"
/>
</strong>
<.dynamic_tag class="hover:no-underline text-black" tag_name={@tag_name} href={@stop_url}>
<strong class="flex items-center gap-2">
{@place.name}
<.icon
:if={!is_nil(@place.stop) and Stop.accessible?(@place.stop)}
type="icon-svg"
name="icon-accessible-default"
class="h-5 w-5 ml-0.5 shrink-0"
aria-hidden="true"
/>
</strong>
</.dynamic_tag>
<time class="text-right no-wrap">{format_time(@time)}</time>
</.dynamic_tag>
<.alert_group class="col-start-2 col-end-4 mr-4" alerts={@alerts} />
</div>
"""
end

Expand Down
22 changes: 15 additions & 7 deletions lib/dotcom_web/components/trip_planner/transit_leg.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,47 @@ defmodule DotcomWeb.Components.TripPlanner.TransitLeg do

use Phoenix.Component

import DotcomWeb.Components.TripPlanner.AlertGroup, only: [alert_group: 1]
import DotcomWeb.Components.RouteSymbols, only: [route_symbol: 1]
import DotcomWeb.Components.TripPlanner.Place
import MbtaMetro.Components.Icon, only: [icon: 1]
import Routes.Route, only: [is_external?: 1, is_shuttle?: 1]

alias Dotcom.TripPlan.TransitDetail
alias Dotcom.TripPlan.{Alerts, TransitDetail}
alias Routes.Route

@doc """
Renders a transit leg.
Must be given a `leg`
Must be given a `leg` and a list of `alerts`
"""

attr :alerts, :list, default: []
attr :leg, :any, required: true

def transit_leg(assigns) do
assigns = assign(assigns, :alerts, Alerts.by_mode_and_stops(assigns.alerts, assigns.leg))

~H"""
<div class="bg-gray-bordered-background">
<.place
place={@leg.from}
time={@leg.start}
route={if(match?(%TransitDetail{}, @leg.mode), do: @leg.mode.route)}
alerts={@alerts.from}
/>
<div class={"bg-gray-bordered-background ml-5 border-l-8 #{leg_line_class(@leg.mode.route)}"}>
<%= if Enum.count(@leg.mode.intermediate_stops) < 2 do %>
<.leg_summary leg={@leg} />
<.leg_summary leg={@leg} alerts={@alerts.route} />
<.leg_details leg={@leg} />
<% else %>
<details class="group">
<details class="group/stops">
<summary class="flex cursor-pointer list-none gap-2 relative">
<.leg_summary leg={@leg} />
<.leg_summary leg={@leg} alerts={@alerts.route} />
<.icon
name="chevron-up"
class="group-open:rotate-180 w-4 h-4 absolute top-3 right-3 fill-brand-primary"
class="group-open/stops:rotate-180 w-4 h-4 absolute top-3 right-3 fill-brand-primary"
/>
</summary>
<.leg_details leg={@leg} />
Expand All @@ -51,6 +57,7 @@ defmodule DotcomWeb.Components.TripPlanner.TransitLeg do
place={@leg.to}
time={@leg.stop}
route={if(match?(%TransitDetail{}, @leg.mode), do: @leg.mode.route)}
alerts={@alerts.to}
/>
</div>
"""
Expand Down Expand Up @@ -81,13 +88,14 @@ defmodule DotcomWeb.Components.TripPlanner.TransitLeg do
|> assign(:headsign, headsign(assigns.leg.mode))

~H"""
<div class="gap-x-1 py-2 grid grid-rows-2 grid-cols-[min-content_max-content] pl-4">
<div class="gap-x-1 py-2 grid grid-rows-2 grid-cols-[min-content_auto] pl-4">
<.route_symbol route={@leg.mode.route} />
<span class="font-semibold">{@headsign}</span>
<div class="text-sm col-start-2 row-start-2">
<.ride_message mode={@leg.mode} />
<span class="font-semibold">{@stops_count} {Inflex.inflect("stop", @stops_count)}</span>
</div>
<.alert_group class="col-start-2 mb-2 mr-4" alerts={@alerts} />
</div>
"""
end
Expand Down
39 changes: 39 additions & 0 deletions test/dotcom/trip_plan/alerts_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,45 @@ defmodule Dotcom.TripPlan.AlertsTest do
end
end

describe "by_mode_and_stops/2" do
test "groups alerts by route, to, and from", %{itinerary: itinerary, route_id: route_id} do
expect(MBTA.Api.Mock, :get_json, fn "/trips/" <> id, [] ->
%JsonApi{
data: [
Api.build(:trip_item, %{id: id})
]
}
end)

[leg] = itinerary.legs
[from_stop_id, to_stop_id] = itinerary |> Itinerary.stop_ids()

route_alert =
Alert.new(
active_period: [valid_active_period(itinerary)],
informed_entity: [%InformedEntity{route: route_id}]
)

from_alert =
Alert.new(
active_period: [valid_active_period(itinerary)],
informed_entity: [%InformedEntity{stop: from_stop_id}]
)

to_alert =
Alert.new(
active_period: [valid_active_period(itinerary)],
informed_entity: [%InformedEntity{stop: to_stop_id}]
)

assert by_mode_and_stops([route_alert, from_alert, to_alert], leg) == %{
from: [from_alert],
to: [to_alert],
route: [route_alert]
}
end
end

defp assert_only_good_alert(good_alert, bad_alert, itinerary) do
assert filter_for_itinerary([good_alert, bad_alert], itinerary) == [good_alert]
end
Expand Down
4 changes: 2 additions & 2 deletions test/dotcom/trip_plan/leg_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ defmodule Dotcom.TripPlan.LegTest do

describe "stop_ids/1" do
test "returns the stop IDs @from and @to", context do
assert [@from.stop.id, @to.stop.id] == stop_ids(context.transit_leg)
assert %{from: [@from.stop.id], to: [@to.stop.id]} == stop_ids(context.transit_leg)
end

test "ignores nil stop IDs" do
Expand All @@ -73,7 +73,7 @@ defmodule Dotcom.TripPlan.LegTest do
stop: @stop
)

assert [@to.stop.id] == stop_ids(personal_leg)
assert %{from: [], to: [@to.stop.id]} == stop_ids(personal_leg)
end
end

Expand Down
Loading

0 comments on commit c8a86bf

Please sign in to comment.