-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Filtering for in-memory Alerts cache
Supports filtering the Alerts in the in-memory cache on route, route_type, direction_id, stop, and activity. Route, stop, route type, and activity filters are lists of values where _any_ of these values need to be present in at least one of the informed entities of an alert to match. If no activity filters are provided the default activity filter of BOARD, EXIT, or RIDE is applied. Direction ID can only be filtered on one value at a time because filtering on multiple values is the same as removing the filter altogether. If no filters are passed no filtering is done including no filtering on the default activity filter.
- Loading branch information
1 parent
7d020b5
commit 88bc38d
Showing
4 changed files
with
273 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
defmodule Screens.Alerts.Cache.Filter do | ||
@moduledoc """ | ||
Logic to apply filters to a list of `Screens.Alerts.Alert` structs. | ||
""" | ||
@default_activities ~w[BOARD EXIT RIDE] | ||
|
||
@type filter_opts() :: %{ | ||
optional(:routes) => [String.t()], | ||
optional(:route_types) => [0..4 | nil], | ||
optional(:direction_id) => 0 | 1, | ||
optional(:stops) => [String.t()], | ||
optional(:activities) => [String.t()] | ||
} | ||
|
||
@spec filter_by([Screens.Alerts.Alert.t()], filter_opts()) :: [Screens.Alerts.Alert.t()] | ||
def filter_by(alerts, filter_opts) when filter_opts == %{}, do: alerts | ||
|
||
def filter_by(alerts, filter_opts) do | ||
filter_opts = Map.put_new(filter_opts, :activities, @default_activities) | ||
|
||
alerts | ||
|> filter(filter_opts) | ||
|> filter_by_informed_entity_activity(filter_opts) | ||
end | ||
|
||
defp filter(alerts, filter_opts) do | ||
filter_opts | ||
|> build_matchers() | ||
|> apply_matchers(alerts) | ||
end | ||
|
||
defp filter_by_informed_entity_activity(alerts, %{activities: values}) do | ||
values = MapSet.new(values) | ||
|
||
if MapSet.member?(values, "ALL") do | ||
alerts | ||
else | ||
alerts | ||
|> Enum.filter(fn alert -> | ||
Enum.any?(alert.informed_entities, fn informed_entity -> | ||
activities = | ||
informed_entity | ||
|> Map.get(:activities, []) | ||
|> MapSet.new() | ||
|
||
not MapSet.disjoint?(activities, values) | ||
end) | ||
end) | ||
end | ||
end | ||
|
||
defp filter_by_informed_entity_activity(alerts, filter_opts) do | ||
filter_opts = Map.put(filter_opts, :activities, @default_activities) | ||
|
||
filter_by_informed_entity_activity(alerts, filter_opts) | ||
end | ||
|
||
defp build_matchers(filter_opts) do | ||
filter_opts | ||
|> Enum.reduce([%{}], &build_matcher/2) | ||
end | ||
|
||
defp apply_matchers(matchers, alerts) do | ||
alerts | ||
|> Enum.filter(&matches?(&1, matchers)) | ||
end | ||
|
||
defp build_matcher({:routes, values}, acc) when is_list(values) do | ||
matchers_for_values(acc, :route, values) | ||
end | ||
|
||
defp build_matcher({:route_types, values}, acc) when is_list(values) do | ||
matchers_for_values(acc, :route_type, values) | ||
end | ||
|
||
defp build_matcher({:direction_id, value}, acc) when value in [0, 1] do | ||
matchers_for_values(acc, :direction_id, [value]) | ||
end | ||
|
||
defp build_matcher({:stops, values}, acc) when is_list(values) do | ||
matchers_for_values(acc, :stop, values) | ||
end | ||
|
||
defp build_matcher({:activities, values}, acc) when is_list(values) do | ||
# activities are filtered later, no need to add matchers | ||
acc | ||
end | ||
|
||
defp matchers_for_values(acc, key, values) do | ||
for value <- values, | ||
matcher <- acc do | ||
Map.put(matcher, key, value) | ||
end | ||
end | ||
|
||
defp matches?(alert, matchers) when is_list(matchers) do | ||
matchers | ||
|> Enum.any?(&matches?(alert, &1)) | ||
end | ||
|
||
defp matches?(alert, matcher) when is_map(matcher) do | ||
Enum.all?(matcher, &matches?(alert, &1)) | ||
end | ||
|
||
defp matches?(alert, {key, value}) do | ||
Enum.any?(alert.informed_entities, &(Map.get(&1, key) == value)) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters