Skip to content

Commit

Permalink
This is a copy of the alerts/diversions task with fewer changes (#2145)
Browse files Browse the repository at this point in the history
* copy over relevant changes

* mix format

* rewrite ancient code since i touched it

* file changed upstream

* add tests back in

* rearrange

* more things changed upstream

* test description

* pr feedback
  • Loading branch information
anthonyshull authored Aug 19, 2024
1 parent 79ca80f commit fd52ba8
Show file tree
Hide file tree
Showing 31 changed files with 455 additions and 116 deletions.
4 changes: 4 additions & 0 deletions assets/js/alert-item.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ export const addAlertItemEventHandlers = () => {
[...document.querySelectorAll(`.${ITEM_SELECTOR}`)].forEach(alertItem => {
alertItem.addEventListener("click", handleAlertItemClick);
alertItem.addEventListener("keydown", handleAlertItemKeyPress);

if (document.querySelector(".diversions-template") && alertItem.querySelector("img")) {
alertItem.click();
}
});
}
};
Expand Down
50 changes: 31 additions & 19 deletions lib/alerts/alert.ex
Original file line number Diff line number Diff line change
Expand Up @@ -40,27 +40,31 @@ defmodule Alerts.Alert do
]

@diversion_effects [
:detour,
:shuttle,
:stop_closure,
:station_closure,
:detour
:suspension
]

@lifecycles [:ongoing, :upcoming, :ongoing_upcoming, :new, :unknown]
@lifecycles [:new, :ongoing, :ongoing_upcoming, :unknown, :upcoming]

defstruct id: "",
header: "",
informed_entity: %InformedEntitySet{},
active_period: [],
effect: :unknown,
severity: 5,
lifecycle: :unknown,
banner: "",
cause: "",
updated_at: Timex.now(),
created_at: nil,
description: "",
effect: :unknown,
header: "",
image: nil,
image_alternative_text: nil,
informed_entity: %InformedEntitySet{},
lifecycle: :unknown,
priority: :low,
url: "",
banner: ""
severity: 5,
updated_at: Timex.now(),
url: ""

@type period_pair :: {DateTime.t() | nil, DateTime.t() | nil}

Expand Down Expand Up @@ -102,18 +106,21 @@ defmodule Alerts.Alert do
@type id_t :: String.t()
@type t :: %Alerts.Alert{
id: id_t(),
header: String.t(),
informed_entity: InformedEntitySet.t(),
active_period: [period_pair],
banner: String.t() | nil,
cause: String.t(),
created_at: DateTime.t() | nil,
description: String.t() | nil,
effect: effect,
severity: severity,
header: String.t(),
image: String.t() | nil,
image_alternative_text: String.t() | nil,
informed_entity: InformedEntitySet.t(),
lifecycle: lifecycle,
updated_at: DateTime.t(),
description: String.t() | nil,
priority: Priority.priority_level(),
url: String.t() | nil,
banner: String.t() | nil
severity: severity,
updated_at: DateTime.t(),
url: String.t() | nil
}

@type icon_type :: :alert | :cancel | :none | :shuttle | :snow
Expand Down Expand Up @@ -285,8 +292,13 @@ defmodule Alerts.Alert do
def high_severity_or_high_priority?(_), do: false

@spec diversion?(t) :: boolean()
def diversion?(%{effect: effect}),
do: effect in @diversion_effects
def diversion?(alert) do
alert.effect in @diversion_effects &&
alert.active_period
|> List.first()
|> Kernel.elem(0)
|> Timex.after?(alert.created_at)
end

@spec municipality(t) :: String.t() | nil
def municipality(alert) do
Expand Down
39 changes: 21 additions & 18 deletions lib/alerts/informed_entity.ex
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
defmodule Alerts.InformedEntity do
@fields [:route, :route_type, :stop, :trip, :direction_id, :facility, :activities]
@empty_activities MapSet.new()
defstruct route: nil,
route_type: nil,
stop: nil,
trip: nil,
direction_id: nil,
facility: nil,
activities: @empty_activities

@type t :: %Alerts.InformedEntity{
@moduledoc false

@fields activities: MapSet.new(),
direction_id: nil,
facility: nil,
route: nil,
route_type: nil,
stop: nil,
trip: nil

defstruct @fields

@type t :: %__MODULE__{
activities: MapSet.t(activity),
direction_id: 0 | 1 | nil,
facility: String.t() | nil,
Expand All @@ -29,8 +31,6 @@ defmodule Alerts.InformedEntity do
| :using_escalator
| :using_wheelchair

alias __MODULE__, as: IE

@activities [
:board,
:bringing_bike,
Expand All @@ -47,9 +47,9 @@ defmodule Alerts.InformedEntity do

@doc """
Given a keyword list (with keys matching our fields), returns a new
InformedEntity. Additional keys are ignored.
InformedEntity. Additional keys are ignored.
"""
@spec from_keywords(list) :: IE.t()
@spec from_keywords(list) :: __MODULE__.t()
def from_keywords(options) do
options
|> Enum.map(&ensure_value_type/1)
Expand All @@ -72,13 +72,14 @@ defmodule Alerts.InformedEntity do
Otherwise the nil can match any value in the other InformedEntity.
"""
@spec match?(IE.t(), IE.t()) :: boolean
def match?(%IE{} = first, %IE{} = second) do
@spec match?(__MODULE__.t(), __MODULE__.t()) :: boolean
def match?(%__MODULE__{} = first, %__MODULE__{} = second) do
share_a_key?(first, second) && do_match?(first, second)
end

@spec mapsets_match?(MapSet.t(), MapSet.t()) :: boolean()
def mapsets_match?(%MapSet{} = a, %MapSet{} = b)
when a == @empty_activities or b == @empty_activities,
when a == %MapSet{} or b == %MapSet{},
do: true

def mapsets_match?(%MapSet{} = a, %MapSet{} = b), do: has_intersect?(a, b)
Expand All @@ -87,6 +88,7 @@ defmodule Alerts.InformedEntity do

defp do_match?(f, s) do
@fields
|> Keyword.keys()
|> Enum.all?(&key_match(Map.get(f, &1), Map.get(s, &1)))
end

Expand All @@ -98,6 +100,7 @@ defmodule Alerts.InformedEntity do

defp share_a_key?(first, second) do
@fields
|> Keyword.keys()
|> Enum.any?(&shared_key(Map.get(first, &1), Map.get(second, &1)))
end

Expand Down
35 changes: 18 additions & 17 deletions lib/alerts/informed_entity_set.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,31 @@ defmodule Alerts.InformedEntitySet do
it's present in the InformedEntitySet. If it's not, there's no way for it
to match any of the InformedEntities inside.
"""
alias Alerts.InformedEntity, as: IE

defstruct route: MapSet.new(),
route_type: MapSet.new(),
stop: MapSet.new(),
trip: MapSet.new(),
alias Alerts.InformedEntity

defstruct activities: MapSet.new(),
direction_id: MapSet.new(),
entities: [],
facility: MapSet.new(),
activities: MapSet.new(),
entities: []
route: MapSet.new(),
route_type: MapSet.new(),
stop: MapSet.new(),
trip: MapSet.new()

@type t :: %__MODULE__{
activities: MapSet.t(),
direction_id: MapSet.t(),
entities: [InformedEntity.t()],
facility: MapSet.t(),
route: MapSet.t(),
route_type: MapSet.t(),
stop: MapSet.t(),
trip: MapSet.t(),
direction_id: MapSet.t(),
facility: MapSet.t(),
activities: MapSet.t(),
entities: [IE.t()]
trip: MapSet.t()
}

@doc "Create a new InformedEntitySet from a list of InformedEntitys"
@spec new([IE.t()]) :: t
@spec new([InformedEntity.t()]) :: t
def new(%__MODULE__{} = entity_set) do
entity_set
end
Expand All @@ -40,8 +41,8 @@ defmodule Alerts.InformedEntitySet do
end

@doc "Returns whether the given entity matches the set"
@spec match?(t, IE.t()) :: boolean
def match?(%__MODULE__{} = set, %IE{} = entity) do
@spec match?(t, InformedEntity.t()) :: boolean
def match?(%__MODULE__{} = set, %InformedEntity{} = entity) do
entity
|> Map.from_struct()
|> Enum.all?(&field_in_set?(set, &1))
Expand Down Expand Up @@ -73,7 +74,7 @@ defmodule Alerts.InformedEntitySet do
end

defp field_in_set?(set, {:activities, %MapSet{} = value}) do
IE.mapsets_match?(set.activities, value)
InformedEntity.mapsets_match?(set.activities, value)
end

defp field_in_set?(set, {key, value}) do
Expand All @@ -89,7 +90,7 @@ defmodule Alerts.InformedEntitySet do

defp try_all_entity_match(true, set, entity) do
# we only try matching against the whole set when the MapSets overlapped
Enum.any?(set, &IE.match?(&1, entity))
Enum.any?(set, &InformedEntity.match?(&1, entity))
end
end

Expand Down
17 changes: 10 additions & 7 deletions lib/alerts/parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,20 @@ defmodule Alerts.Parser do
def parse(%JsonApi.Item{type: "alert", id: id, attributes: attributes}) do
Alerts.Alert.new(
id: id,
header: attributes["header"],
informed_entity: parse_informed_entity(attributes["informed_entity"]),
active_period: Enum.map(attributes["active_period"], &active_period/1),
effect: effect(attributes),
banner: description(attributes["banner"]),
cause: cause(attributes["cause"]),
severity: severity(attributes["severity"]),
created_at: parse_time(attributes["created_at"]),
description: description(attributes["description"]),
effect: effect(attributes),
header: attributes["header"],
image: attributes["image"],
image_alternative_text: attributes["image_alternative_text"],
informed_entity: parse_informed_entity(attributes["informed_entity"]),
lifecycle: lifecycle(attributes["lifecycle"]),
severity: severity(attributes["severity"]),
updated_at: parse_time(attributes["updated_at"]),
description: description(attributes["description"]),
url: description(attributes["url"]),
banner: description(attributes["banner"])
url: description(attributes["url"])
)
end

Expand Down
15 changes: 15 additions & 0 deletions lib/alerts/repo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,21 @@ defmodule Alerts.Repo do
|> Store.alerts(now)
end

@doc """
Get alerts that are diversion types: shuttle, station_closure, suspension.
We sort them so that earlier alerts are displaed first.
"""
@spec diversions_by_route_ids([String.t()], DateTime.t()) :: [Alert.t()]
def diversions_by_route_ids(route_ids, now) do
route_ids
|> by_route_ids(now)
|> Enum.filter(&Alert.diversion?/1)
|> Enum.sort(fn a, b ->
a.active_period |> List.first() |> elem(0) < b.active_period |> List.first() |> elem(0)
end)
end

@spec by_route_types(Enumerable.t(), DateTime.t()) :: [Alert.t()]
def by_route_types(types, now) do
types
Expand Down
16 changes: 16 additions & 0 deletions lib/cms/helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,22 @@ defmodule CMS.Helpers do
|> Enum.map(&File.from_api/1)
end

@spec parse_page_types(map) :: list(String.t())
def parse_page_types(%{} = data) do
data
|> Map.get("field_page_type", [])
|> Kernel.get_in([Access.all(), "name"])
|> Enum.reject(&Kernel.is_nil/1)
end

@spec parse_related_transit(map) :: list(String.t())
def parse_related_transit(%{} = data) do
data
|> Map.get("field_related_transit", [])
|> Kernel.get_in([Access.all(), "name"])
|> Enum.reject(&Kernel.is_nil/1)
end

@spec path_alias(map) :: String.t() | nil
def path_alias(data) do
data
Expand Down
5 changes: 5 additions & 0 deletions lib/cms/page.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ defmodule CMS.Page do

alias CMS.Page.{
Basic,
Diversions,
Event,
EventAgenda,
Landing,
Expand Down Expand Up @@ -73,6 +74,10 @@ defmodule CMS.Page do
Redirect.from_api(api_data)
end

defp parse(%{"field_page_type" => [%{"name" => "Diversions"}]} = api_data, preview_opts) do
Diversions.from_api(api_data, preview_opts)
end

# For all other node/content types from the CMS, use a common struct/template
defp parse(%{"type" => [%{"target_type" => "node_type"}]} = api_data, preview_opts) do
Basic.from_api(api_data, preview_opts)
Expand Down
Loading

0 comments on commit fd52ba8

Please sign in to comment.