Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Disruption list route filter #1133

Merged
merged 4 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions lib/arrow/disruptions.ex
Original file line number Diff line number Diff line change
Expand Up @@ -396,4 +396,25 @@ defmodule Arrow.Disruptions do
&(&1 |> Integer.to_string() |> String.pad_leading(2, "0"))
)
end

def start_end_dates(%DisruptionV2{limits: [], replacement_services: []}) do
{nil, nil}
end

def start_end_dates(%DisruptionV2{
limits: limits,
replacement_services: replacement_services
}) do
min_date =
(limits ++ replacement_services)
|> Enum.map(& &1.start_date)
|> Enum.min(Date, fn -> ~D[9999-12-31] end)

max_date =
(limits ++ replacement_services)
|> Enum.map(& &1.end_date)
|> Enum.max(Date, fn -> ~D[0000-01-01] end)

{min_date, max_date}
end
end
7 changes: 4 additions & 3 deletions lib/arrow_web/controllers/disruption_v2_controller.ex
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
defmodule ArrowWeb.DisruptionV2Controller do
use ArrowWeb, :controller

alias Arrow.Disruptions
alias ArrowWeb.DisruptionV2Controller.Filters
alias ArrowWeb.DisruptionV2Controller.{Filters, Index}
alias ArrowWeb.Plug.Authorize
alias Plug.Conn

Expand All @@ -12,9 +11,11 @@ defmodule ArrowWeb.DisruptionV2Controller do
def index(conn, params) do
filters = Filters.from_params(params)

disruptions = Index.all(filters)

render(conn, "index.html",
user: conn.assigns.current_user,
disruptions: Disruptions.list_disruptionsv2(),
disruptions: disruptions,
filters: filters
)
end
Expand Down
32 changes: 30 additions & 2 deletions lib/arrow_web/controllers/disruption_v2_controller/filters.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ defmodule ArrowWeb.DisruptionV2Controller.Filters do

@behaviour Behaviour

@type t :: %__MODULE__{view: Calendar.t() | Table.t()}
@type t :: %__MODULE__{
kinds: MapSet.t(atom()),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: I know kinds is a term from v1, but it feels odd to me. Does it make sense to just go with routes instead since it will always hold route IDs anyway?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, that's a good point. The one thing that gives me pause about that though is that it will eventually also include Commuter Rail, which is a mode as opposed to a route, so it's actually kind of a heterogeneous mix of routes and modes, and I'm not really sure of a better term that captures both of those things.

only_approved?: boolean(),
view: Calendar.t() | Table.t()
}

defstruct kinds: @empty_set, only_approved?: false, search: nil, view: %Table{}

Expand All @@ -37,7 +41,12 @@ defmodule ArrowWeb.DisruptionV2Controller.Filters do
def from_params(params) when is_map(params) do
view_mod = if(params["view"] == "calendar", do: Calendar, else: Table)

%__MODULE__{view: view_mod.from_params(params)}
%__MODULE__{
kinds:
params |> Map.get("kinds", []) |> Enum.map(&String.to_existing_atom/1) |> MapSet.new(),
only_approved?: not is_nil(params["only_approved"]),
view: view_mod.from_params(params)
}
end

@impl true
Expand All @@ -50,16 +59,35 @@ defmodule ArrowWeb.DisruptionV2Controller.Filters do
%__MODULE__{view: view_mod.reset(view)}
end

@spec toggle_kind(%__MODULE__{}, atom()) :: %__MODULE__{}
def toggle_kind(%__MODULE__{kinds: kinds} = filters, kind) do
new_kinds = if(kind in kinds, do: MapSet.delete(kinds, kind), else: MapSet.put(kinds, kind))
struct!(filters, kinds: new_kinds)
end

@spec toggle_only_approved(t()) :: t()
def toggle_only_approved(%__MODULE__{only_approved?: only_approved} = filters) do
%__MODULE__{filters | only_approved?: !only_approved}
end

@spec toggle_view(%__MODULE__{}) :: %__MODULE__{}
def toggle_view(%__MODULE__{view: %Calendar{}} = filters), do: %{filters | view: %Table{}}
def toggle_view(%__MODULE__{view: %Table{}} = filters), do: %{filters | view: %Calendar{}}

@impl true
def to_params(%__MODULE__{
kinds: kinds,
only_approved?: only_approved?,
view: %{__struct__: view_mod} = view
}) do
%{}
|> put_if(view_mod == Calendar, "view", "calendar")
|> put_if(
kinds != @empty_set,
"kinds",
kinds |> MapSet.to_list() |> Enum.map(&to_string/1) |> Enum.sort()
)
|> put_if(only_approved?, "only_approved", "true")
|> Map.merge(view_mod.to_params(view))
end

Expand Down
62 changes: 62 additions & 0 deletions lib/arrow_web/controllers/disruption_v2_controller/index.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
defmodule ArrowWeb.DisruptionV2Controller.Index do
@moduledoc """
Applies filters on disruptions for index view
"""

alias Arrow.Disruptions
alias Arrow.Disruptions.DisruptionV2
alias ArrowWeb.DisruptionV2Controller.Filters
alias ArrowWeb.DisruptionV2Controller.Filters.Table

@disruption_kind_routes %{
blue_line: ["Blue"],
orange_line: ["Orange"],
red_line: ["Red"],
mattapan_line: ["Mattapan"],
green_line: ["Green-B", "Green-C", "Green-D", "Green-E"],
green_line_b: ["Green-B"],
green_line_c: ["Green-C"],
green_line_d: ["Green-D"],
green_line_e: ["Green-E"]
}

@empty_set MapSet.new()

@spec all(Filters.t() | nil) :: [DisruptionV2.t()]
def all(filters),
do: apply_to_disruptions(Disruptions.list_disruptionsv2(), filters)

@spec apply_to_disruptions([DisruptionV2.t()], Filters.t()) :: [DisruptionV2.t()]
def apply_to_disruptions(disruptions, filters) do
Enum.filter(
disruptions,
&(apply_kinds_filter(&1, filters) and apply_only_approved_filter(&1, filters) and
apply_past_filter(&1, filters))
)
end

defp apply_kinds_filter(_disruption, %Filters{kinds: kinds}) when kinds == @empty_set,
do: true

defp apply_kinds_filter(disruption, %Filters{kinds: kinds}) do
kind_routes = kinds |> Enum.map(&@disruption_kind_routes[&1]) |> List.flatten()

Enum.any?(disruption.limits, fn limit -> limit.route.id in kind_routes end)
end

defp apply_only_approved_filter(disruption, %Filters{only_approved?: true}),
do: disruption.is_active

defp apply_only_approved_filter(_disruption, %Filters{only_approved?: false}),
do: true

defp apply_past_filter(disruption, %Filters{view: %Table{include_past?: false}}) do
cutoff = Date.utc_today() |> Date.add(-7)

{_start_date, end_date} = Disruptions.start_end_dates(disruption)

is_nil(end_date) or Date.after?(end_date, cutoff)
end

defp apply_past_filter(_disruption, _filter), do: true
end
66 changes: 47 additions & 19 deletions lib/arrow_web/controllers/disruption_v2_html.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ defmodule ArrowWeb.DisruptionV2View do
use ArrowWeb, :html

alias __MODULE__.Calendar, as: DCalendar
alias Arrow.Disruptions
alias Arrow.Disruptions.DisruptionV2
alias Arrow.Permissions
alias ArrowWeb.DisruptionV2Controller.Filters
Expand All @@ -20,6 +21,36 @@ defmodule ArrowWeb.DisruptionV2View do
"Red" => "red-line"
}

@disruption_kinds ~w(
blue_line
orange_line
red_line
mattapan_line
green_line
green_line_b
green_line_c
green_line_d
green_line_e
commuter_rail
silver_line
bus
)a

@disruption_kind_icon_names %{
blue_line: "blue-line",
bus: "mode-bus",
commuter_rail: "mode-commuter-rail",
green_line: "green-line",
green_line_b: "green-line-b",
green_line_c: "green-line-c",
green_line_d: "green-line-d",
green_line_e: "green-line-e",
mattapan_line: "mattapan-line",
orange_line: "orange-line",
red_line: "red-line",
silver_line: "silver-line"
}

attr :conn, Plug.Conn, required: true
attr :route_id, :string, required: true
attr :size, :string, values: ~w(sm lg), required: true
Expand All @@ -39,29 +70,22 @@ defmodule ArrowWeb.DisruptionV2View do
Routes.static_path(conn, "/images/icon-#{@route_icon_names[route_id]}-small.svg")
end

defp disrupted_routes(%DisruptionV2{limits: limits}) do
limits |> Enum.map(& &1.route.id) |> Enum.uniq()
@spec disruption_kind_icon_path(Plug.Conn.t(), atom()) :: String.t()
def disruption_kind_icon_path(conn, kind) do
Routes.static_path(conn, "/images/icon-#{@disruption_kind_icon_names[kind]}-small.svg")
end

defp get_dates(%DisruptionV2{limits: [], replacement_services: []}) do
{nil, nil}
defp disruption_kinds, do: @disruption_kinds

defp disruption_kind_icon(conn, kind, size, opts \\ []) when size in ~w(sm lg) do
content_tag(:span, "",
class: "m-icon m-icon-#{size} #{Keyword.get(opts, :class, "")}",
style: "background-image: url(#{disruption_kind_icon_path(conn, kind)})"
)
end

defp get_dates(%DisruptionV2{
limits: limits,
replacement_services: replacement_services
}) do
min_date =
(limits ++ replacement_services)
|> Enum.map(& &1.start_date)
|> Enum.min(Date, fn -> ~D[9999-12-31] end)

max_date =
(limits ++ replacement_services)
|> Enum.map(& &1.end_date)
|> Enum.max(Date, fn -> ~D[0000-01-01] end)

{min_date, max_date}
defp disrupted_routes(%DisruptionV2{limits: limits}) do
limits |> Enum.map(& &1.route.id) |> Enum.uniq()
end

defp format_date(nil), do: "N/A"
Expand All @@ -73,4 +97,8 @@ defmodule ArrowWeb.DisruptionV2View do
defp update_filters_path(conn, filters) do
Controller.current_path(conn, Filters.to_params(filters))
end

defp update_view_path(conn, %{view: view} = filters, key, value) do
update_filters_path(conn, %{filters | view: %{view | key => value}})
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

<tbody>
<%= for disruption <- @disruptions do %>
<% {start_date, end_date} = get_dates(disruption) %>
<% {start_date, end_date} = Disruptions.start_end_dates(disruption) %>
<tr>
<td>
<a href={~p"/disruptionsv2/#{disruption.id}/edit"} class="btn-link btn-sm">
Expand Down
31 changes: 30 additions & 1 deletion lib/arrow_web/controllers/disruption_v2_html/index.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,36 @@
</div>

<div class="row my-3">
<div class="col">
<div class="col flex">
<%= for kind <- disruption_kinds() do %>
<% show_as_active? = MapSet.size(@filters.kinds) == 0 or kind in @filters.kinds %>
<% active_class = if(show_as_active?, do: "active", else: "") %>

<.link
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should look into this page using LiveView in the future? Would be nice if the filters worked without a page load.

class={"d-flex mr-1 m-disruption-index__route_filter #{active_class}"}
aria-label={kind |> to_string() |> String.replace("_", " ")}
href={update_filters_path(@conn, Filters.toggle_kind(@filters, kind))}
>
{disruption_kind_icon(@conn, kind, "lg")}
</.link>
<% end %>

<%= if not Filters.calendar?(@filters) do %>
{link("include past",
class:
"mx-2 btn btn-outline-secondary" <>
if(@filters.view.include_past?, do: " active", else: ""),
to: update_view_path(@conn, @filters, :include_past?, [email protected]_past?)
)}
<% end %>

{link("approved",
class:
"mx-2 btn btn-outline-secondary" <>
if(@filters.only_approved?, do: " active", else: ""),
to: update_filters_path(@conn, Filters.toggle_only_approved(@filters))
)}

{link("⬒ #{if(Filters.calendar?(@filters), do: "list", else: "calendar")} view",
class: "ml-auto btn btn-outline-secondary",
to: update_filters_path(@conn, Filters.toggle_view(@filters))
Expand Down
Loading
Loading