From 0c790aa2d543386d8b16554b94f0da0fa8bcb01f Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Tue, 29 Oct 2024 09:18:34 -0400 Subject: [PATCH 01/30] API additions needed for Elevators. --- lib/screens/facilities/facility.ex | 18 ++++++++++++++++++ lib/screens/stops/stop.ex | 6 ++++-- 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 lib/screens/facilities/facility.ex diff --git a/lib/screens/facilities/facility.ex b/lib/screens/facilities/facility.ex new file mode 100644 index 000000000..0a53b7768 --- /dev/null +++ b/lib/screens/facilities/facility.ex @@ -0,0 +1,18 @@ +defmodule Screens.Facilities.Facility do + alias Screens.Stops + + @type id :: String.t() + + @callback fetch_stop_for_facility(id()) :: {:ok, Stops.Stop.t()} | {:error, term()} + def fetch_stop_for_facility(facility_id, get_json_fn \\ &Screens.V3Api.get_json/2) do + case get_json_fn.("facilities/#{facility_id}", %{ + "include" => "stop" + }) do + {:ok, %{"data" => _data, "included" => [stop_map]}} -> + {:ok, Stops.Parser.parse_stop(stop_map)} + + error -> + {:error, error} + end + end +end diff --git a/lib/screens/stops/stop.ex b/lib/screens/stops/stop.ex index c935a130a..463265f76 100644 --- a/lib/screens/stops/stop.ex +++ b/lib/screens/stops/stop.ex @@ -17,7 +17,7 @@ defmodule Screens.Stops.Stop do alias Screens.Stops alias Screens.Util alias Screens.V3Api - alias ScreensConfig.V2.{BusEink, BusShelter, Dup, GlEink, PreFare} + alias ScreensConfig.V2.{BusEink, BusShelter, Dup, Elevator, GlEink, PreFare} defstruct ~w[id name location_type platform_code platform_name]a @@ -493,6 +493,7 @@ defmodule Screens.Stops.Stop do # WTC is a special bus-only case def get_route_type_filter(Dup, "place-wtcst"), do: [:bus] def get_route_type_filter(Dup, _), do: [:light_rail, :subway] + def get_route_type_filter(Elevator, _), do: [:subway] @spec upstream_stop_id_set(String.t(), list(list(id()))) :: MapSet.t(id()) def upstream_stop_id_set(stop_id, stop_sequences) do @@ -530,7 +531,8 @@ defmodule Screens.Stops.Stop do RoutePattern.fetch_tagged_parent_station_sequences_through_stop(stop_id, route_ids) end - defp fetch_tagged_stop_sequences_by_app(PreFare, stop_id, routes_at_stop) do + defp fetch_tagged_stop_sequences_by_app(app, stop_id, routes_at_stop) + when app in [Elevator, PreFare] do route_ids = Route.route_ids(routes_at_stop) # We limit results to canonical route patterns only--no stop sequences for nonstandard patterns. From e0ea48f5673d21ff53ed93b9bf79171ecbbf422e Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Tue, 29 Oct 2024 09:19:13 -0400 Subject: [PATCH 02/30] Added new CG. --- .../v2/candidate_generator/elevator.ex | 16 ++-- .../elevator/elevator_closures.ex | 76 +++++++++++++++++++ 2 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 lib/screens/v2/candidate_generator/elevator/elevator_closures.ex diff --git a/lib/screens/v2/candidate_generator/elevator.ex b/lib/screens/v2/candidate_generator/elevator.ex index eeefbdb50..9716ac24d 100644 --- a/lib/screens/v2/candidate_generator/elevator.ex +++ b/lib/screens/v2/candidate_generator/elevator.ex @@ -2,8 +2,9 @@ defmodule Screens.V2.CandidateGenerator.Elevator do @moduledoc false alias Screens.V2.CandidateGenerator + alias Screens.V2.CandidateGenerator.Elevator.ElevatorClosures alias Screens.V2.Template.Builder - alias Screens.V2.WidgetInstance.{ElevatorClosures, Footer, NormalHeader} + alias Screens.V2.WidgetInstance.{Footer, NormalHeader} alias ScreensConfig.Screen alias ScreensConfig.V2.Elevator @@ -23,16 +24,17 @@ defmodule Screens.V2.CandidateGenerator.Elevator do |> Builder.build_template() end - def candidate_instances(config, now \\ DateTime.utc_now()) do - [header_instance(config, now), elevator_closures_instance(config), footer_instance(config)] + def candidate_instances( + config, + now \\ DateTime.utc_now(), + elevator_closure_instances_fn \\ &ElevatorClosures.elevator_status_instances/2 + ) do + [header_instance(config, now), footer_instance(config)] ++ + elevator_closure_instances_fn.(config, now) end def audio_only_instances(_widgets, _config), do: [] - defp elevator_closures_instance(config) do - %ElevatorClosures{screen: config, alerts: []} - end - defp header_instance(%Screen{app_params: %Elevator{elevator_id: elevator_id}} = config, now) do %NormalHeader{text: "Elevator #{elevator_id}", screen: config, time: now} end diff --git a/lib/screens/v2/candidate_generator/elevator/elevator_closures.ex b/lib/screens/v2/candidate_generator/elevator/elevator_closures.ex new file mode 100644 index 000000000..8f0375206 --- /dev/null +++ b/lib/screens/v2/candidate_generator/elevator/elevator_closures.ex @@ -0,0 +1,76 @@ +defmodule Screens.V2.CandidateGenerator.Elevator.ElevatorClosures do + @moduledoc false + + alias Screens.Alerts.Alert + alias Screens.Facilities.Facility + alias Screens.Routes.Route + alias Screens.Stops.Stop + alias Screens.V2.WidgetInstance.ElevatorClosures + alias ScreensConfig.Screen + alias ScreensConfig.V2.Elevator + + def elevator_status_instances( + %Screen{ + app_params: %Elevator{ + elevator_id: elevator_id + } + } = config, + now \\ DateTime.utc_now(), + fetch_stop_for_facility_fn \\ &Facility.fetch_stop_for_facility/1, + fetch_location_context_fn \\ &Stop.fetch_location_context/3, + fetch_elevator_alerts_with_facilities_fn \\ &Alert.fetch_elevator_alerts_with_facilities/0 + ) do + with {:ok, %Stop{id: stop_id}} <- fetch_stop_for_facility_fn.(elevator_id), + {:ok, location_context} <- fetch_location_context_fn.(Elevator, stop_id, now), + {:ok, parent_station_map} <- Stop.fetch_parent_station_name_map(), + {:ok, alerts} <- fetch_elevator_alerts_with_facilities_fn.() do + elevator_closures = relevant_alerts(alerts) + routes_map = get_routes_map(elevator_closures, stop_id) + + [ + %ElevatorClosures{ + alerts: elevator_closures, + location_context: location_context, + screen: config, + now: now, + station_id_to_name: parent_station_map, + station_id_to_routes: routes_map + } + ] + else + :error -> [] + end + end + + defp relevant_alerts(alerts) do + Enum.filter(alerts, &(&1.effect == :elevator_closure)) + end + + defp get_routes_map(elevator_closures, home_parent_station_id) do + elevator_closures + |> get_parent_station_ids_from_entities() + |> MapSet.new() + |> MapSet.put(home_parent_station_id) + |> Enum.map(fn station_id -> + {station_id, route_ids_serving_stop(station_id)} + end) + |> Enum.into(%{}) + end + + defp get_parent_station_ids_from_entities(alerts) do + alerts + |> Enum.flat_map(fn %Alert{informed_entities: informed_entities} -> + informed_entities + |> Enum.map(fn %{stop: stop_id} -> stop_id end) + |> Enum.filter(&String.starts_with?(&1, "place-")) + end) + end + + defp route_ids_serving_stop(stop_id) do + case Route.fetch(%{stop_id: stop_id}) do + {:ok, routes} -> Enum.map(routes, & &1.id) + # Show no route pills instead of crashing the screen + :error -> [] + end + end +end From 3fcea5268b4a9e2c446b05e7a27eed22d2ecbaec Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Tue, 29 Oct 2024 09:19:32 -0400 Subject: [PATCH 03/30] Return maps with all info client will need. --- .../v2/widget_instance/elevator_closures.ex | 87 ++++++++++++++++++- 1 file changed, 83 insertions(+), 4 deletions(-) diff --git a/lib/screens/v2/widget_instance/elevator_closures.ex b/lib/screens/v2/widget_instance/elevator_closures.ex index 59f612653..d6600cb7c 100644 --- a/lib/screens/v2/widget_instance/elevator_closures.ex +++ b/lib/screens/v2/widget_instance/elevator_closures.ex @@ -1,20 +1,99 @@ defmodule Screens.V2.WidgetInstance.ElevatorClosures do @moduledoc false + alias Screens.LocationContext alias Screens.Alerts.Alert alias ScreensConfig.Screen alias ScreensConfig.V2.Elevator defstruct screen: nil, - alerts: nil + alerts: nil, + location_context: nil, + now: nil, + station_id_to_name: nil, + station_id_to_routes: nil + + @type icon :: + :red + | :blue + | :orange + | :green + | :silver + | :rail + | :bus + | :mattapan @type t :: %__MODULE__{ screen: Screen.t(), - alerts: list(Alert.t()) + alerts: list(Alert.t()), + location_context: LocationContext.t(), + now: DateTime.t(), + station_id_to_name: %{String.t() => String.t()}, + station_id_to_routes: %{String.t() => list(String.t())} + } + + def serialize(%__MODULE__{ + screen: %Screen{app_params: %Elevator{elevator_id: id}}, + alerts: alerts, + location_context: location_context, + station_id_to_name: station_id_to_name, + station_id_to_routes: station_id_to_routes + }) do + {in_station_alerts, outside_alerts} = split_alerts_by_location(alerts, location_context) + + %{ + id: id, + in_station_alerts: + serialize_alerts(in_station_alerts, station_id_to_name, station_id_to_routes), + outside_alerts: serialize_alerts(outside_alerts, station_id_to_name, station_id_to_routes) + } + end + + defp split_alerts_by_location(alerts, location_context) do + Enum.split_with(alerts, fn %Alert{informed_entities: informed_entities} -> + location_context.home_stop in Enum.map(informed_entities, & &1["stop"]) + end) + end + + defp get_informed_facility(entities) do + entities + |> Enum.find_value(fn + %{facility: facility} -> facility + _ -> false + end) + end + + defp serialize_alerts(alerts, station_id_to_name, station_id_to_routes) do + alerts + |> Enum.group_by(&get_parent_station_id_from_informed_entities(&1.informed_entities)) + |> Enum.map(fn {parent_station_id, alerts} -> + Enum.map(alerts, fn %Alert{ + id: id, + informed_entities: entities, + description: description, + header: header + } -> + facility = get_informed_facility(entities) + + %{ + station_name: Map.fetch!(station_id_to_name, parent_station_id), + routes: Map.fetch!(station_id_to_routes, parent_station_id), + alert_id: id, + elevator_name: facility.name, + elevator_id: facility.id, + description: description, + header_text: header } + end) + end) + end - def serialize(%__MODULE__{screen: %Screen{app_params: %Elevator{elevator_id: id}}}) do - %{id: id, in_station_alerts: [], outside_alerts: []} + defp get_parent_station_id_from_informed_entities(entities) do + entities + |> Enum.find_value(fn + %{stop: "place-" <> _ = parent_station_id} -> parent_station_id + _ -> false + end) end defimpl Screens.V2.WidgetInstance do From 211314ec56e4d77b3e81f4290928ed249369f652 Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Tue, 29 Oct 2024 10:00:59 -0400 Subject: [PATCH 04/30] Added mock support for tests. --- config/test.exs | 6 ++++++ lib/screens/alerts/alert.ex | 3 ++- lib/screens/facilities/facility.ex | 4 ++-- lib/screens/routes/route.ex | 4 ++-- lib/screens/stops/stop.ex | 11 +++++----- .../elevator/elevator_closures.ex | 20 ++++++++++--------- test/support/mocks.ex | 3 +++ 7 files changed, 32 insertions(+), 19 deletions(-) diff --git a/config/test.exs b/config/test.exs index 21faa85b7..ade642f84 100644 --- a/config/test.exs +++ b/config/test.exs @@ -170,6 +170,12 @@ config :screens, Screens.V2.RDS, route_pattern_module: Screens.RoutePatterns.MockRoutePattern, stop_module: Screens.Stops.MockStop +config :screens, Screens.V2.CandidateGenerator.Elevator.ElevatorClosures, + stop_module: Screens.Stops.MockStop, + facility_module: Screens.Facilities.MockFacility, + alert_module: Screens.Alerts.MockAlert, + route_module: Screens.Routes.MockRoute + config :screens, Screens.LastTrip, trip_updates_adapter: Screens.LastTrip.TripUpdates.Noop, vehicle_positions_adapter: Screens.LastTrip.VehiclePositions.Noop diff --git a/lib/screens/alerts/alert.ex b/lib/screens/alerts/alert.ex index ae342482c..19dc4ac31 100644 --- a/lib/screens/alerts/alert.ex +++ b/lib/screens/alerts/alert.ex @@ -1,7 +1,7 @@ defmodule Screens.Alerts.Alert do @moduledoc false - alias Screens.Alerts.InformedEntity + alias Screens.Alerts.{Alert, InformedEntity} alias Screens.Routes.Route alias Screens.RouteType alias Screens.Stops.Stop @@ -204,6 +204,7 @@ defmodule Screens.Alerts.Alert do end end + @callback fetch_elevator_alerts_with_facilities() :: {:ok, list(Alert.t())} | :error def fetch_elevator_alerts_with_facilities(get_json_fn \\ &V3Api.get_json/2) do query_opts = [activity: "USING_WHEELCHAIR", include: ~w[facilities]] diff --git a/lib/screens/facilities/facility.ex b/lib/screens/facilities/facility.ex index 0a53b7768..2ed98ca4e 100644 --- a/lib/screens/facilities/facility.ex +++ b/lib/screens/facilities/facility.ex @@ -4,8 +4,8 @@ defmodule Screens.Facilities.Facility do @type id :: String.t() @callback fetch_stop_for_facility(id()) :: {:ok, Stops.Stop.t()} | {:error, term()} - def fetch_stop_for_facility(facility_id, get_json_fn \\ &Screens.V3Api.get_json/2) do - case get_json_fn.("facilities/#{facility_id}", %{ + def fetch_stop_for_facility(facility_id) do + case Screens.V3Api.get_json("facilities/#{facility_id}", %{ "include" => "stop" }) do {:ok, %{"data" => _data, "included" => [stop_map]}} -> diff --git a/lib/screens/routes/route.ex b/lib/screens/routes/route.ex index 78e2825c6..fc4386f34 100644 --- a/lib/screens/routes/route.ex +++ b/lib/screens/routes/route.ex @@ -48,8 +48,8 @@ defmodule Screens.Routes.Route do end end - @spec fetch() :: {:ok, [t()]} | :error - @spec fetch(params()) :: {:ok, [t()]} | :error + @callback fetch() :: {:ok, [t()]} | :error + @callback fetch(params()) :: {:ok, [t()]} | :error def fetch(opts \\ %{}, get_json_fn \\ &V3Api.get_json/2) do params = opts diff --git a/lib/screens/stops/stop.ex b/lib/screens/stops/stop.ex index 463265f76..82abadb0d 100644 --- a/lib/screens/stops/stop.ex +++ b/lib/screens/stops/stop.ex @@ -251,6 +251,7 @@ defmodule Screens.Stops.Stop do # --- These functions involve the API --- + @callback fetch_parent_station_name_map() :: {:ok, list(%{String.t() => String.t()})} | :error def fetch_parent_station_name_map(get_json_fn \\ &V3Api.get_json/2) do case get_json_fn.("stops", %{ "filter[location_type]" => 1 @@ -442,11 +443,11 @@ defmodule Screens.Stops.Stop do @doc """ Fetches all the location context for a screen given its app type, stop id, and time """ - @spec fetch_location_context( - screen_type(), - id(), - DateTime.t() - ) :: {:ok, LocationContext.t()} | :error + @callback fetch_location_context( + screen_type(), + id(), + DateTime.t() + ) :: {:ok, LocationContext.t()} | :error def fetch_location_context(app, stop_id, now) do Screens.Telemetry.span( ~w[screens stops stop fetch_location_context]a, diff --git a/lib/screens/v2/candidate_generator/elevator/elevator_closures.ex b/lib/screens/v2/candidate_generator/elevator/elevator_closures.ex index 8f0375206..004ac4283 100644 --- a/lib/screens/v2/candidate_generator/elevator/elevator_closures.ex +++ b/lib/screens/v2/candidate_generator/elevator/elevator_closures.ex @@ -9,21 +9,23 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ElevatorClosures do alias ScreensConfig.Screen alias ScreensConfig.V2.Elevator + @stop Application.compile_env(:screens, [__MODULE__, :stop_module], Stop) + @facility Application.compile_env(:screens, [__MODULE__, :facility_module], Facility) + @alert Application.compile_env(:screens, [__MODULE__, :alert_module], Alert) + @route Application.compile_env(:screens, [__MODULE__, :route_module], Route) + def elevator_status_instances( %Screen{ app_params: %Elevator{ elevator_id: elevator_id } } = config, - now \\ DateTime.utc_now(), - fetch_stop_for_facility_fn \\ &Facility.fetch_stop_for_facility/1, - fetch_location_context_fn \\ &Stop.fetch_location_context/3, - fetch_elevator_alerts_with_facilities_fn \\ &Alert.fetch_elevator_alerts_with_facilities/0 + now \\ DateTime.utc_now() ) do - with {:ok, %Stop{id: stop_id}} <- fetch_stop_for_facility_fn.(elevator_id), - {:ok, location_context} <- fetch_location_context_fn.(Elevator, stop_id, now), - {:ok, parent_station_map} <- Stop.fetch_parent_station_name_map(), - {:ok, alerts} <- fetch_elevator_alerts_with_facilities_fn.() do + with {:ok, %Stop{id: stop_id}} <- @facility.fetch_stop_for_facility(elevator_id), + {:ok, location_context} <- @stop.fetch_location_context(Elevator, stop_id, now), + {:ok, parent_station_map} <- @stop.fetch_parent_station_name_map(), + {:ok, alerts} <- @alert.fetch_elevator_alerts_with_facilities() do elevator_closures = relevant_alerts(alerts) routes_map = get_routes_map(elevator_closures, stop_id) @@ -67,7 +69,7 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ElevatorClosures do end defp route_ids_serving_stop(stop_id) do - case Route.fetch(%{stop_id: stop_id}) do + case @route.fetch(%{stop_id: stop_id}) do {:ok, routes} -> Enum.map(routes, & &1.id) # Show no route pills instead of crashing the screen :error -> [] diff --git a/test/support/mocks.ex b/test/support/mocks.ex index d035ff3b4..4cad77793 100644 --- a/test/support/mocks.ex +++ b/test/support/mocks.ex @@ -3,3 +3,6 @@ Mox.defmock(Screens.RoutePatterns.MockRoutePattern, for: Screens.RoutePatterns.R Mox.defmock(Screens.Stops.MockStop, for: Screens.Stops.Stop) Mox.defmock(Screens.V2.MockDeparture, for: Screens.V2.Departure) Mox.defmock(Screens.V2.ScreenData.MockParameters, for: Screens.V2.ScreenData.Parameters) +Mox.defmock(Screens.Facilities.MockFacility, for: Screens.Facilities.Facility) +Mox.defmock(Screens.Alerts.MockAlert, for: Screens.Alerts.Alert) +Mox.defmock(Screens.Routes.MockRoute, for: Screens.Routes.Route) From fd6d0ece4318b7b74f9ba44ba025ffd98c34bf3d Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Tue, 29 Oct 2024 10:02:36 -0400 Subject: [PATCH 05/30] Added CG tests. --- .../elevator/elevator_closures_test.exs | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 test/screens/v2/candidate_generator/elevator/elevator_closures_test.exs diff --git a/test/screens/v2/candidate_generator/elevator/elevator_closures_test.exs b/test/screens/v2/candidate_generator/elevator/elevator_closures_test.exs new file mode 100644 index 000000000..e0fb8cf43 --- /dev/null +++ b/test/screens/v2/candidate_generator/elevator/elevator_closures_test.exs @@ -0,0 +1,124 @@ +defmodule Screens.V2.CandidateGenerator.Elevator.ElevatorClosuresTest do + use ExUnit.Case, async: true + + import Mox + setup :verify_on_exit! + + alias Screens.Alerts.{Alert, MockAlert} + alias Screens.Facilities.MockFacility + alias Screens.LocationContext + alias Screens.Routes.{MockRoute, Route} + alias Screens.Stops.{MockStop, Stop} + alias Screens.V2.CandidateGenerator.Elevator.ElevatorClosures + alias ScreensConfig.Screen + alias ScreensConfig.V2.Elevator + + describe "elevator_status_instances/5" do + test "Only returns alerts with effect of :elevator_closure" do + now = ~U[2024-10-01T05:00:00Z] + + expect(MockFacility, :fetch_stop_for_facility, fn "111" -> + {:ok, %Stop{id: "place-test"}} + end) + + expect(MockStop, :fetch_location_context, fn Elevator, "place-test", ^now -> + {:ok, %LocationContext{home_stop: "place-test"}} + end) + + expect(MockStop, :fetch_parent_station_name_map, fn -> + {:ok, [%{"place-test" => "Place Test"}]} + end) + + expect(MockRoute, :fetch, fn %{stop_id: "place-test"} -> + {:ok, [%Route{id: "Red"}]} + end) + + expect(MockAlert, :fetch_elevator_alerts_with_facilities, fn -> + alerts = [ + struct(Alert, effect: :elevator_closure, informed_entities: [%{stop: "place-test"}]), + struct(Alert, effect: :detour, informed_entities: [%{stop: "place-test"}]) + ] + + {:ok, alerts} + end) + + [ + %Screens.V2.WidgetInstance.ElevatorClosures{ + screen: %Screen{ + app_id: :elevator_v2, + app_params: %Elevator{elevator_id: "111", evergreen_content: []} + }, + alerts: [ + %Alert{ + effect: :elevator_closure, + informed_entities: [%{stop: "place-test"}] + } + ], + location_context: %LocationContext{ + home_stop: "place-test" + }, + now: ~U[2024-10-01 05:00:00Z], + station_id_to_name: [%{"place-test" => "Place Test"}], + station_id_to_routes: %{"place-test" => ["Red"]} + } + ] = + ElevatorClosures.elevator_status_instances( + struct(Screen, app_id: :elevator_v2, app_params: %Elevator{elevator_id: "111"}), + now + ) + end + + test "Return empty routes on API error" do + now = ~U[2024-10-01T05:00:00Z] + + expect(MockFacility, :fetch_stop_for_facility, fn "111" -> + {:ok, %Stop{id: "place-test"}} + end) + + expect(MockStop, :fetch_location_context, fn Elevator, "place-test", ^now -> + {:ok, %LocationContext{home_stop: "place-test"}} + end) + + expect(MockStop, :fetch_parent_station_name_map, fn -> + {:ok, [%{"place-test" => "Place Test"}]} + end) + + expect(MockRoute, :fetch, fn %{stop_id: "place-test"} -> + :error + end) + + expect(MockAlert, :fetch_elevator_alerts_with_facilities, fn -> + alerts = [ + struct(Alert, effect: :elevator_closure, informed_entities: [%{stop: "place-test"}]) + ] + + {:ok, alerts} + end) + + [ + %Screens.V2.WidgetInstance.ElevatorClosures{ + screen: %Screen{ + app_id: :elevator_v2, + app_params: %Elevator{elevator_id: "111", evergreen_content: []} + }, + alerts: [ + %Alert{ + effect: :elevator_closure, + informed_entities: [%{stop: "place-test"}] + } + ], + location_context: %LocationContext{ + home_stop: "place-test" + }, + now: ~U[2024-10-01 05:00:00Z], + station_id_to_name: [%{"place-test" => "Place Test"}], + station_id_to_routes: %{"place-test" => []} + } + ] = + ElevatorClosures.elevator_status_instances( + struct(Screen, app_id: :elevator_v2, app_params: %Elevator{elevator_id: "111"}), + now + ) + end + end +end From 997144a52158269c15a90bc9e20f6590d9440335 Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Tue, 29 Oct 2024 10:20:10 -0400 Subject: [PATCH 06/30] Fixed reference to key. --- lib/screens/v2/widget_instance/elevator_closures.ex | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/lib/screens/v2/widget_instance/elevator_closures.ex b/lib/screens/v2/widget_instance/elevator_closures.ex index d6600cb7c..a32417144 100644 --- a/lib/screens/v2/widget_instance/elevator_closures.ex +++ b/lib/screens/v2/widget_instance/elevator_closures.ex @@ -13,16 +13,6 @@ defmodule Screens.V2.WidgetInstance.ElevatorClosures do station_id_to_name: nil, station_id_to_routes: nil - @type icon :: - :red - | :blue - | :orange - | :green - | :silver - | :rail - | :bus - | :mattapan - @type t :: %__MODULE__{ screen: Screen.t(), alerts: list(Alert.t()), @@ -51,7 +41,7 @@ defmodule Screens.V2.WidgetInstance.ElevatorClosures do defp split_alerts_by_location(alerts, location_context) do Enum.split_with(alerts, fn %Alert{informed_entities: informed_entities} -> - location_context.home_stop in Enum.map(informed_entities, & &1["stop"]) + location_context.home_stop in Enum.map(informed_entities, & &1.stop) end) end From 2d82e1118950c055e45dddf64caaffde9f5bc5c1 Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Tue, 29 Oct 2024 10:20:29 -0400 Subject: [PATCH 07/30] Improved test. --- .../elevator_closures_test.exs | 54 +++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/test/screens/v2/widget_instance/elevator_closures_test.exs b/test/screens/v2/widget_instance/elevator_closures_test.exs index 96ef7e858..7fe11d0c7 100644 --- a/test/screens/v2/widget_instance/elevator_closures_test.exs +++ b/test/screens/v2/widget_instance/elevator_closures_test.exs @@ -1,6 +1,8 @@ defmodule Screens.V2.WidgetInstance.ElevatorClosuresTest do use ExUnit.Case, async: true + alias Screens.Alerts.Alert + alias Screens.LocationContext alias Screens.V2.WidgetInstance alias Screens.V2.WidgetInstance.ElevatorClosures alias ScreensConfig.Screen @@ -10,7 +12,29 @@ defmodule Screens.V2.WidgetInstance.ElevatorClosuresTest do %{ instance: %ElevatorClosures{ screen: struct(Screen, %{app_params: %Elevator{elevator_id: "111"}}), - alerts: [] + location_context: %LocationContext{home_stop: "place-test"}, + alerts: [ + %Alert{ + id: "1", + description: "Test Alert Description", + effect: :elevator_closure, + header: "Test Alert Header", + informed_entities: [ + %{stop: "place-test", facility: %{id: "111", name: "Test Elevator"}} + ] + }, + %Alert{ + id: "2", + description: "FH Alert Description", + effect: :elevator_closure, + header: "FH Alert Header", + informed_entities: [ + %{stop: "place-forhl", facility: %{id: "222", name: "FH Elevator"}} + ] + } + ], + station_id_to_name: %{"place-test" => "Test", "place-forhl" => "Forest Hills"}, + station_id_to_routes: %{"place-test" => ["Red"], "place-forhl" => ["Orange"]} } } end @@ -25,8 +49,32 @@ defmodule Screens.V2.WidgetInstance.ElevatorClosuresTest do test "returns map with id and alerts", %{instance: instance} do assert %{ id: "111", - in_station_alerts: [], - outside_alerts: [] + in_station_alerts: [ + [ + %{ + description: "Test Alert Description", + routes: ["Red"], + elevator_name: "Test Elevator", + elevator_id: "111", + station_name: "Test", + alert_id: "1", + header_text: "Test Alert Header" + } + ] + ], + outside_alerts: [ + [ + %{ + description: "FH Alert Description", + routes: ["Orange"], + elevator_name: "FH Elevator", + elevator_id: "222", + station_name: "Forest Hills", + alert_id: "2", + header_text: "FH Alert Header" + } + ] + ] } == WidgetInstance.serialize(instance) end end From d8fbca496af71ee6274dbb720b17cdc973ffb1bf Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Tue, 29 Oct 2024 10:22:08 -0400 Subject: [PATCH 08/30] Credo. --- lib/screens/facilities/facility.ex | 4 ++++ lib/screens/v2/widget_instance/elevator_closures.ex | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/screens/facilities/facility.ex b/lib/screens/facilities/facility.ex index 2ed98ca4e..3bf1e8ffb 100644 --- a/lib/screens/facilities/facility.ex +++ b/lib/screens/facilities/facility.ex @@ -1,4 +1,8 @@ defmodule Screens.Facilities.Facility do + @moduledoc """ + Functions for fetching facility data from the V3 API. + """ + alias Screens.Stops @type id :: String.t() diff --git a/lib/screens/v2/widget_instance/elevator_closures.ex b/lib/screens/v2/widget_instance/elevator_closures.ex index a32417144..f4f7d35a2 100644 --- a/lib/screens/v2/widget_instance/elevator_closures.ex +++ b/lib/screens/v2/widget_instance/elevator_closures.ex @@ -1,8 +1,8 @@ defmodule Screens.V2.WidgetInstance.ElevatorClosures do @moduledoc false - alias Screens.LocationContext alias Screens.Alerts.Alert + alias Screens.LocationContext alias ScreensConfig.Screen alias ScreensConfig.V2.Elevator From cfcda68a749982a089b244697ac0252a350c20a2 Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Tue, 29 Oct 2024 10:28:00 -0400 Subject: [PATCH 09/30] Logger error if one is returned. --- .../v2/candidate_generator/elevator/elevator_closures.ex | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/screens/v2/candidate_generator/elevator/elevator_closures.ex b/lib/screens/v2/candidate_generator/elevator/elevator_closures.ex index 004ac4283..fff79c87f 100644 --- a/lib/screens/v2/candidate_generator/elevator/elevator_closures.ex +++ b/lib/screens/v2/candidate_generator/elevator/elevator_closures.ex @@ -1,6 +1,8 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ElevatorClosures do @moduledoc false + require Logger + alias Screens.Alerts.Alert alias Screens.Facilities.Facility alias Screens.Routes.Route @@ -40,7 +42,12 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ElevatorClosures do } ] else - :error -> [] + :error -> + [] + + {:error, error} -> + Logger.error("[elevator_status_instances] #{inspect(error)}") + [] end end From 0672aff2d2f9751d0d6f6d60b9c4ccba35e5121a Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Mon, 4 Nov 2024 08:20:24 -0500 Subject: [PATCH 10/30] Address PR feedback. --- config/test.exs | 2 +- lib/screens/v2/candidate_generator/elevator.ex | 2 +- .../elevator/{elevator_closures.ex => closures.ex} | 4 +++- lib/screens/v2/widget_instance/elevator_closures.ex | 5 ++--- .../{elevator_closures_test.exs => closures_test.exs} | 4 ++-- 5 files changed, 9 insertions(+), 8 deletions(-) rename lib/screens/v2/candidate_generator/elevator/{elevator_closures.ex => closures.ex} (92%) rename test/screens/v2/candidate_generator/elevator/{elevator_closures_test.exs => closures_test.exs} (96%) diff --git a/config/test.exs b/config/test.exs index ade642f84..d47c5d39e 100644 --- a/config/test.exs +++ b/config/test.exs @@ -170,7 +170,7 @@ config :screens, Screens.V2.RDS, route_pattern_module: Screens.RoutePatterns.MockRoutePattern, stop_module: Screens.Stops.MockStop -config :screens, Screens.V2.CandidateGenerator.Elevator.ElevatorClosures, +config :screens, Screens.V2.CandidateGenerator.Elevator.Closures, stop_module: Screens.Stops.MockStop, facility_module: Screens.Facilities.MockFacility, alert_module: Screens.Alerts.MockAlert, diff --git a/lib/screens/v2/candidate_generator/elevator.ex b/lib/screens/v2/candidate_generator/elevator.ex index 9716ac24d..75f47458e 100644 --- a/lib/screens/v2/candidate_generator/elevator.ex +++ b/lib/screens/v2/candidate_generator/elevator.ex @@ -2,7 +2,7 @@ defmodule Screens.V2.CandidateGenerator.Elevator do @moduledoc false alias Screens.V2.CandidateGenerator - alias Screens.V2.CandidateGenerator.Elevator.ElevatorClosures + alias Screens.V2.CandidateGenerator.Elevator.Closures, as: ElevatorClosures alias Screens.V2.Template.Builder alias Screens.V2.WidgetInstance.{Footer, NormalHeader} alias ScreensConfig.Screen diff --git a/lib/screens/v2/candidate_generator/elevator/elevator_closures.ex b/lib/screens/v2/candidate_generator/elevator/closures.ex similarity index 92% rename from lib/screens/v2/candidate_generator/elevator/elevator_closures.ex rename to lib/screens/v2/candidate_generator/elevator/closures.ex index fff79c87f..d41f9bbd6 100644 --- a/lib/screens/v2/candidate_generator/elevator/elevator_closures.ex +++ b/lib/screens/v2/candidate_generator/elevator/closures.ex @@ -1,4 +1,4 @@ -defmodule Screens.V2.CandidateGenerator.Elevator.ElevatorClosures do +defmodule Screens.V2.CandidateGenerator.Elevator.Closures do @moduledoc false require Logger @@ -16,6 +16,8 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ElevatorClosures do @alert Application.compile_env(:screens, [__MODULE__, :alert_module], Alert) @route Application.compile_env(:screens, [__MODULE__, :route_module], Route) + @spec elevator_status_instances(Screen.t()) :: list(ElevatorClosures.t()) + @spec elevator_status_instances(Screen.t(), DateTime.t()) :: list(ElevatorClosures.t()) def elevator_status_instances( %Screen{ app_params: %Elevator{ diff --git a/lib/screens/v2/widget_instance/elevator_closures.ex b/lib/screens/v2/widget_instance/elevator_closures.ex index f4f7d35a2..259a8a0e5 100644 --- a/lib/screens/v2/widget_instance/elevator_closures.ex +++ b/lib/screens/v2/widget_instance/elevator_closures.ex @@ -1,7 +1,7 @@ defmodule Screens.V2.WidgetInstance.ElevatorClosures do @moduledoc false - alias Screens.Alerts.Alert + alias Screens.Alerts.{Alert, InformedEntity} alias Screens.LocationContext alias ScreensConfig.Screen alias ScreensConfig.V2.Elevator @@ -81,8 +81,7 @@ defmodule Screens.V2.WidgetInstance.ElevatorClosures do defp get_parent_station_id_from_informed_entities(entities) do entities |> Enum.find_value(fn - %{stop: "place-" <> _ = parent_station_id} -> parent_station_id - _ -> false + ie -> if InformedEntity.parent_station?(ie), do: ie.stop end) end diff --git a/test/screens/v2/candidate_generator/elevator/elevator_closures_test.exs b/test/screens/v2/candidate_generator/elevator/closures_test.exs similarity index 96% rename from test/screens/v2/candidate_generator/elevator/elevator_closures_test.exs rename to test/screens/v2/candidate_generator/elevator/closures_test.exs index e0fb8cf43..d2795cc64 100644 --- a/test/screens/v2/candidate_generator/elevator/elevator_closures_test.exs +++ b/test/screens/v2/candidate_generator/elevator/closures_test.exs @@ -1,4 +1,4 @@ -defmodule Screens.V2.CandidateGenerator.Elevator.ElevatorClosuresTest do +defmodule Screens.V2.CandidateGenerator.Elevator.ClosuresTest do use ExUnit.Case, async: true import Mox @@ -9,7 +9,7 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ElevatorClosuresTest do alias Screens.LocationContext alias Screens.Routes.{MockRoute, Route} alias Screens.Stops.{MockStop, Stop} - alias Screens.V2.CandidateGenerator.Elevator.ElevatorClosures + alias Screens.V2.CandidateGenerator.Elevator.Closures, as: ElevatorClosures alias ScreensConfig.Screen alias ScreensConfig.V2.Elevator From c952f3a29fbc9b37e58edcb829361bed699f336e Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Mon, 4 Nov 2024 11:24:53 -0500 Subject: [PATCH 11/30] Simplify business logic so it all lives in CG. --- .../candidate_generator/elevator/closures.ex | 64 +++++++++++-- .../v2/widget_instance/elevator_closures.ex | 94 ++++--------------- .../elevator/closures_test.exs | 76 ++++++++------- .../elevator_closures_test.exs | 75 +++++---------- 4 files changed, 136 insertions(+), 173 deletions(-) diff --git a/lib/screens/v2/candidate_generator/elevator/closures.ex b/lib/screens/v2/candidate_generator/elevator/closures.ex index d41f9bbd6..57aa2b59a 100644 --- a/lib/screens/v2/candidate_generator/elevator/closures.ex +++ b/lib/screens/v2/candidate_generator/elevator/closures.ex @@ -3,7 +3,7 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do require Logger - alias Screens.Alerts.Alert + alias Screens.Alerts.{Alert, InformedEntity} alias Screens.Facilities.Facility alias Screens.Routes.Route alias Screens.Stops.Stop @@ -23,7 +23,7 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do app_params: %Elevator{ elevator_id: elevator_id } - } = config, + }, now \\ DateTime.utc_now() ) do with {:ok, %Stop{id: stop_id}} <- @facility.fetch_stop_for_facility(elevator_id), @@ -33,14 +33,16 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do elevator_closures = relevant_alerts(alerts) routes_map = get_routes_map(elevator_closures, stop_id) + {in_station_alerts, outside_alerts} = + split_alerts_by_location(elevator_closures, location_context) + [ %ElevatorClosures{ - alerts: elevator_closures, - location_context: location_context, - screen: config, - now: now, - station_id_to_name: parent_station_map, - station_id_to_routes: routes_map + id: elevator_id, + in_station_alerts: + alert_to_elevator_closure(in_station_alerts, parent_station_map, routes_map), + outside_alerts: + alert_to_elevator_closure(outside_alerts, parent_station_map, routes_map) } ] else @@ -84,4 +86,50 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do :error -> [] end end + + defp split_alerts_by_location(alerts, location_context) do + Enum.split_with(alerts, fn %Alert{informed_entities: informed_entities} -> + location_context.home_stop in Enum.map(informed_entities, & &1.stop) + end) + end + + defp get_informed_facility(entities) do + entities + |> Enum.find_value(fn + %{facility: facility} -> facility + _ -> false + end) + end + + defp alert_to_elevator_closure(alerts, station_id_to_name, station_id_to_routes) do + alerts + |> Enum.group_by(&get_parent_station_id_from_informed_entities(&1.informed_entities)) + |> Enum.map(fn {parent_station_id, alerts} -> + Enum.map(alerts, fn %Alert{ + id: id, + informed_entities: entities, + description: description, + header: header + } -> + facility = get_informed_facility(entities) + + %{ + station_name: Map.fetch!(station_id_to_name, parent_station_id), + routes: Map.fetch!(station_id_to_routes, parent_station_id), + alert_id: id, + elevator_name: facility.name, + elevator_id: facility.id, + description: description, + header_text: header + } + end) + end) + end + + defp get_parent_station_id_from_informed_entities(entities) do + entities + |> Enum.find_value(fn + ie -> if InformedEntity.parent_station?(ie), do: ie.stop + end) + end end diff --git a/lib/screens/v2/widget_instance/elevator_closures.ex b/lib/screens/v2/widget_instance/elevator_closures.ex index 259a8a0e5..c3c863521 100644 --- a/lib/screens/v2/widget_instance/elevator_closures.ex +++ b/lib/screens/v2/widget_instance/elevator_closures.ex @@ -1,89 +1,29 @@ defmodule Screens.V2.WidgetInstance.ElevatorClosures do @moduledoc false - alias Screens.Alerts.{Alert, InformedEntity} - alias Screens.LocationContext - alias ScreensConfig.Screen - alias ScreensConfig.V2.Elevator - - defstruct screen: nil, - alerts: nil, - location_context: nil, - now: nil, - station_id_to_name: nil, - station_id_to_routes: nil + defstruct ~w[id in_station_alerts outside_alerts]a @type t :: %__MODULE__{ - screen: Screen.t(), - alerts: list(Alert.t()), - location_context: LocationContext.t(), - now: DateTime.t(), - station_id_to_name: %{String.t() => String.t()}, - station_id_to_routes: %{String.t() => list(String.t())} + id: String.t(), + in_station_alerts: list(__MODULE__.Alert.t()), + outside_alerts: list(__MODULE__.Alert.t()) } - def serialize(%__MODULE__{ - screen: %Screen{app_params: %Elevator{elevator_id: id}}, - alerts: alerts, - location_context: location_context, - station_id_to_name: station_id_to_name, - station_id_to_routes: station_id_to_routes - }) do - {in_station_alerts, outside_alerts} = split_alerts_by_location(alerts, location_context) - - %{ - id: id, - in_station_alerts: - serialize_alerts(in_station_alerts, station_id_to_name, station_id_to_routes), - outside_alerts: serialize_alerts(outside_alerts, station_id_to_name, station_id_to_routes) - } - end - - defp split_alerts_by_location(alerts, location_context) do - Enum.split_with(alerts, fn %Alert{informed_entities: informed_entities} -> - location_context.home_stop in Enum.map(informed_entities, & &1.stop) - end) - end - - defp get_informed_facility(entities) do - entities - |> Enum.find_value(fn - %{facility: facility} -> facility - _ -> false - end) + defmodule Alert do + defstruct ~w[station_name routes alert_id elevator_name elevator_id description header_text]a + + @type t :: %__MODULE__{ + station_name: String.t(), + routes: list(String.t()), + alert_id: String.t(), + elevator_name: String.t(), + elevator_id: String.t(), + description: String.t(), + header_text: String.t() + } end - defp serialize_alerts(alerts, station_id_to_name, station_id_to_routes) do - alerts - |> Enum.group_by(&get_parent_station_id_from_informed_entities(&1.informed_entities)) - |> Enum.map(fn {parent_station_id, alerts} -> - Enum.map(alerts, fn %Alert{ - id: id, - informed_entities: entities, - description: description, - header: header - } -> - facility = get_informed_facility(entities) - - %{ - station_name: Map.fetch!(station_id_to_name, parent_station_id), - routes: Map.fetch!(station_id_to_routes, parent_station_id), - alert_id: id, - elevator_name: facility.name, - elevator_id: facility.id, - description: description, - header_text: header - } - end) - end) - end - - defp get_parent_station_id_from_informed_entities(entities) do - entities - |> Enum.find_value(fn - ie -> if InformedEntity.parent_station?(ie), do: ie.stop - end) - end + def serialize(t), do: t defimpl Screens.V2.WidgetInstance do alias Screens.V2.WidgetInstance.ElevatorClosures diff --git a/test/screens/v2/candidate_generator/elevator/closures_test.exs b/test/screens/v2/candidate_generator/elevator/closures_test.exs index d2795cc64..2a0e9dcb7 100644 --- a/test/screens/v2/candidate_generator/elevator/closures_test.exs +++ b/test/screens/v2/candidate_generator/elevator/closures_test.exs @@ -26,7 +26,7 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ClosuresTest do end) expect(MockStop, :fetch_parent_station_name_map, fn -> - {:ok, [%{"place-test" => "Place Test"}]} + {:ok, %{"place-test" => "Place Test"}} end) expect(MockRoute, :fetch, fn %{stop_id: "place-test"} -> @@ -35,7 +35,12 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ClosuresTest do expect(MockAlert, :fetch_elevator_alerts_with_facilities, fn -> alerts = [ - struct(Alert, effect: :elevator_closure, informed_entities: [%{stop: "place-test"}]), + struct(Alert, + effect: :elevator_closure, + informed_entities: [ + %{stop: "place-test", facility: %{name: "Test", id: "facility-test"}} + ] + ), struct(Alert, effect: :detour, informed_entities: [%{stop: "place-test"}]) ] @@ -44,22 +49,21 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ClosuresTest do [ %Screens.V2.WidgetInstance.ElevatorClosures{ - screen: %Screen{ - app_id: :elevator_v2, - app_params: %Elevator{elevator_id: "111", evergreen_content: []} - }, - alerts: [ - %Alert{ - effect: :elevator_closure, - informed_entities: [%{stop: "place-test"}] - } + id: "111", + in_station_alerts: [ + [ + %{ + description: nil, + routes: ["Red"], + elevator_name: "Test", + elevator_id: "facility-test", + station_name: "Place Test", + alert_id: nil, + header_text: nil + } + ] ], - location_context: %LocationContext{ - home_stop: "place-test" - }, - now: ~U[2024-10-01 05:00:00Z], - station_id_to_name: [%{"place-test" => "Place Test"}], - station_id_to_routes: %{"place-test" => ["Red"]} + outside_alerts: [] } ] = ElevatorClosures.elevator_status_instances( @@ -80,7 +84,7 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ClosuresTest do end) expect(MockStop, :fetch_parent_station_name_map, fn -> - {:ok, [%{"place-test" => "Place Test"}]} + {:ok, %{"place-test" => "Place Test"}} end) expect(MockRoute, :fetch, fn %{stop_id: "place-test"} -> @@ -89,7 +93,12 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ClosuresTest do expect(MockAlert, :fetch_elevator_alerts_with_facilities, fn -> alerts = [ - struct(Alert, effect: :elevator_closure, informed_entities: [%{stop: "place-test"}]) + struct(Alert, + effect: :elevator_closure, + informed_entities: [ + %{stop: "place-test", facility: %{name: "Test", id: "facility-test"}} + ] + ) ] {:ok, alerts} @@ -97,22 +106,21 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ClosuresTest do [ %Screens.V2.WidgetInstance.ElevatorClosures{ - screen: %Screen{ - app_id: :elevator_v2, - app_params: %Elevator{elevator_id: "111", evergreen_content: []} - }, - alerts: [ - %Alert{ - effect: :elevator_closure, - informed_entities: [%{stop: "place-test"}] - } + id: "111", + in_station_alerts: [ + [ + %{ + description: nil, + routes: [], + elevator_name: "Test", + elevator_id: "facility-test", + alert_id: nil, + header_text: nil, + station_name: "Place Test" + } + ] ], - location_context: %LocationContext{ - home_stop: "place-test" - }, - now: ~U[2024-10-01 05:00:00Z], - station_id_to_name: [%{"place-test" => "Place Test"}], - station_id_to_routes: %{"place-test" => []} + outside_alerts: [] } ] = ElevatorClosures.elevator_status_instances( diff --git a/test/screens/v2/widget_instance/elevator_closures_test.exs b/test/screens/v2/widget_instance/elevator_closures_test.exs index 7fe11d0c7..702410e9c 100644 --- a/test/screens/v2/widget_instance/elevator_closures_test.exs +++ b/test/screens/v2/widget_instance/elevator_closures_test.exs @@ -1,40 +1,35 @@ defmodule Screens.V2.WidgetInstance.ElevatorClosuresTest do use ExUnit.Case, async: true - alias Screens.Alerts.Alert - alias Screens.LocationContext alias Screens.V2.WidgetInstance alias Screens.V2.WidgetInstance.ElevatorClosures - alias ScreensConfig.Screen - alias ScreensConfig.V2.Elevator setup do %{ instance: %ElevatorClosures{ - screen: struct(Screen, %{app_params: %Elevator{elevator_id: "111"}}), - location_context: %LocationContext{home_stop: "place-test"}, - alerts: [ - %Alert{ - id: "1", + id: "111", + in_station_alerts: [ + %ElevatorClosures.Alert{ description: "Test Alert Description", - effect: :elevator_closure, - header: "Test Alert Header", - informed_entities: [ - %{stop: "place-test", facility: %{id: "111", name: "Test Elevator"}} - ] - }, - %Alert{ - id: "2", - description: "FH Alert Description", - effect: :elevator_closure, - header: "FH Alert Header", - informed_entities: [ - %{stop: "place-forhl", facility: %{id: "222", name: "FH Elevator"}} - ] + routes: ["Red"], + elevator_name: "Test Elevator", + elevator_id: "111", + station_name: "Test", + alert_id: "1", + header_text: "Test Alert Header" } ], - station_id_to_name: %{"place-test" => "Test", "place-forhl" => "Forest Hills"}, - station_id_to_routes: %{"place-test" => ["Red"], "place-forhl" => ["Orange"]} + outside_alerts: [ + %ElevatorClosures.Alert{ + description: "FH Alert Description", + routes: ["Orange"], + elevator_name: "FH Elevator", + elevator_id: "222", + station_name: "Forest Hills", + alert_id: "2", + header_text: "FH Alert Header" + } + ] } } end @@ -47,35 +42,7 @@ defmodule Screens.V2.WidgetInstance.ElevatorClosuresTest do describe "serialize/1" do test "returns map with id and alerts", %{instance: instance} do - assert %{ - id: "111", - in_station_alerts: [ - [ - %{ - description: "Test Alert Description", - routes: ["Red"], - elevator_name: "Test Elevator", - elevator_id: "111", - station_name: "Test", - alert_id: "1", - header_text: "Test Alert Header" - } - ] - ], - outside_alerts: [ - [ - %{ - description: "FH Alert Description", - routes: ["Orange"], - elevator_name: "FH Elevator", - elevator_id: "222", - station_name: "Forest Hills", - alert_id: "2", - header_text: "FH Alert Header" - } - ] - ] - } == WidgetInstance.serialize(instance) + assert instance == WidgetInstance.serialize(instance) end end From 8591033cd66bee191daa3334bb5feef2c9181368 Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Mon, 4 Nov 2024 11:28:05 -0500 Subject: [PATCH 12/30] Credo. --- lib/screens/v2/widget_instance/elevator_closures.ex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/screens/v2/widget_instance/elevator_closures.ex b/lib/screens/v2/widget_instance/elevator_closures.ex index c3c863521..a8ad3d1ee 100644 --- a/lib/screens/v2/widget_instance/elevator_closures.ex +++ b/lib/screens/v2/widget_instance/elevator_closures.ex @@ -10,6 +10,8 @@ defmodule Screens.V2.WidgetInstance.ElevatorClosures do } defmodule Alert do + @moduledoc false + defstruct ~w[station_name routes alert_id elevator_name elevator_id description header_text]a @type t :: %__MODULE__{ From f13482618b3bfaa7661c27b1075d8834b665a7e9 Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Mon, 4 Nov 2024 11:59:15 -0500 Subject: [PATCH 13/30] Fix serialization. --- .../v2/candidate_generator/elevator/closures.ex | 4 ++-- lib/screens/v2/widget_instance/elevator_closures.ex | 13 ++++++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/screens/v2/candidate_generator/elevator/closures.ex b/lib/screens/v2/candidate_generator/elevator/closures.ex index 57aa2b59a..b656428d3 100644 --- a/lib/screens/v2/candidate_generator/elevator/closures.ex +++ b/lib/screens/v2/candidate_generator/elevator/closures.ex @@ -104,7 +104,7 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do defp alert_to_elevator_closure(alerts, station_id_to_name, station_id_to_routes) do alerts |> Enum.group_by(&get_parent_station_id_from_informed_entities(&1.informed_entities)) - |> Enum.map(fn {parent_station_id, alerts} -> + |> Enum.flat_map(fn {parent_station_id, alerts} -> Enum.map(alerts, fn %Alert{ id: id, informed_entities: entities, @@ -116,7 +116,7 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do %{ station_name: Map.fetch!(station_id_to_name, parent_station_id), routes: Map.fetch!(station_id_to_routes, parent_station_id), - alert_id: id, + id: id, elevator_name: facility.name, elevator_id: facility.id, description: description, diff --git a/lib/screens/v2/widget_instance/elevator_closures.ex b/lib/screens/v2/widget_instance/elevator_closures.ex index a8ad3d1ee..7ae1f91e5 100644 --- a/lib/screens/v2/widget_instance/elevator_closures.ex +++ b/lib/screens/v2/widget_instance/elevator_closures.ex @@ -12,12 +12,14 @@ defmodule Screens.V2.WidgetInstance.ElevatorClosures do defmodule Alert do @moduledoc false - defstruct ~w[station_name routes alert_id elevator_name elevator_id description header_text]a + @derive Jason.Encoder + + defstruct ~w[station_name routes id elevator_name elevator_id description header_text]a @type t :: %__MODULE__{ station_name: String.t(), routes: list(String.t()), - alert_id: String.t(), + id: String.t(), elevator_name: String.t(), elevator_id: String.t(), description: String.t(), @@ -25,7 +27,12 @@ defmodule Screens.V2.WidgetInstance.ElevatorClosures do } end - def serialize(t), do: t + def serialize(%__MODULE__{ + id: id, + in_station_alerts: in_station_alerts, + outside_alerts: outside_alerts + }), + do: %{id: id, in_station_alerts: in_station_alerts, outside_alerts: outside_alerts} defimpl Screens.V2.WidgetInstance do alias Screens.V2.WidgetInstance.ElevatorClosures From fd5d802671697b014988b24c7f41412de089ca66 Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Tue, 5 Nov 2024 09:17:20 -0500 Subject: [PATCH 14/30] Implement list without paging. --- assets/css/colors.scss | 1 + assets/css/v2/elevator/elevator_closures.scss | 45 ++++++++++- .../v2/elevator/elevator_closures.tsx | 79 ++++++++++++++++--- 3 files changed, 113 insertions(+), 12 deletions(-) diff --git a/assets/css/colors.scss b/assets/css/colors.scss index 71753a1d3..b4b0fb251 100644 --- a/assets/css/colors.scss +++ b/assets/css/colors.scss @@ -9,6 +9,7 @@ $line-color-ferry: #008eaa; $alert-yellow: #fd0; +$true-grey-45: #737373; $true-grey-70: #b2b1af; $warm-neutral-80: #cccbc8; diff --git a/assets/css/v2/elevator/elevator_closures.scss b/assets/css/v2/elevator/elevator_closures.scss index b8f33e3b7..8e72e2194 100644 --- a/assets/css/v2/elevator/elevator_closures.scss +++ b/assets/css/v2/elevator/elevator_closures.scss @@ -27,13 +27,52 @@ .outside-alert-list { height: 100%; background-color: $warm-neutral-90; + padding: 48px; + position: relative; + font-family: Inter; .header { + max-height: 432px; display: flex; - padding: 48px; - font-size: 150px; + font-size: 112px; font-weight: 700; - line-height: 150px; + line-height: 112px; + margin-bottom: 48px; + + &__title { + word-spacing: 9999px; + } + } + + .alert-list-container { + height: 904px; + + .alert-list { + .alert-row { + margin-top: 24px; + + &__station-name { + font-weight: 600; + font-size: 62px; + line-height: 80px; + color: $cool-black-15; + } + + hr { + background-color: $true-grey-45; + height: 2px; + margin-bottom: 24px; + } + } + } + } + + .paging-info-container, + .alert-row__elevator-name { + font-weight: 400; + font-size: 48px; + line-height: 64px; + color: $cool-black-30; } } } diff --git a/assets/src/components/v2/elevator/elevator_closures.tsx b/assets/src/components/v2/elevator/elevator_closures.tsx index 0e7b668c6..cb41238a9 100644 --- a/assets/src/components/v2/elevator/elevator_closures.tsx +++ b/assets/src/components/v2/elevator/elevator_closures.tsx @@ -1,9 +1,36 @@ -import React from "react"; +import React, { useLayoutEffect, useRef, useState } from "react"; import NormalService from "Images/svgr_bundled/normal-service.svg"; import AccessibilityAlert from "Images/svgr_bundled/accessibility-alert.svg"; +type ElevatorClosure = { + station_name: string; + routes: string[]; + id: string; + elevator_name: string; + elevator_id: string; + description: string; + header_text: string; +}; + +interface ClosureRowProps { + alert: ElevatorClosure; +} + +const ClosureRow = ({ alert }: ClosureRowProps) => { + const { station_name, elevator_name, elevator_id } = alert; + return ( +
+
+
{station_name}
+
+ {elevator_name} ({elevator_id}) +
+
+ ); +}; + interface InStationSummaryProps { - alerts: string[]; + alerts: ElevatorClosure[]; } const InStationSummary = ({ alerts }: InStationSummaryProps) => { @@ -25,17 +52,51 @@ const InStationSummary = ({ alerts }: InStationSummaryProps) => { }; interface OutsideAlertListProps { - alerts: string[]; + alerts: ElevatorClosure[]; } -const OutsideAlertList = (_props: OutsideAlertListProps) => { +const OutsideAlertList = ({ alerts }: OutsideAlertListProps) => { + const ref = useRef(null); + const maxHeight = 904; + const [keepChecking, setKeepChecking] = useState(true); + const [renderedAlerts, setRenderedAlerts] = useState([]); + const [overflowingAlerts, setOverflowingAlerts] = + useState(alerts); + + useLayoutEffect(() => { + if (!ref.current || !keepChecking) return; + + if (ref.current.clientHeight <= maxHeight && overflowingAlerts.length) { + setRenderedAlerts(renderedAlerts.concat(overflowingAlerts[0])); + setOverflowingAlerts(overflowingAlerts.slice(1)); + } + + if (ref.current.clientHeight > maxHeight) { + setRenderedAlerts(renderedAlerts.slice(0, -1)); + setOverflowingAlerts( + renderedAlerts.slice(0, -1).concat(overflowingAlerts), + ); + setKeepChecking(false); + } + }); + return (
- MBTA Elevator Closures - +
MBTA Elevator Closures
+
- +
+
+
+
+ {renderedAlerts.map((alert) => ( + + ))} +
+
+
+ +{overflowingAlerts.length} more elevators
); @@ -43,8 +104,8 @@ const OutsideAlertList = (_props: OutsideAlertListProps) => { interface Props { id: string; - in_station_alerts: string[]; - outside_alerts: string[]; + in_station_alerts: ElevatorClosure[]; + outside_alerts: ElevatorClosure[]; } const ElevatorClosures: React.ComponentType = ({ From 3e649d6ae2ea1e1fb1c08b0844c3fd244ba4f955 Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Tue, 5 Nov 2024 13:44:14 -0500 Subject: [PATCH 15/30] Add paging. --- .../v2/elevator/elevator_closures.tsx | 91 ++++++++++++++----- 1 file changed, 67 insertions(+), 24 deletions(-) diff --git a/assets/src/components/v2/elevator/elevator_closures.tsx b/assets/src/components/v2/elevator/elevator_closures.tsx index cb41238a9..5a9d213ba 100644 --- a/assets/src/components/v2/elevator/elevator_closures.tsx +++ b/assets/src/components/v2/elevator/elevator_closures.tsx @@ -1,6 +1,13 @@ -import React, { useLayoutEffect, useRef, useState } from "react"; +import React, { + ComponentType, + useEffect, + useLayoutEffect, + useRef, + useState, +} from "react"; import NormalService from "Images/svgr_bundled/normal-service.svg"; import AccessibilityAlert from "Images/svgr_bundled/accessibility-alert.svg"; +import makePersistent, { WrappedComponentProps } from "../persistent_wrapper"; type ElevatorClosure = { station_name: string; @@ -51,32 +58,60 @@ const InStationSummary = ({ alerts }: InStationSummaryProps) => { ); }; -interface OutsideAlertListProps { +interface OutsideAlertListProps extends WrappedComponentProps { alerts: ElevatorClosure[]; + lastUpdate: number | null; } -const OutsideAlertList = ({ alerts }: OutsideAlertListProps) => { +const OutsideAlertList = ({ + alerts, + lastUpdate, + onFinish, +}: OutsideAlertListProps) => { + const [isResizing, setIsResizing] = useState(true); + const [visibleAlerts, setVisibleAlerts] = useState([]); + const [alertsQueue, setAlertsQueue] = useState(alerts); + const [isFirstRender, setIsFirstRender] = useState(true); const ref = useRef(null); - const maxHeight = 904; - const [keepChecking, setKeepChecking] = useState(true); - const [renderedAlerts, setRenderedAlerts] = useState([]); - const [overflowingAlerts, setOverflowingAlerts] = - useState(alerts); - useLayoutEffect(() => { - if (!ref.current || !keepChecking) return; + useEffect(() => { + // Give the page a sec on first render + if (isFirstRender) { + setIsFirstRender(false); + return; + } + // If leftover alerts list is empty here, onFinish() was called in last render. + // Reset the list back to props to pick up any changes. + else if (alertsQueue.length === 0) { + setAlertsQueue(alerts); + } - if (ref.current.clientHeight <= maxHeight && overflowingAlerts.length) { - setRenderedAlerts(renderedAlerts.concat(overflowingAlerts[0])); - setOverflowingAlerts(overflowingAlerts.slice(1)); + // If we are not already resizing the list, reset it so it will start resizing. + if (!isResizing) { + setVisibleAlerts([]); + setIsResizing(true); } + }, [lastUpdate]); + + useLayoutEffect(() => { + if (!ref.current || !isResizing || isFirstRender) return; + + const maxHeight = 904; - if (ref.current.clientHeight > maxHeight) { - setRenderedAlerts(renderedAlerts.slice(0, -1)); - setOverflowingAlerts( - renderedAlerts.slice(0, -1).concat(overflowingAlerts), - ); - setKeepChecking(false); + // If we have leftover alerts and still have room in the list, add an alert to render. + if (ref.current.clientHeight < maxHeight && alertsQueue.length) { + setVisibleAlerts([...visibleAlerts, alertsQueue[0]]); + setAlertsQueue(alertsQueue.slice(1)); + } + // If adding an alert made the list too big, remove the last alert, add it back to leftover, and stop resizing. + else if (ref.current.clientHeight > maxHeight) { + setVisibleAlerts(visibleAlerts.slice(0, -1)); + setAlertsQueue(visibleAlerts.slice(-1).concat(alertsQueue)); + setIsResizing(false); + } + // If we are done resizing and there are no more alerts to page through, trigger a prop update. + else if (alertsQueue.length === 0) { + onFinish(); } }); @@ -90,19 +125,19 @@ const OutsideAlertList = ({ alerts }: OutsideAlertListProps) => {
- {renderedAlerts.map((alert) => ( + {visibleAlerts.map((alert) => ( ))}
- +{overflowingAlerts.length} more elevators + +{alerts.length - visibleAlerts.length} more elevators
); }; -interface Props { +interface Props extends WrappedComponentProps { id: string; in_station_alerts: ElevatorClosure[]; outside_alerts: ElevatorClosure[]; @@ -111,13 +146,21 @@ interface Props { const ElevatorClosures: React.ComponentType = ({ in_station_alerts: inStationAlerts, outside_alerts: outsideAlerts, + lastUpdate, + onFinish, }: Props) => { return (
- +
); }; -export default ElevatorClosures; +export default makePersistent( + ElevatorClosures as ComponentType, +); From 3803e25870350d2d6b7a8cbb770741af220c560f Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Tue, 5 Nov 2024 14:03:16 -0500 Subject: [PATCH 16/30] Added route pills. --- assets/css/elevator_v2.scss | 1 + assets/css/v2/elevator/elevator_closures.scss | 11 ++++++++ .../v2/elevator/elevator_closures.tsx | 13 +++++++--- .../candidate_generator/elevator/closures.ex | 25 ++++++++++++++++--- 4 files changed, 44 insertions(+), 6 deletions(-) diff --git a/assets/css/elevator_v2.scss b/assets/css/elevator_v2.scss index c874acaab..cdc11dd92 100644 --- a/assets/css/elevator_v2.scss +++ b/assets/css/elevator_v2.scss @@ -6,6 +6,7 @@ @import "v2/lcd_common/simulation"; @import "v2/elevator/header"; @import "v2/elevator/footer"; +@import "v2/lcd_common/route_pill"; body { margin: 0; diff --git a/assets/css/v2/elevator/elevator_closures.scss b/assets/css/v2/elevator/elevator_closures.scss index 8e72e2194..aee75b2fa 100644 --- a/assets/css/v2/elevator/elevator_closures.scss +++ b/assets/css/v2/elevator/elevator_closures.scss @@ -63,6 +63,17 @@ height: 2px; margin-bottom: 24px; } + + &__name-and-pills { + display: flex; + align-items: center; + gap: 24px; + + .route-pill { + width: 132px; + height: 68.13px; + } + } } } } diff --git a/assets/src/components/v2/elevator/elevator_closures.tsx b/assets/src/components/v2/elevator/elevator_closures.tsx index 5a9d213ba..3decb7aee 100644 --- a/assets/src/components/v2/elevator/elevator_closures.tsx +++ b/assets/src/components/v2/elevator/elevator_closures.tsx @@ -8,10 +8,11 @@ import React, { import NormalService from "Images/svgr_bundled/normal-service.svg"; import AccessibilityAlert from "Images/svgr_bundled/accessibility-alert.svg"; import makePersistent, { WrappedComponentProps } from "../persistent_wrapper"; +import RoutePill, { routePillKey, type Pill } from "../departures/route_pill"; type ElevatorClosure = { station_name: string; - routes: string[]; + routes: Pill[]; id: string; elevator_name: string; elevator_id: string; @@ -24,11 +25,17 @@ interface ClosureRowProps { } const ClosureRow = ({ alert }: ClosureRowProps) => { - const { station_name, elevator_name, elevator_id } = alert; + const { station_name, elevator_name, elevator_id, routes } = alert; + console.log(routes); return (

-
{station_name}
+
+ {routes.map((route) => ( + + ))} +
{station_name}
+
{elevator_name} ({elevator_id})
diff --git a/lib/screens/v2/candidate_generator/elevator/closures.ex b/lib/screens/v2/candidate_generator/elevator/closures.ex index b656428d3..3b50b7a70 100644 --- a/lib/screens/v2/candidate_generator/elevator/closures.ex +++ b/lib/screens/v2/candidate_generator/elevator/closures.ex @@ -8,6 +8,7 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do alias Screens.Routes.Route alias Screens.Stops.Stop alias Screens.V2.WidgetInstance.ElevatorClosures + alias Screens.V2.WidgetInstance.Serializer.RoutePill alias ScreensConfig.Screen alias ScreensConfig.V2.Elevator @@ -65,7 +66,7 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do |> MapSet.new() |> MapSet.put(home_parent_station_id) |> Enum.map(fn station_id -> - {station_id, route_ids_serving_stop(station_id)} + {station_id, station_id |> route_ids_serving_stop() |> routes_to_labels()} end) |> Enum.into(%{}) end @@ -81,12 +82,25 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do defp route_ids_serving_stop(stop_id) do case @route.fetch(%{stop_id: stop_id}) do - {:ok, routes} -> Enum.map(routes, & &1.id) + {:ok, routes} -> routes # Show no route pills instead of crashing the screen :error -> [] end end + defp routes_to_labels(routes) do + routes + |> Enum.map(fn + %Route{type: :subway, id: id} -> id |> String.downcase() |> String.to_atom() + %Route{type: :light_rail, id: "Green-" <> _} -> :green + %Route{type: :light_rail, id: "Mattapan" <> _} -> :mattapan + %Route{type: :bus, short_name: "SL" <> _} -> :silver + %Route{type: :rail} -> :cr + %Route{type: type} -> type + end) + |> Enum.uniq() + end + defp split_alerts_by_location(alerts, location_context) do Enum.split_with(alerts, fn %Alert{informed_entities: informed_entities} -> location_context.home_stop in Enum.map(informed_entities, & &1.stop) @@ -113,9 +127,14 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do } -> facility = get_informed_facility(entities) + route_pills = + station_id_to_routes + |> Map.fetch!(parent_station_id) + |> Enum.map(&RoutePill.serialize_icon/1) + %{ station_name: Map.fetch!(station_id_to_name, parent_station_id), - routes: Map.fetch!(station_id_to_routes, parent_station_id), + routes: route_pills, id: id, elevator_name: facility.name, elevator_id: facility.id, From 928bbef1d9b8023c5d8d1edf15840884ccc01c44 Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Tue, 5 Nov 2024 14:15:04 -0500 Subject: [PATCH 17/30] Fix horizontal rules. --- assets/css/v2/elevator/elevator_closures.scss | 18 +++++++++++------- .../v2/elevator/elevator_closures.tsx | 3 ++- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/assets/css/v2/elevator/elevator_closures.scss b/assets/css/v2/elevator/elevator_closures.scss index aee75b2fa..5d672904e 100644 --- a/assets/css/v2/elevator/elevator_closures.scss +++ b/assets/css/v2/elevator/elevator_closures.scss @@ -18,10 +18,11 @@ hr { width: 100%; - height: 24px; + min-height: 24px; margin-top: 0; margin-bottom: 0; background-color: $cool-black-15; + border: none; } .outside-alert-list { @@ -31,6 +32,14 @@ position: relative; font-family: Inter; + hr { + background-color: $true-grey-45; + opacity: 50%; + min-height: 2px; + margin-top: 24px; + border: none; + } + .header { max-height: 432px; display: flex; @@ -58,16 +67,11 @@ color: $cool-black-15; } - hr { - background-color: $true-grey-45; - height: 2px; - margin-bottom: 24px; - } - &__name-and-pills { display: flex; align-items: center; gap: 24px; + margin-bottom: 14px; .route-pill { width: 132px; diff --git a/assets/src/components/v2/elevator/elevator_closures.tsx b/assets/src/components/v2/elevator/elevator_closures.tsx index 3decb7aee..625fd0176 100644 --- a/assets/src/components/v2/elevator/elevator_closures.tsx +++ b/assets/src/components/v2/elevator/elevator_closures.tsx @@ -29,7 +29,6 @@ const ClosureRow = ({ alert }: ClosureRowProps) => { console.log(routes); return (
-
{routes.map((route) => ( @@ -39,6 +38,7 @@ const ClosureRow = ({ alert }: ClosureRowProps) => {
{elevator_name} ({elevator_id})
+
); }; @@ -130,6 +130,7 @@ const OutsideAlertList = ({
+
{visibleAlerts.map((alert) => ( From 2cb6c73e4de945d88a79fde4007ac012b580f1dd Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Tue, 5 Nov 2024 14:56:06 -0500 Subject: [PATCH 18/30] Generate pages before rendering any alerts. --- .../v2/elevator/elevator_closures.tsx | 82 ++++++++++++------- 1 file changed, 54 insertions(+), 28 deletions(-) diff --git a/assets/src/components/v2/elevator/elevator_closures.tsx b/assets/src/components/v2/elevator/elevator_closures.tsx index 625fd0176..9588f7398 100644 --- a/assets/src/components/v2/elevator/elevator_closures.tsx +++ b/assets/src/components/v2/elevator/elevator_closures.tsx @@ -26,7 +26,7 @@ interface ClosureRowProps { const ClosureRow = ({ alert }: ClosureRowProps) => { const { station_name, elevator_name, elevator_id, routes } = alert; - console.log(routes); + return (
@@ -75,33 +75,53 @@ const OutsideAlertList = ({ lastUpdate, onFinish, }: OutsideAlertListProps) => { - const [isResizing, setIsResizing] = useState(true); const [visibleAlerts, setVisibleAlerts] = useState([]); const [alertsQueue, setAlertsQueue] = useState(alerts); const [isFirstRender, setIsFirstRender] = useState(true); + const [pages, setPages] = useState([]); + const [pageIndex, setPageIndex] = useState(0); + // Value that tells hooks if useLayoutEffect is actively running + const [isResizing, setIsResizing] = useState(true); + // Value that forces a hook to add a page to state + const [addPage, setAddPage] = useState(true); + // Value that tells all hooks we are done until onFinish is called + const [doneGettingPages, setDoneGettingPages] = useState(false); const ref = useRef(null); useEffect(() => { - // Give the page a sec on first render - if (isFirstRender) { - setIsFirstRender(false); - return; + if (lastUpdate != null) { + if (isFirstRender) { + setIsFirstRender(false); + } else { + setPageIndex((i) => i + 1); + } } - // If leftover alerts list is empty here, onFinish() was called in last render. - // Reset the list back to props to pick up any changes. - else if (alertsQueue.length === 0) { - setAlertsQueue(alerts); + }, [lastUpdate]); + + useEffect(() => { + if (pageIndex === pages.length - 1) { + onFinish(); } + }, [pageIndex]); - // If we are not already resizing the list, reset it so it will start resizing. + useEffect(() => { + // Make sure we aren't actively calculating a page before adding to list if (!isResizing) { - setVisibleAlerts([]); - setIsResizing(true); + setPages([...pages, visibleAlerts]); + // If queue isn't empty, there are more pages to calculate + if (alertsQueue.length > 0) { + setVisibleAlerts([]); + setIsResizing(true); + } + // Done paging and safe to render content + else { + setDoneGettingPages(true); + } } - }, [lastUpdate]); + }, [addPage]); useLayoutEffect(() => { - if (!ref.current || !isResizing || isFirstRender) return; + if (!ref.current || !isResizing) return; const maxHeight = 904; @@ -115,12 +135,14 @@ const OutsideAlertList = ({ setVisibleAlerts(visibleAlerts.slice(0, -1)); setAlertsQueue(visibleAlerts.slice(-1).concat(alertsQueue)); setIsResizing(false); + setAddPage(!addPage); + } else { + setIsResizing(false); + setAddPage(!addPage); } - // If we are done resizing and there are no more alerts to page through, trigger a prop update. - else if (alertsQueue.length === 0) { - onFinish(); - } - }); + }, [visibleAlerts]); + + const alertsToRender = doneGettingPages ? pages[pageIndex] : visibleAlerts; return (
@@ -132,15 +154,19 @@ const OutsideAlertList = ({

-
- {visibleAlerts.map((alert) => ( - - ))} -
-
-
- +{alerts.length - visibleAlerts.length} more elevators + { +
+ {alertsToRender.map((alert) => ( + + ))} +
+ }
+ {pages.length && ( +
+ +{alerts.length - pages[pageIndex].length} more elevators +
+ )}
); }; From 9fad0bcadb5bc769f784a4b799ac03acc654edfe Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Tue, 5 Nov 2024 15:15:31 -0500 Subject: [PATCH 19/30] Added paging indicators. --- assets/css/v2/elevator/elevator_closures.scss | 12 ++++++++++++ .../components/v2/elevator/elevator_closures.tsx | 14 ++++++++++++-- .../images/svgr_bundled/paging_dot_selected.svg | 3 +++ .../images/svgr_bundled/paging_dot_unselected.svg | 3 +++ 4 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 assets/static/images/svgr_bundled/paging_dot_selected.svg create mode 100644 assets/static/images/svgr_bundled/paging_dot_unselected.svg diff --git a/assets/css/v2/elevator/elevator_closures.scss b/assets/css/v2/elevator/elevator_closures.scss index 5d672904e..ac0ce2e72 100644 --- a/assets/css/v2/elevator/elevator_closures.scss +++ b/assets/css/v2/elevator/elevator_closures.scss @@ -82,6 +82,18 @@ } } + .paging-info-container { + display: flex; + justify-content: space-between; + + .paging-indicators { + display: flex; + align-items: center; + gap: 27px; + margin-right: 66px; + } + } + .paging-info-container, .alert-row__elevator-name { font-weight: 400; diff --git a/assets/src/components/v2/elevator/elevator_closures.tsx b/assets/src/components/v2/elevator/elevator_closures.tsx index 9588f7398..d2ec4b429 100644 --- a/assets/src/components/v2/elevator/elevator_closures.tsx +++ b/assets/src/components/v2/elevator/elevator_closures.tsx @@ -7,6 +7,8 @@ import React, { } from "react"; import NormalService from "Images/svgr_bundled/normal-service.svg"; import AccessibilityAlert from "Images/svgr_bundled/accessibility-alert.svg"; +import PagingDotUnselected from "Images/svgr_bundled/paging_dot_unselected.svg"; +import PagingDotSelected from "Images/svgr_bundled/paging_dot_selected.svg"; import makePersistent, { WrappedComponentProps } from "../persistent_wrapper"; import RoutePill, { routePillKey, type Pill } from "../departures/route_pill"; @@ -143,7 +145,6 @@ const OutsideAlertList = ({ }, [visibleAlerts]); const alertsToRender = doneGettingPages ? pages[pageIndex] : visibleAlerts; - return (
@@ -164,7 +165,16 @@ const OutsideAlertList = ({
{pages.length && (
- +{alerts.length - pages[pageIndex].length} more elevators +
+{alerts.length - pages[pageIndex].length} more elevators
+
+ {[...Array(pages.length)].map((_, i) => { + return pageIndex === i ? ( + + ) : ( + + ); + })} +
)}
diff --git a/assets/static/images/svgr_bundled/paging_dot_selected.svg b/assets/static/images/svgr_bundled/paging_dot_selected.svg new file mode 100644 index 000000000..44cdf0c59 --- /dev/null +++ b/assets/static/images/svgr_bundled/paging_dot_selected.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/assets/static/images/svgr_bundled/paging_dot_unselected.svg b/assets/static/images/svgr_bundled/paging_dot_unselected.svg new file mode 100644 index 000000000..9ce2cd659 --- /dev/null +++ b/assets/static/images/svgr_bundled/paging_dot_unselected.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file From 43cbbf4cf11c78e552ef6b8dc7d90c3adc6f4c21 Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Tue, 5 Nov 2024 15:35:18 -0500 Subject: [PATCH 20/30] Fix tests. --- .../elevator/closures_test.exs | 44 +++++++++---------- .../elevator_closures_test.exs | 6 +-- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/test/screens/v2/candidate_generator/elevator/closures_test.exs b/test/screens/v2/candidate_generator/elevator/closures_test.exs index 2a0e9dcb7..48bb42756 100644 --- a/test/screens/v2/candidate_generator/elevator/closures_test.exs +++ b/test/screens/v2/candidate_generator/elevator/closures_test.exs @@ -30,12 +30,13 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ClosuresTest do end) expect(MockRoute, :fetch, fn %{stop_id: "place-test"} -> - {:ok, [%Route{id: "Red"}]} + {:ok, [%Route{id: "Red", type: :subway}]} end) expect(MockAlert, :fetch_elevator_alerts_with_facilities, fn -> alerts = [ struct(Alert, + id: "1", effect: :elevator_closure, informed_entities: [ %{stop: "place-test", facility: %{name: "Test", id: "facility-test"}} @@ -51,17 +52,15 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ClosuresTest do %Screens.V2.WidgetInstance.ElevatorClosures{ id: "111", in_station_alerts: [ - [ - %{ - description: nil, - routes: ["Red"], - elevator_name: "Test", - elevator_id: "facility-test", - station_name: "Place Test", - alert_id: nil, - header_text: nil - } - ] + %{ + id: "1", + description: nil, + elevator_name: "Test", + elevator_id: "facility-test", + routes: [%{type: :text, text: "RL", color: :red}], + station_name: "Place Test", + header_text: nil + } ], outside_alerts: [] } @@ -94,6 +93,7 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ClosuresTest do expect(MockAlert, :fetch_elevator_alerts_with_facilities, fn -> alerts = [ struct(Alert, + id: "1", effect: :elevator_closure, informed_entities: [ %{stop: "place-test", facility: %{name: "Test", id: "facility-test"}} @@ -108,17 +108,15 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ClosuresTest do %Screens.V2.WidgetInstance.ElevatorClosures{ id: "111", in_station_alerts: [ - [ - %{ - description: nil, - routes: [], - elevator_name: "Test", - elevator_id: "facility-test", - alert_id: nil, - header_text: nil, - station_name: "Place Test" - } - ] + %{ + id: "1", + description: nil, + elevator_name: "Test", + elevator_id: "facility-test", + routes: [], + station_name: "Place Test", + header_text: nil + } ], outside_alerts: [] } diff --git a/test/screens/v2/widget_instance/elevator_closures_test.exs b/test/screens/v2/widget_instance/elevator_closures_test.exs index 702410e9c..cf8bd40aa 100644 --- a/test/screens/v2/widget_instance/elevator_closures_test.exs +++ b/test/screens/v2/widget_instance/elevator_closures_test.exs @@ -15,7 +15,7 @@ defmodule Screens.V2.WidgetInstance.ElevatorClosuresTest do elevator_name: "Test Elevator", elevator_id: "111", station_name: "Test", - alert_id: "1", + id: "1", header_text: "Test Alert Header" } ], @@ -26,7 +26,7 @@ defmodule Screens.V2.WidgetInstance.ElevatorClosuresTest do elevator_name: "FH Elevator", elevator_id: "222", station_name: "Forest Hills", - alert_id: "2", + id: "2", header_text: "FH Alert Header" } ] @@ -42,7 +42,7 @@ defmodule Screens.V2.WidgetInstance.ElevatorClosuresTest do describe "serialize/1" do test "returns map with id and alerts", %{instance: instance} do - assert instance == WidgetInstance.serialize(instance) + assert Map.from_struct(instance) == WidgetInstance.serialize(instance) end end From 98455cdd1049ba8c5e72b44e01ab65b2deac91a9 Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Tue, 5 Nov 2024 15:36:00 -0500 Subject: [PATCH 21/30] CSS styles. --- assets/css/v2/elevator/elevator_closures.scss | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/assets/css/v2/elevator/elevator_closures.scss b/assets/css/v2/elevator/elevator_closures.scss index ac0ce2e72..bd62597db 100644 --- a/assets/css/v2/elevator/elevator_closures.scss +++ b/assets/css/v2/elevator/elevator_closures.scss @@ -26,27 +26,27 @@ } .outside-alert-list { + position: relative; height: 100%; - background-color: $warm-neutral-90; padding: 48px; - position: relative; font-family: Inter; + background-color: $warm-neutral-90; hr { - background-color: $true-grey-45; - opacity: 50%; min-height: 2px; margin-top: 24px; + background-color: $true-grey-45; border: none; + opacity: 0.5; } .header { - max-height: 432px; display: flex; + max-height: 432px; + margin-bottom: 48px; font-size: 112px; font-weight: 700; line-height: 112px; - margin-bottom: 48px; &__title { word-spacing: 9999px; @@ -61,16 +61,16 @@ margin-top: 24px; &__station-name { - font-weight: 600; font-size: 62px; + font-weight: 600; line-height: 80px; color: $cool-black-15; } &__name-and-pills { display: flex; - align-items: center; gap: 24px; + align-items: center; margin-bottom: 14px; .route-pill { @@ -88,16 +88,16 @@ .paging-indicators { display: flex; - align-items: center; gap: 27px; + align-items: center; margin-right: 66px; } } .paging-info-container, .alert-row__elevator-name { - font-weight: 400; font-size: 48px; + font-weight: 400; line-height: 64px; color: $cool-black-30; } From 6ae4646de43c4eaf676f9a34dc1a166eb2ec7fda Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Tue, 5 Nov 2024 15:47:57 -0500 Subject: [PATCH 22/30] Drop refresh rate to 7 seconds. --- lib/screens/v2/screen_data/parameters.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/screens/v2/screen_data/parameters.ex b/lib/screens/v2/screen_data/parameters.ex index 2169f757a..25d45d7a4 100644 --- a/lib/screens/v2/screen_data/parameters.ex +++ b/lib/screens/v2/screen_data/parameters.ex @@ -38,7 +38,7 @@ defmodule Screens.V2.ScreenData.Parameters do }, elevator_v2: %Static{ candidate_generator: CandidateGenerator.Elevator, - refresh_rate: 30 + refresh_rate: 7 }, gl_eink_v2: %Static{ audio_active_time: @all_times, From b290119fc84dacf8aa6fd691cd77d3713a5e52ac Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Wed, 6 Nov 2024 10:55:03 -0500 Subject: [PATCH 23/30] Simplify paging. --- assets/css/v2/elevator/elevator_closures.scss | 13 ++- .../v2/elevator/elevator_closures.tsx | 91 ++++++------------- 2 files changed, 37 insertions(+), 67 deletions(-) diff --git a/assets/css/v2/elevator/elevator_closures.scss b/assets/css/v2/elevator/elevator_closures.scss index bd62597db..2009eb8f8 100644 --- a/assets/css/v2/elevator/elevator_closures.scss +++ b/assets/css/v2/elevator/elevator_closures.scss @@ -28,7 +28,6 @@ .outside-alert-list { position: relative; height: 100%; - padding: 48px; font-family: Inter; background-color: $warm-neutral-90; @@ -43,7 +42,7 @@ .header { display: flex; max-height: 432px; - margin-bottom: 48px; + margin: 48px; font-size: 112px; font-weight: 700; line-height: 112px; @@ -54,11 +53,16 @@ } .alert-list-container { - height: 904px; + overflow: hidden; .alert-list { + display: flex; + flex-flow: column wrap; + height: 904px; + transform: translateX(calc(-100% * var(--alert-list-offset))); + .alert-row { - margin-top: 24px; + margin: 24px 48px 0; &__station-name { font-size: 62px; @@ -85,6 +89,7 @@ .paging-info-container { display: flex; justify-content: space-between; + margin: 0 48px; .paging-indicators { display: flex; diff --git a/assets/src/components/v2/elevator/elevator_closures.tsx b/assets/src/components/v2/elevator/elevator_closures.tsx index d2ec4b429..90a3bd8d0 100644 --- a/assets/src/components/v2/elevator/elevator_closures.tsx +++ b/assets/src/components/v2/elevator/elevator_closures.tsx @@ -2,7 +2,6 @@ import React, { ComponentType, useEffect, useLayoutEffect, - useRef, useState, } from "react"; import NormalService from "Images/svgr_bundled/normal-service.svg"; @@ -77,18 +76,9 @@ const OutsideAlertList = ({ lastUpdate, onFinish, }: OutsideAlertListProps) => { - const [visibleAlerts, setVisibleAlerts] = useState([]); - const [alertsQueue, setAlertsQueue] = useState(alerts); const [isFirstRender, setIsFirstRender] = useState(true); - const [pages, setPages] = useState([]); const [pageIndex, setPageIndex] = useState(0); - // Value that tells hooks if useLayoutEffect is actively running - const [isResizing, setIsResizing] = useState(true); - // Value that forces a hook to add a page to state - const [addPage, setAddPage] = useState(true); - // Value that tells all hooks we are done until onFinish is called - const [doneGettingPages, setDoneGettingPages] = useState(false); - const ref = useRef(null); + const [numPages, setNumPages] = useState(0); useEffect(() => { if (lastUpdate != null) { @@ -101,50 +91,20 @@ const OutsideAlertList = ({ }, [lastUpdate]); useEffect(() => { - if (pageIndex === pages.length - 1) { + if (pageIndex === numPages - 1) { onFinish(); } }, [pageIndex]); - useEffect(() => { - // Make sure we aren't actively calculating a page before adding to list - if (!isResizing) { - setPages([...pages, visibleAlerts]); - // If queue isn't empty, there are more pages to calculate - if (alertsQueue.length > 0) { - setVisibleAlerts([]); - setIsResizing(true); - } - // Done paging and safe to render content - else { - setDoneGettingPages(true); - } - } - }, [addPage]); - useLayoutEffect(() => { - if (!ref.current || !isResizing) return; + const closureRows = document.getElementsByClassName("alert-row"); + const uniqueOffsets = Array.from(closureRows) + .map((closure) => (closure as HTMLDivElement).offsetLeft) + .filter((val, i, self) => self.indexOf(val) === i); - const maxHeight = 904; + setNumPages(uniqueOffsets.length); + }, []); - // If we have leftover alerts and still have room in the list, add an alert to render. - if (ref.current.clientHeight < maxHeight && alertsQueue.length) { - setVisibleAlerts([...visibleAlerts, alertsQueue[0]]); - setAlertsQueue(alertsQueue.slice(1)); - } - // If adding an alert made the list too big, remove the last alert, add it back to leftover, and stop resizing. - else if (ref.current.clientHeight > maxHeight) { - setVisibleAlerts(visibleAlerts.slice(0, -1)); - setAlertsQueue(visibleAlerts.slice(-1).concat(alertsQueue)); - setIsResizing(false); - setAddPage(!addPage); - } else { - setIsResizing(false); - setAddPage(!addPage); - } - }, [visibleAlerts]); - - const alertsToRender = doneGettingPages ? pages[pageIndex] : visibleAlerts; return (
@@ -156,27 +116,32 @@ const OutsideAlertList = ({
{ -
- {alertsToRender.map((alert) => ( +
+ {alerts.map((alert) => ( ))}
}
- {pages.length && ( -
-
+{alerts.length - pages[pageIndex].length} more elevators
-
- {[...Array(pages.length)].map((_, i) => { - return pageIndex === i ? ( - - ) : ( - - ); - })} -
+
+
+{alerts.length} more elevators
+
+ {[...Array(numPages)].map((_, i) => { + return pageIndex === i ? ( + + ) : ( + + ); + })}
- )} +
); }; From 8f43924150defebdebf3a39ee3f439ac97edf52b Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Wed, 6 Nov 2024 11:13:51 -0500 Subject: [PATCH 24/30] Fix horizontal rules. --- assets/css/v2/elevator/elevator_closures.scss | 42 ++++++++++--------- .../v2/elevator/elevator_closures.tsx | 16 +++---- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/assets/css/v2/elevator/elevator_closures.scss b/assets/css/v2/elevator/elevator_closures.scss index 2009eb8f8..429bde8ba 100644 --- a/assets/css/v2/elevator/elevator_closures.scss +++ b/assets/css/v2/elevator/elevator_closures.scss @@ -16,39 +16,41 @@ color: $cool-black-30; } - hr { - width: 100%; + hr.thick { min-height: 24px; - margin-top: 0; - margin-bottom: 0; + margin: 0; background-color: $cool-black-15; border: none; } + hr.thin { + min-height: 2px; + margin: 24px 0 0; + background-color: $true-grey-45; + border: none; + opacity: 0.5; + } + .outside-alert-list { position: relative; height: 100%; font-family: Inter; background-color: $warm-neutral-90; - hr { - min-height: 2px; - margin-top: 24px; - background-color: $true-grey-45; - border: none; - opacity: 0.5; - } - - .header { - display: flex; - max-height: 432px; + .header-container { margin: 48px; - font-size: 112px; - font-weight: 700; - line-height: 112px; + margin-bottom: 0; + + .header { + display: flex; + max-height: 432px; + font-size: 112px; + font-weight: 700; + line-height: 112px; - &__title { - word-spacing: 9999px; + &__title { + word-spacing: 9999px; + } } } diff --git a/assets/src/components/v2/elevator/elevator_closures.tsx b/assets/src/components/v2/elevator/elevator_closures.tsx index 90a3bd8d0..d666c213a 100644 --- a/assets/src/components/v2/elevator/elevator_closures.tsx +++ b/assets/src/components/v2/elevator/elevator_closures.tsx @@ -39,7 +39,7 @@ const ClosureRow = ({ alert }: ClosureRowProps) => {
{elevator_name} ({elevator_id})
-
+
); }; @@ -61,7 +61,7 @@ const InStationSummary = ({ alerts }: InStationSummaryProps) => {
-
+
); }; @@ -107,13 +107,15 @@ const OutsideAlertList = ({ return (
-
-
MBTA Elevator Closures
-
- +
+
+
MBTA Elevator Closures
+
+ +
+
-
{
Date: Wed, 6 Nov 2024 11:54:33 -0500 Subject: [PATCH 25/30] Fixed paging logic. --- .../v2/elevator/elevator_closures.tsx | 52 +++++++++++++------ 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/assets/src/components/v2/elevator/elevator_closures.tsx b/assets/src/components/v2/elevator/elevator_closures.tsx index d666c213a..99728884b 100644 --- a/assets/src/components/v2/elevator/elevator_closures.tsx +++ b/assets/src/components/v2/elevator/elevator_closures.tsx @@ -2,6 +2,7 @@ import React, { ComponentType, useEffect, useLayoutEffect, + useMemo, useState, } from "react"; import NormalService from "Images/svgr_bundled/normal-service.svg"; @@ -78,7 +79,17 @@ const OutsideAlertList = ({ }: OutsideAlertListProps) => { const [isFirstRender, setIsFirstRender] = useState(true); const [pageIndex, setPageIndex] = useState(0); - const [numPages, setNumPages] = useState(0); + + // Each value represents the pageIndex the row is visible on + const [rowPageIndexes, setRowPageIndexes] = useState([]); + + const [numPages, numOffsetRows] = useMemo( + () => [ + rowPageIndexes.filter((val, i, self) => self.indexOf(val) === i).length, + rowPageIndexes.filter((offset) => offset !== pageIndex).length, + ], + [rowPageIndexes], + ); useEffect(() => { if (lastUpdate != null) { @@ -97,14 +108,31 @@ const OutsideAlertList = ({ }, [pageIndex]); useLayoutEffect(() => { - const closureRows = document.getElementsByClassName("alert-row"); - const uniqueOffsets = Array.from(closureRows) - .map((closure) => (closure as HTMLDivElement).offsetLeft) - .filter((val, i, self) => self.indexOf(val) === i); + const closureRows = Array.from( + document.getElementsByClassName("alert-row"), + ); + + const rowPageIndexes = closureRows.map((closure) => { + const val = (closure as HTMLDivElement).offsetLeft - 48; + return val / 1080; + }); - setNumPages(uniqueOffsets.length); + setRowPageIndexes(rowPageIndexes); }, []); + const getPagingIndicators = (num: number) => { + const indicators: JSX.Element[] = []; + for (let i = 0; i < num; i++) { + const indicator = + pageIndex === i ? ( + + ) : ( + + ); + indicators.push(indicator); + } + }; + return (
@@ -133,16 +161,8 @@ const OutsideAlertList = ({ }
-
+{alerts.length} more elevators
-
- {[...Array(numPages)].map((_, i) => { - return pageIndex === i ? ( - - ) : ( - - ); - })} -
+
+{numOffsetRows} more elevators
+
{getPagingIndicators(numPages)}
); From 7dce38aa0938a0b088e19d83588f6023a202019f Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Wed, 6 Nov 2024 15:22:55 -0500 Subject: [PATCH 26/30] Group alerts by station. --- assets/css/v2/elevator/elevator_closures.scss | 6 ++ .../v2/elevator/elevator_closures.tsx | 67 ++++++++++++------- .../candidate_generator/elevator/closures.ex | 64 ++++++++++-------- .../v2/widget_instance/elevator_closures.ex | 35 ++++++++-- .../elevator/closures_test.exs | 15 +++-- .../elevator_closures_test.exs | 22 +++--- 6 files changed, 130 insertions(+), 79 deletions(-) diff --git a/assets/css/v2/elevator/elevator_closures.scss b/assets/css/v2/elevator/elevator_closures.scss index 429bde8ba..66012a562 100644 --- a/assets/css/v2/elevator/elevator_closures.scss +++ b/assets/css/v2/elevator/elevator_closures.scss @@ -84,6 +84,12 @@ height: 68.13px; } } + + &__elevator-name.list-item { + display: list-item; + margin-bottom: 8px; + margin-left: 48px; + } } } } diff --git a/assets/src/components/v2/elevator/elevator_closures.tsx b/assets/src/components/v2/elevator/elevator_closures.tsx index 99728884b..0b3df0e38 100644 --- a/assets/src/components/v2/elevator/elevator_closures.tsx +++ b/assets/src/components/v2/elevator/elevator_closures.tsx @@ -5,6 +5,7 @@ import React, { useMemo, useState, } from "react"; +import cx from "classnames"; import NormalService from "Images/svgr_bundled/normal-service.svg"; import AccessibilityAlert from "Images/svgr_bundled/accessibility-alert.svg"; import PagingDotUnselected from "Images/svgr_bundled/paging_dot_unselected.svg"; @@ -12,9 +13,14 @@ import PagingDotSelected from "Images/svgr_bundled/paging_dot_selected.svg"; import makePersistent, { WrappedComponentProps } from "../persistent_wrapper"; import RoutePill, { routePillKey, type Pill } from "../departures/route_pill"; -type ElevatorClosure = { - station_name: string; +type StationWithAlert = { + id: string; + name: string; routes: Pill[]; + alerts: ElevatorClosure[]; +}; + +type ElevatorClosure = { id: string; elevator_name: string; elevator_id: string; @@ -22,24 +28,31 @@ type ElevatorClosure = { header_text: string; }; -interface ClosureRowProps { - alert: ElevatorClosure; +interface AlertRowProps { + station: StationWithAlert; } -const ClosureRow = ({ alert }: ClosureRowProps) => { - const { station_name, elevator_name, elevator_id, routes } = alert; +const AlertRow = ({ station }: AlertRowProps) => { + const { name, alerts, routes, id } = station; return (
{routes.map((route) => ( - + ))} -
{station_name}
-
-
- {elevator_name} ({elevator_id}) +
{name}
+ {alerts.map((alert) => ( +
1, + })} + > + {alert.elevator_name} ({alert.elevator_id}) +
+ ))}
); @@ -68,12 +81,12 @@ const InStationSummary = ({ alerts }: InStationSummaryProps) => { }; interface OutsideAlertListProps extends WrappedComponentProps { - alerts: ElevatorClosure[]; + stations: StationWithAlert[]; lastUpdate: number | null; } const OutsideAlertList = ({ - alerts, + stations, lastUpdate, onFinish, }: OutsideAlertListProps) => { @@ -108,17 +121,17 @@ const OutsideAlertList = ({ }, [pageIndex]); useLayoutEffect(() => { - const closureRows = Array.from( - document.getElementsByClassName("alert-row"), - ); + const alertRows = Array.from(document.getElementsByClassName("alert-row")); + const screenWidth = 1080; + const totalXMargins = 48; - const rowPageIndexes = closureRows.map((closure) => { - const val = (closure as HTMLDivElement).offsetLeft - 48; - return val / 1080; + const rowPageIndexes = alertRows.map((alert) => { + const val = (alert as HTMLDivElement).offsetLeft - totalXMargins; + return val / screenWidth; }); setRowPageIndexes(rowPageIndexes); - }, []); + }, [stations]); const getPagingIndicators = (num: number) => { const indicators: JSX.Element[] = []; @@ -131,6 +144,8 @@ const OutsideAlertList = ({ ); indicators.push(indicator); } + + return indicators; }; return ( @@ -139,7 +154,7 @@ const OutsideAlertList = ({
MBTA Elevator Closures
- +

@@ -154,8 +169,8 @@ const OutsideAlertList = ({ } as React.CSSProperties } > - {alerts.map((alert) => ( - + {stations.map((station) => ( + ))}
} @@ -171,12 +186,12 @@ const OutsideAlertList = ({ interface Props extends WrappedComponentProps { id: string; in_station_alerts: ElevatorClosure[]; - outside_alerts: ElevatorClosure[]; + stations_with_alerts: StationWithAlert[]; } const ElevatorClosures: React.ComponentType = ({ + stations_with_alerts: stationsWithAlerts, in_station_alerts: inStationAlerts, - outside_alerts: outsideAlerts, lastUpdate, onFinish, }: Props) => { @@ -184,7 +199,7 @@ const ElevatorClosures: React.ComponentType = ({
diff --git a/lib/screens/v2/candidate_generator/elevator/closures.ex b/lib/screens/v2/candidate_generator/elevator/closures.ex index 3b50b7a70..db324024f 100644 --- a/lib/screens/v2/candidate_generator/elevator/closures.ex +++ b/lib/screens/v2/candidate_generator/elevator/closures.ex @@ -40,10 +40,9 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do [ %ElevatorClosures{ id: elevator_id, - in_station_alerts: - alert_to_elevator_closure(in_station_alerts, parent_station_map, routes_map), - outside_alerts: - alert_to_elevator_closure(outside_alerts, parent_station_map, routes_map) + in_station_alerts: Enum.map(in_station_alerts, &alert_to_elevator_closure/1), + stations_with_alerts: + format_outside_alerts(outside_alerts, parent_station_map, routes_map) } ] else @@ -115,33 +114,40 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do end) end - defp alert_to_elevator_closure(alerts, station_id_to_name, station_id_to_routes) do + defp alert_to_elevator_closure(%Alert{ + id: id, + informed_entities: entities, + description: description, + header: header + }) do + facility = get_informed_facility(entities) + + %{ + id: id, + elevator_name: facility.name, + elevator_id: facility.id, + description: description, + header_text: header + } + end + + defp format_outside_alerts(alerts, station_id_to_name, station_id_to_routes) do alerts |> Enum.group_by(&get_parent_station_id_from_informed_entities(&1.informed_entities)) - |> Enum.flat_map(fn {parent_station_id, alerts} -> - Enum.map(alerts, fn %Alert{ - id: id, - informed_entities: entities, - description: description, - header: header - } -> - facility = get_informed_facility(entities) - - route_pills = - station_id_to_routes - |> Map.fetch!(parent_station_id) - |> Enum.map(&RoutePill.serialize_icon/1) - - %{ - station_name: Map.fetch!(station_id_to_name, parent_station_id), - routes: route_pills, - id: id, - elevator_name: facility.name, - elevator_id: facility.id, - description: description, - header_text: header - } - end) + |> Enum.map(fn {parent_station_id, alerts} -> + alerts_at_station = Enum.map(alerts, &alert_to_elevator_closure/1) + + route_pills = + station_id_to_routes + |> Map.fetch!(parent_station_id) + |> Enum.map(&RoutePill.serialize_icon/1) + + %{ + id: parent_station_id, + name: Map.fetch!(station_id_to_name, parent_station_id), + routes: route_pills, + alerts: alerts_at_station + } end) end diff --git a/lib/screens/v2/widget_instance/elevator_closures.ex b/lib/screens/v2/widget_instance/elevator_closures.ex index 7ae1f91e5..fceb325a5 100644 --- a/lib/screens/v2/widget_instance/elevator_closures.ex +++ b/lib/screens/v2/widget_instance/elevator_closures.ex @@ -1,24 +1,41 @@ defmodule Screens.V2.WidgetInstance.ElevatorClosures do @moduledoc false - defstruct ~w[id in_station_alerts outside_alerts]a + alias Screens.Stops.Stop + + defstruct ~w[id in_station_alerts stations_with_alerts]a @type t :: %__MODULE__{ id: String.t(), in_station_alerts: list(__MODULE__.Alert.t()), - outside_alerts: list(__MODULE__.Alert.t()) + stations_with_alerts: list(__MODULE__.Station.t()) } - defmodule Alert do + defmodule Station do @moduledoc false + alias Screens.V2.WidgetInstance.ElevatorClosures.Alert + @derive Jason.Encoder - defstruct ~w[station_name routes id elevator_name elevator_id description header_text]a + defstruct ~w[id name routes alerts]a @type t :: %__MODULE__{ - station_name: String.t(), + id: Stop.id(), + name: String.t(), routes: list(String.t()), + alerts: list(Alert.t()) + } + end + + defmodule Alert do + @moduledoc false + + @derive Jason.Encoder + + defstruct ~w[id elevator_name elevator_id description header_text]a + + @type t :: %__MODULE__{ id: String.t(), elevator_name: String.t(), elevator_id: String.t(), @@ -30,9 +47,13 @@ defmodule Screens.V2.WidgetInstance.ElevatorClosures do def serialize(%__MODULE__{ id: id, in_station_alerts: in_station_alerts, - outside_alerts: outside_alerts + stations_with_alerts: stations_with_alerts }), - do: %{id: id, in_station_alerts: in_station_alerts, outside_alerts: outside_alerts} + do: %{ + id: id, + in_station_alerts: in_station_alerts, + stations_with_alerts: stations_with_alerts + } defimpl Screens.V2.WidgetInstance do alias Screens.V2.WidgetInstance.ElevatorClosures diff --git a/test/screens/v2/candidate_generator/elevator/closures_test.exs b/test/screens/v2/candidate_generator/elevator/closures_test.exs index 48bb42756..2933e10dd 100644 --- a/test/screens/v2/candidate_generator/elevator/closures_test.exs +++ b/test/screens/v2/candidate_generator/elevator/closures_test.exs @@ -42,7 +42,12 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ClosuresTest do %{stop: "place-test", facility: %{name: "Test", id: "facility-test"}} ] ), - struct(Alert, effect: :detour, informed_entities: [%{stop: "place-test"}]) + struct(Alert, + effect: :detour, + informed_entities: [ + %{stop: "place-test", facility: %{name: "Test 2", id: "facility-test2"}} + ] + ) ] {:ok, alerts} @@ -57,12 +62,10 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ClosuresTest do description: nil, elevator_name: "Test", elevator_id: "facility-test", - routes: [%{type: :text, text: "RL", color: :red}], - station_name: "Place Test", header_text: nil } ], - outside_alerts: [] + stations_with_alerts: [] } ] = ElevatorClosures.elevator_status_instances( @@ -113,12 +116,10 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ClosuresTest do description: nil, elevator_name: "Test", elevator_id: "facility-test", - routes: [], - station_name: "Place Test", header_text: nil } ], - outside_alerts: [] + stations_with_alerts: [] } ] = ElevatorClosures.elevator_status_instances( diff --git a/test/screens/v2/widget_instance/elevator_closures_test.exs b/test/screens/v2/widget_instance/elevator_closures_test.exs index cf8bd40aa..1a093dfa8 100644 --- a/test/screens/v2/widget_instance/elevator_closures_test.exs +++ b/test/screens/v2/widget_instance/elevator_closures_test.exs @@ -11,23 +11,25 @@ defmodule Screens.V2.WidgetInstance.ElevatorClosuresTest do in_station_alerts: [ %ElevatorClosures.Alert{ description: "Test Alert Description", - routes: ["Red"], elevator_name: "Test Elevator", elevator_id: "111", - station_name: "Test", id: "1", header_text: "Test Alert Header" } ], - outside_alerts: [ - %ElevatorClosures.Alert{ - description: "FH Alert Description", + stations_with_alerts: [ + %ElevatorClosures.Station{ + name: "Forest Hills", routes: ["Orange"], - elevator_name: "FH Elevator", - elevator_id: "222", - station_name: "Forest Hills", - id: "2", - header_text: "FH Alert Header" + alerts: [ + %ElevatorClosures.Alert{ + description: "FH Alert Description", + elevator_name: "FH Elevator", + elevator_id: "222", + id: "2", + header_text: "FH Alert Header" + } + ] } ] } From 09e652d1fbfbf6247241af79364b37d3caf1746f Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Wed, 6 Nov 2024 15:24:40 -0500 Subject: [PATCH 27/30] Renamed variable. --- assets/src/components/v2/elevator/elevator_closures.tsx | 6 +++--- lib/screens/v2/candidate_generator/elevator/closures.ex | 2 +- lib/screens/v2/widget_instance/elevator_closures.ex | 8 ++++---- .../v2/candidate_generator/elevator/closures_test.exs | 4 ++-- .../screens/v2/widget_instance/elevator_closures_test.exs | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/assets/src/components/v2/elevator/elevator_closures.tsx b/assets/src/components/v2/elevator/elevator_closures.tsx index 0b3df0e38..66087ae73 100644 --- a/assets/src/components/v2/elevator/elevator_closures.tsx +++ b/assets/src/components/v2/elevator/elevator_closures.tsx @@ -186,11 +186,11 @@ const OutsideAlertList = ({ interface Props extends WrappedComponentProps { id: string; in_station_alerts: ElevatorClosure[]; - stations_with_alerts: StationWithAlert[]; + other_stations_with_alerts: StationWithAlert[]; } const ElevatorClosures: React.ComponentType = ({ - stations_with_alerts: stationsWithAlerts, + other_stations_with_alerts: otherStationsWithAlerts, in_station_alerts: inStationAlerts, lastUpdate, onFinish, @@ -199,7 +199,7 @@ const ElevatorClosures: React.ComponentType = ({
diff --git a/lib/screens/v2/candidate_generator/elevator/closures.ex b/lib/screens/v2/candidate_generator/elevator/closures.ex index db324024f..f83165e72 100644 --- a/lib/screens/v2/candidate_generator/elevator/closures.ex +++ b/lib/screens/v2/candidate_generator/elevator/closures.ex @@ -41,7 +41,7 @@ defmodule Screens.V2.CandidateGenerator.Elevator.Closures do %ElevatorClosures{ id: elevator_id, in_station_alerts: Enum.map(in_station_alerts, &alert_to_elevator_closure/1), - stations_with_alerts: + other_stations_with_alerts: format_outside_alerts(outside_alerts, parent_station_map, routes_map) } ] diff --git a/lib/screens/v2/widget_instance/elevator_closures.ex b/lib/screens/v2/widget_instance/elevator_closures.ex index fceb325a5..3197bbf3d 100644 --- a/lib/screens/v2/widget_instance/elevator_closures.ex +++ b/lib/screens/v2/widget_instance/elevator_closures.ex @@ -3,12 +3,12 @@ defmodule Screens.V2.WidgetInstance.ElevatorClosures do alias Screens.Stops.Stop - defstruct ~w[id in_station_alerts stations_with_alerts]a + defstruct ~w[id in_station_alerts other_stations_with_alerts]a @type t :: %__MODULE__{ id: String.t(), in_station_alerts: list(__MODULE__.Alert.t()), - stations_with_alerts: list(__MODULE__.Station.t()) + other_stations_with_alerts: list(__MODULE__.Station.t()) } defmodule Station do @@ -47,12 +47,12 @@ defmodule Screens.V2.WidgetInstance.ElevatorClosures do def serialize(%__MODULE__{ id: id, in_station_alerts: in_station_alerts, - stations_with_alerts: stations_with_alerts + other_stations_with_alerts: other_stations_with_alerts }), do: %{ id: id, in_station_alerts: in_station_alerts, - stations_with_alerts: stations_with_alerts + other_stations_with_alerts: other_stations_with_alerts } defimpl Screens.V2.WidgetInstance do diff --git a/test/screens/v2/candidate_generator/elevator/closures_test.exs b/test/screens/v2/candidate_generator/elevator/closures_test.exs index 2933e10dd..706989f66 100644 --- a/test/screens/v2/candidate_generator/elevator/closures_test.exs +++ b/test/screens/v2/candidate_generator/elevator/closures_test.exs @@ -65,7 +65,7 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ClosuresTest do header_text: nil } ], - stations_with_alerts: [] + other_stations_with_alerts: [] } ] = ElevatorClosures.elevator_status_instances( @@ -119,7 +119,7 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ClosuresTest do header_text: nil } ], - stations_with_alerts: [] + other_stations_with_alerts: [] } ] = ElevatorClosures.elevator_status_instances( diff --git a/test/screens/v2/widget_instance/elevator_closures_test.exs b/test/screens/v2/widget_instance/elevator_closures_test.exs index 1a093dfa8..71750b1c6 100644 --- a/test/screens/v2/widget_instance/elevator_closures_test.exs +++ b/test/screens/v2/widget_instance/elevator_closures_test.exs @@ -17,7 +17,7 @@ defmodule Screens.V2.WidgetInstance.ElevatorClosuresTest do header_text: "Test Alert Header" } ], - stations_with_alerts: [ + other_stations_with_alerts: [ %ElevatorClosures.Station{ name: "Forest Hills", routes: ["Orange"], From 1a0ee4bd207953d4ba6e20cba238e6a46767d6c5 Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Wed, 6 Nov 2024 15:29:47 -0500 Subject: [PATCH 28/30] Added a test. --- .../elevator/closures_test.exs | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/test/screens/v2/candidate_generator/elevator/closures_test.exs b/test/screens/v2/candidate_generator/elevator/closures_test.exs index 706989f66..4ea01b3c5 100644 --- a/test/screens/v2/candidate_generator/elevator/closures_test.exs +++ b/test/screens/v2/candidate_generator/elevator/closures_test.exs @@ -74,6 +74,85 @@ defmodule Screens.V2.CandidateGenerator.Elevator.ClosuresTest do ) end + test "Groups outside alerts by station" do + now = ~U[2024-10-01T05:00:00Z] + + expect(MockFacility, :fetch_stop_for_facility, fn "111" -> + {:ok, %Stop{id: "place-test"}} + end) + + expect(MockStop, :fetch_location_context, fn Elevator, "place-test", ^now -> + {:ok, %LocationContext{home_stop: "place-test"}} + end) + + expect(MockStop, :fetch_parent_station_name_map, fn -> + {:ok, %{"place-haecl" => "Haymarket"}} + end) + + expect(MockRoute, :fetch, 2, fn + %{stop_id: "place-haecl"} -> + {:ok, [%Route{id: "Orange", type: :subway}]} + + %{stop_id: "place-test"} -> + {:ok, [%Route{id: "Red", type: :subway}]} + end) + + expect(MockAlert, :fetch_elevator_alerts_with_facilities, fn -> + alerts = [ + struct(Alert, + id: "1", + effect: :elevator_closure, + informed_entities: [ + %{stop: "place-haecl", facility: %{name: "Test 1", id: "facility-test-1"}} + ] + ), + struct(Alert, + id: "2", + effect: :elevator_closure, + informed_entities: [ + %{stop: "place-haecl", facility: %{name: "Test 2", id: "facility-test-2"}} + ] + ) + ] + + {:ok, alerts} + end) + + [ + %Screens.V2.WidgetInstance.ElevatorClosures{ + id: "111", + in_station_alerts: [], + other_stations_with_alerts: [ + %{ + id: "place-haecl", + name: "Haymarket", + routes: [%{type: :text, text: "OL", color: :orange}], + alerts: [ + %{ + id: "1", + description: nil, + elevator_name: "Test 1", + elevator_id: "facility-test-1", + header_text: nil + }, + %{ + id: "2", + description: nil, + elevator_name: "Test 2", + elevator_id: "facility-test-2", + header_text: nil + } + ] + } + ] + } + ] = + ElevatorClosures.elevator_status_instances( + struct(Screen, app_id: :elevator_v2, app_params: %Elevator{elevator_id: "111"}), + now + ) + end + test "Return empty routes on API error" do now = ~U[2024-10-01T05:00:00Z] From 0c630aedb28ef6a636c048ac044b332d57061ed6 Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Wed, 6 Nov 2024 15:30:25 -0500 Subject: [PATCH 29/30] Update refresh interval. --- lib/screens/v2/screen_data/parameters.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/screens/v2/screen_data/parameters.ex b/lib/screens/v2/screen_data/parameters.ex index 25d45d7a4..bdea93454 100644 --- a/lib/screens/v2/screen_data/parameters.ex +++ b/lib/screens/v2/screen_data/parameters.ex @@ -38,7 +38,7 @@ defmodule Screens.V2.ScreenData.Parameters do }, elevator_v2: %Static{ candidate_generator: CandidateGenerator.Elevator, - refresh_rate: 7 + refresh_rate: 8 }, gl_eink_v2: %Static{ audio_active_time: @all_times, From f2b8184134531cea468b0dac044720364550e00e Mon Sep 17 00:00:00 2001 From: cmaddox5 Date: Wed, 6 Nov 2024 16:13:48 -0500 Subject: [PATCH 30/30] Various spacing/sizing fixes. --- assets/css/v2/elevator/elevator_closures.scss | 21 ++++++++++++++++--- .../v2/elevator/elevator_closures.tsx | 4 ++-- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/assets/css/v2/elevator/elevator_closures.scss b/assets/css/v2/elevator/elevator_closures.scss index 66012a562..d81e94555 100644 --- a/assets/css/v2/elevator/elevator_closures.scss +++ b/assets/css/v2/elevator/elevator_closures.scss @@ -8,8 +8,9 @@ .in-station-summary { display: flex; + gap: 82px; justify-content: space-between; - padding: 24px 58px; + padding: 24px 48px; font-size: 48px; font-weight: 400; line-height: 64px; @@ -82,9 +83,17 @@ .route-pill { width: 132px; height: 68.13px; + + & > * { + height: 100%; + } } } + &__elevator-name { + line-height: 64px; + } + &__elevator-name.list-item { display: list-item; margin-bottom: 8px; @@ -95,9 +104,16 @@ } .paging-info-container { + position: absolute; + bottom: 0; display: flex; justify-content: space-between; - margin: 0 48px; + width: 100%; + height: 72px; + + & > * { + margin: 0 48px 12px; + } .paging-indicators { display: flex; @@ -111,7 +127,6 @@ .alert-row__elevator-name { font-size: 48px; font-weight: 400; - line-height: 64px; color: $cool-black-30; } } diff --git a/assets/src/components/v2/elevator/elevator_closures.tsx b/assets/src/components/v2/elevator/elevator_closures.tsx index 66087ae73..9374e29ec 100644 --- a/assets/src/components/v2/elevator/elevator_closures.tsx +++ b/assets/src/components/v2/elevator/elevator_closures.tsx @@ -138,9 +138,9 @@ const OutsideAlertList = ({ for (let i = 0; i < num; i++) { const indicator = pageIndex === i ? ( - + ) : ( - + ); indicators.push(indicator); }