Skip to content

Commit

Permalink
feat: Add search functionality to Disruptions v2 homepage
Browse files Browse the repository at this point in the history
  • Loading branch information
Whoops committed Feb 28, 2025
1 parent 909a50c commit 08fb3d8
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 3 deletions.
8 changes: 8 additions & 0 deletions lib/arrow_web/controllers/disruption_v2_controller/filters.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,16 @@ defmodule ArrowWeb.DisruptionV2Controller.Filters do
def from_params(params) when is_map(params) do
view_mod = if(params["view"] == "calendar", do: Calendar, else: Table)

search =
if params["search"] in [nil, ""] or String.trim(params["search"]) == "",
do: nil,
else: params["search"]

%__MODULE__{
kinds:
params |> Map.get("kinds", []) |> Enum.map(&String.to_existing_atom/1) |> MapSet.new(),
only_approved?: not is_nil(params["only_approved"]),
search: search,
view: view_mod.from_params(params)
}
end
Expand Down Expand Up @@ -78,6 +84,7 @@ defmodule ArrowWeb.DisruptionV2Controller.Filters do
def to_params(%__MODULE__{
kinds: kinds,
only_approved?: only_approved?,
search: search,
view: %{__struct__: view_mod} = view
}) do
%{}
Expand All @@ -88,6 +95,7 @@ defmodule ArrowWeb.DisruptionV2Controller.Filters do
kinds |> MapSet.to_list() |> Enum.map(&to_string/1) |> Enum.sort()
)
|> put_if(only_approved?, "only_approved", "true")
|> put_if(not is_nil(search), "search", search)
|> Map.merge(view_mod.to_params(view))
end

Expand Down
36 changes: 35 additions & 1 deletion lib/arrow_web/controllers/disruption_v2_controller/index.ex
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ defmodule ArrowWeb.DisruptionV2Controller.Index do
Enum.filter(
disruptions,
&(apply_kinds_filter(&1, filters) and apply_only_approved_filter(&1, filters) and
apply_past_filter(&1, filters))
apply_past_filter(&1, filters) and apply_search_filter(&1, filters))
)
end

Expand Down Expand Up @@ -59,4 +59,38 @@ defmodule ArrowWeb.DisruptionV2Controller.Index do
end

defp apply_past_filter(_disruption, _filter), do: true

defp apply_search_filter(_disruption, %Filters{search: nil}), do: true

defp apply_search_filter(%DisruptionV2{} = disruption, %Filters{search: search}) do
title_contains?(disruption, search) or
limits_contains?(disruption, search) or
replacement_services_contains?(disruption, search)
end

defp title_contains?(disruption, search) do
string_contains?(disruption.title, search)
end

defp limits_contains?(%DisruptionV2{limits: limits}, search) do
Enum.any?(limits, fn limit ->
string_contains?(limit.start_stop.name, search) ||
string_contains?(limit.end_stop.name, search)
end)
end

defp replacement_services_contains?(
%DisruptionV2{replacement_services: replacement_services},
search
) do
Enum.any?(replacement_services, fn replacement_service ->
string_contains?(replacement_service.shuttle.shuttle_name, search)
end)
end

defp string_contains?(string, search) do
string
|> String.downcase()
|> String.contains?(String.downcase(search))
end
end
17 changes: 16 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 @@ -5,7 +5,22 @@
/>
<%= form_tag(Controller.current_path(@conn), method: "get", class: "col-3") do %>
<div class="input-group">
<input type="text" name="search" class="form-control" placeholder="search" />
<%= for {name, value} <- Filters.to_params(@filters) |> Map.delete("search") do %>
<%= if is_list(value) do %>
<%= for v <- value do %>
<input type="hidden" name={"#{name}[]"} value={v} />
<% end %>
<% else %>
<input type="hidden" name={name} value={value} />
<% end %>
<% end %>
<input
type="text"
name="search"
class="form-control"
placeholder="search"
value={@filters.search}
/>

<div class="input-group-append">
<button type="submit" class="btn btn-outline-secondary" title="search">🔎</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ defmodule ArrowWeb.DisruptionV2Controller.FiltersTest do
test "functions as to_params/1 but flattens lists into query-param format" do
filters = %Filters{search: "test", kinds: set(~w(red_line blue_line)a)}

expected = [{"kinds[]", "blue_line"}, {"kinds[]", "red_line"}]
expected = [{"kinds[]", "blue_line"}, {"kinds[]", "red_line"}, {"search", "test"}]
assert Filters.to_flat_params(filters) == expected
end
end
Expand Down
105 changes: 105 additions & 0 deletions test/arrow_web/controllers/disruption_v2_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,110 @@ defmodule ArrowWeb.DisruptionV2ControllerTest do

refute resp =~ "Test disruption"
end

@tag :authenticated
test "lists disruptions that match a search query on title", %{conn: conn} do
insert(:disruption_v2, title: "Test disruption Alpha")
insert(:disruption_v2, title: "Test disruption Beta")

resp = conn |> get(~p"/disruptionsv2?search=Alpha") |> html_response(200)

assert resp =~ "Test disruption Alpha"
refute resp =~ "Test disruption Beta"
end

@tag :authenticated
test "lists disruptions that match a search query on stop names", %{conn: conn} do
route = insert(:gtfs_route)
start_stop = insert(:gtfs_stop, name: "UniqueStopName Station")
end_stop = insert(:gtfs_stop, name: "Regular Station")

insert(:limit,
disruption: build(:disruption_v2, title: "Matching Disruption"),
route: route,
start_stop: start_stop,
end_stop: end_stop
)

insert(:limit,
disruption: build(:disruption_v2, title: "Nonmatching Disruption"),
route: route
)

resp = conn |> get(~p"/disruptionsv2?search=UniqueStopName") |> html_response(200)

assert resp =~ "Matching Disruption"
refute resp =~ "Nonmatching Disruption"
end

@tag :authenticated
test "lists disruptions that match a search query on shuttle names", %{conn: conn} do
route = insert(:gtfs_route)

disruption_with_shuttle = insert(:disruption_v2, title: "Matching Disruption")
disruption_without_shuttle = insert(:disruption_v2, title: "Nonmatching Disruption")

matching_shuttle =
Arrow.ShuttlesFixtures.shuttle_fixture(
%{
shuttle_name: "Right Bus",
status: :active
},
true,
true
)

Arrow.DisruptionsFixtures.replacement_service_fixture(%{
disruption_id: disruption_with_shuttle.id,
shuttle_id: matching_shuttle.id,
start_date: Date.utc_today(),
end_date: Date.add(Date.utc_today(), 30)
})

wrong_shuttle =
Arrow.ShuttlesFixtures.shuttle_fixture(
%{
shuttle_name: "Wrong Bus",
status: :active
},
true,
true
)

Arrow.DisruptionsFixtures.replacement_service_fixture(%{
disruption_id: disruption_without_shuttle.id,
shuttle_id: wrong_shuttle.id,
start_date: Date.utc_today(),
end_date: Date.add(Date.utc_today(), 30)
})

insert(:limit, disruption: disruption_with_shuttle, route: route)
insert(:limit, disruption: disruption_without_shuttle, route: route)

resp = conn |> get(~p"/disruptionsv2?search=Right") |> html_response(200)

assert resp =~ "Matching Disruption"
refute resp =~ "Nonmatching Disruption"
end

@tag :authenticated
test "search is case-insensitive", %{conn: conn} do
insert(:disruption_v2, title: "Case Sensitive Test")

resp = conn |> get(~p"/disruptionsv2?search=case sensitive") |> html_response(200)

assert resp =~ "Case Sensitive Test"
end

@tag :authenticated
test "empty search string returns all disruptions", %{conn: conn} do
insert(:disruption_v2, title: "Test disruption Alpha")
insert(:disruption_v2, title: "Test disruption Beta")

resp = conn |> get(~p"/disruptionsv2?search=") |> html_response(200)

assert resp =~ "Alpha"
assert resp =~ "Beta"
end
end
end

0 comments on commit 08fb3d8

Please sign in to comment.