Skip to content

Commit

Permalink
Trip Planner Release (#2341)
Browse files Browse the repository at this point in the history
  • Loading branch information
anthonyshull authored Jan 29, 2025
1 parent abbe797 commit 9b28737
Show file tree
Hide file tree
Showing 26 changed files with 747 additions and 1,639 deletions.
11 changes: 8 additions & 3 deletions assets/css/_autocomplete-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,6 @@

.aa-InputWrapperSuffix {
order: 3;
width: calc(var(--aa-spacing) + var(--aa-icon-size) - 1px);
}

// hide default search magnifying glass icon
Expand All @@ -252,6 +251,12 @@
content: "B"
}

.aa-DetachedOverlay .aa-InputWrapper {
padding-left: calc(var(--aa-spacing));
.aa-DetachedOverlay {
.aa-InputWrapper {
padding-left: calc(var(--aa-spacing));
}

.aa-InputWrapperSuffix {
width: calc(var(--aa-spacing) + var(--aa-icon-size) - 1px);
}
}
24 changes: 0 additions & 24 deletions cypress/e2e/smoke.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,30 +152,6 @@ describe("passes smoke test", () => {
}
});

it("trip planner", () => {
cy.visit("/trip-planner");

// reverses the inputs
cy.get("#from").type("A");
cy.get("#to").type("B");
cy.get("#trip-plan-reverse-control").click();
cy.get("#from").should("have.value", "B");
cy.get("#to").should("have.value", "A");

// opens the date picker
cy.contains("#trip-plan-datepicker").should("not.exist");
cy.get('label[for="arrive"]').click();
cy.get("#trip-plan-datepicker");

// shortcut /from/ - marker A prepopulated
cy.visit("/trip-planner/from/North+Station");
cy.get('img.leaflet-marker-icon[src="/icon-svg/icon-map-pin-a.svg"]');

// shortcut /to/ - marker B prepopulated
cy.visit("/trip-planner/to/North+Station");
cy.get('img.leaflet-marker-icon[src="/icon-svg/icon-map-pin-b.svg"]');
});

it("alerts page", () => {
cy.visit("/alerts");
cy.contains(".m-alerts__mode-buttons a", "Bus").click();
Expand Down
2 changes: 1 addition & 1 deletion integration/scenarios/plan-a-trip-from-homepage.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ exports.scenario = async ({ page, baseURL }) => {

await expect
.poll(async () =>
page.locator("div.m-trip-plan-results__itinerary").count(),
page.locator("section#trip-planner-results").count(),
)
.toBeGreaterThan(0);
};
24 changes: 14 additions & 10 deletions integration/scenarios/plan-a-trip.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,33 @@ const { expect } = require("@playwright/test");
exports.scenario = async ({ page, baseURL }) => {
await page.goto(`${baseURL}/trip-planner`);

await page.locator("input#from").pressSequentially("North Station");
await expect(
page.getByRole("heading", { name: "Trip Planner" }),
).toBeVisible();

await page.locator("#trip-planner-input-form--from input[type='search']").pressSequentially("North Station");
await page.waitForSelector(
"div#from-autocomplete-results span.c-search-bar__-dropdown-menu",
"ul.aa-List",
);
await page.keyboard.press("ArrowDown");
await page.keyboard.press("Enter");

await page.locator("input#to").pressSequentially("South Station");
// The A location pin.
await page.waitForSelector("#mbta-metro-pin-0");

await page.locator("#trip-planner-input-form--to input[type='search']").pressSequentially("South Station");
await page.waitForSelector(
"div#to-autocomplete-results span.c-search-bar__-dropdown-menu",
"ul.aa-List",
);
await page.keyboard.press("ArrowDown");
await page.keyboard.press("Enter");

await page.locator("button#trip-plan__submit").click();

await expect(
page.getByRole("heading", { name: "Trip Planner" }),
).toBeVisible();
// The B location pin.
await page.waitForSelector("#mbta-metro-pin-1");

await expect
.poll(async () =>
page.locator("div.m-trip-plan-results__itinerary").count(),
page.locator("section#trip-planner-results").count(),
)
.toBeGreaterThan(0);
};
116 changes: 83 additions & 33 deletions lib/dotcom/trip_plan/anti_corruption_layer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,72 @@ defmodule Dotcom.TripPlan.AntiCorruptionLayer do
We ignore datetime_type and datetime and allow those to be set to 'now' and the current time respectively.
"""

@location_service Application.compile_env!(:dotcom, :location_service)
@default_modes Dotcom.TripPlan.InputForm.initial_modes()
@default_params %{
"datetime_type" => "now",
"modes" => @default_modes,
"wheelchair" => "false"
}

@doc """
Given a query for the old trip planner /to or /from actions, replicate the old
behavior by searching for a location and using the first result. Convert this
to the new trip planner form values.
Given the params from the old trip planner, convert them to the new trip planner form values.
If no plan is given, then we default to empty form values.
"""
def convert_old_action(action) do
with [key] when key in [:from, :to] <- Map.keys(action),
query when is_binary(query) <- Map.get(action, key),
{:ok, [%LocationService.Address{} = geocoded | _]} <- @location_service.geocode(query) do
%{
"plan" => %{
"#{key}_latitude" => geocoded.latitude,
"#{key}_longitude" => geocoded.longitude,
"#{key}" => geocoded.formatted
}
}
def convert_old_params(%{"plan" => params}) do
Map.merge(@default_params, copy_params(params))
end

def convert_old_params(_), do: convert_old_params(%{"plan" => %{}})

# Decode a string into form values.
# Add in defaults if they were omitted.
def decode(string) do
with {:ok, binary} <- Base.url_decode64(string),
{:ok, params} <- :msgpack.unpack(binary) do
params
|> decode_datetime()
|> add_defaults()
else
_ ->
%{"plan" => %{}}
_ -> @default_params
end
end

@doc """
Given the params from the old trip planner, convert them to the new trip planner form values.
def default_params, do: @default_params

If no plan is given, then we default to empty form values.
"""
def convert_old_params(%{"plan" => params}) do
# Encode form values into a single string.
# Strip out defaults so we don't waste space encoding them.
def encode(params) do
params
|> strip_defaults()
|> encode_datetime()
|> :msgpack.pack()
|> Base.url_encode64()
end

# Make sure that the params have all of the defaults set.
defp add_defaults(params) do
Map.merge(@default_params, params)
end

# All other modes but commuter rail are just the mode in uppercase.
defp convert_mode("commuter_rail"), do: "RAIL"
defp convert_mode(mode), do: String.upcase(mode)

# When modes are given, we set all non-given modes to false.
defp convert_modes(modes) when is_map(modes) do
default_modes = for {k, _} <- @default_modes, into: %{}, do: {k, "false"}

Enum.reduce(modes, default_modes, fn {key, value}, acc ->
Map.put(acc, convert_mode(key), value)
end)
end

# When no modes are given, we use the initial modes--all modes are true.
defp convert_modes(_), do: @default_modes

# Copy the old params into the new param structure.
defp copy_params(params) do
%{
"from" => %{
"latitude" => Map.get(params, "from_latitude"),
Expand All @@ -56,20 +92,34 @@ defmodule Dotcom.TripPlan.AntiCorruptionLayer do
}
end

def convert_old_params(_), do: convert_old_params(%{"plan" => %{}})
defp decode_datetime(%{"datetime" => datetime} = params) do
case DateTime.from_iso8601(datetime) do
{:ok, datetime, _} -> Map.put(params, "datetime", datetime)
_ -> params
end
end

defp convert_modes(modes) when is_map(modes) do
default_modes =
for {k, _} <- Dotcom.TripPlan.InputForm.initial_modes(), into: %{}, do: {k, "false"}
defp decode_datetime(params), do: params

modes
|> Enum.reduce(default_modes, fn {key, value}, acc ->
Map.put(acc, convert_mode(key), value)
end)
# Encode the datetime into an ISO8601 string.
defp encode_datetime(params) do
case params |> Map.get("datetime") do
%DateTime{} = datetime -> Map.put(params, "datetime", DateTime.to_iso8601(datetime))
_ -> params
end
end

defp convert_modes(_), do: Dotcom.TripPlan.InputForm.initial_modes()
# If the params have a key set and it's just the default value, then remove it.
defp strip_default(params, key) do
if Map.has_key?(params, key) && Map.get(params, key) == Map.get(@default_params, key) do
Map.delete(params, key)
else
params
end
end

defp convert_mode("commuter_rail"), do: "RAIL"
defp convert_mode(mode), do: String.upcase(mode)
# Strip default params so we don't waste space encoding them.
defp strip_defaults(params) do
Enum.reduce(@default_params, params, fn {key, _}, acc -> strip_default(acc, key) end)
end
end
2 changes: 1 addition & 1 deletion lib/dotcom/trip_plan/input_form.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ defmodule Dotcom.TripPlan.InputForm do
embeds_one(:modes, __MODULE__.Modes)
field(:datetime_type, :string)
field(:datetime, :naive_datetime)
field(:wheelchair, :boolean, default: true)
field(:wheelchair, :boolean)
end

def initial_modes do
Expand Down
34 changes: 23 additions & 11 deletions lib/dotcom/trip_plan/parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ defmodule Dotcom.TripPlan.Parser do
MBTA system.
"""

require Logger

alias Dotcom.TripPlan.{FarePasses, Itinerary, Leg, NamedPosition, PersonalDetail, TransitDetail}
alias OpenTripPlannerClient.Schema

Expand Down Expand Up @@ -164,20 +166,30 @@ defmodule Dotcom.TripPlan.Parser do
defp route_color("Logan Express", "DV", _), do: "704c9f"
defp route_color(_, _, color), do: color

defp build_stop(stop, attributes \\ %{}) do
case stop.gtfs_id do
"mbta-ma-us:" <> gtfs_id ->
@stops_repo.get(gtfs_id)
|> struct(attributes)

_ ->
stop
|> Map.from_struct()
|> Map.merge(attributes)
|> then(&struct(Stops.Stop, &1))
defp build_stop(stop, attributes \\ %{})

defp build_stop(%Schema.Stop{gtfs_id: "mbta-ma-us:" <> gtfs_id} = schema_stop, attributes) do
stop = @stops_repo.get(gtfs_id)

if stop do
stop
|> Map.merge(attributes)
else
Logger.notice("dotcom.trip_plan.parser unknown_stop=mbta-ma-us:#{gtfs_id}")

schema_stop
|> Map.put(:gtfs_id, gtfs_id)
|> build_stop(attributes)
end
end

defp build_stop(stop, attributes) do
stop
|> Map.from_struct()
|> Map.merge(attributes)
|> then(&struct(Stops.Stop, &1))
end

defp id_from_gtfs(gtfs_id) do
case String.split(gtfs_id, ":") do
[_, id] -> id
Expand Down
5 changes: 4 additions & 1 deletion lib/dotcom_web/components/trip_planner/results.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ defmodule DotcomWeb.Components.TripPlanner.Results do
<div
:if={Enum.count(@results.itinerary_groups) > 0 && @results.itinerary_group_selection}
class="h-min w-full mb-3.5"
data-test={"results:itinerary_group:selected:#{@results.itinerary_group_selection}"}
>
<button type="button" phx-click="reset_itinerary_group" class="btn-link">
<span class="flex flex-row items-center">
Expand Down Expand Up @@ -55,6 +56,7 @@ defmodule DotcomWeb.Components.TripPlanner.Results do
class="border border-solid border-gray-lighter p-4"
phx-click="select_itinerary_group"
phx-value-index={index}
data-test={"results:itinerary_group:#{index}"}
>
<div
:if={group.summary.tag}
Expand Down Expand Up @@ -107,7 +109,7 @@ defmodule DotcomWeb.Components.TripPlanner.Results do
}

~H"""
<div>
<div data-test={"itinerary_detail:selected:#{@itinerary_selection}"}>
<.itinerary_summary summary={@summary} />
<div :if={Enum.count(@all_times) > 1}>
<hr class="border-gray-lighter" />
Expand All @@ -121,6 +123,7 @@ defmodule DotcomWeb.Components.TripPlanner.Results do
variant="secondary"
phx-click="select_itinerary"
phx-value-index={index}
data-test={"itinerary_detail:#{index}"}
>
{Util.kitchen_downcase_time(time)}
</.button>
Expand Down
4 changes: 3 additions & 1 deletion lib/dotcom_web/components/trip_planner/results_summary.ex
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ defmodule DotcomWeb.Components.TripPlanner.ResultsSummary do

defp results_feedback(assigns) do
~H"""
<.feedback kind={:error}>{@results.error}</.feedback>
<.feedback kind={:error}>
<span data-test="results-summary:error">{@results.error}</span>
</.feedback>
"""
end

Expand Down
2 changes: 2 additions & 0 deletions lib/dotcom_web/components/trip_planner/transit_leg.ex
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ defmodule DotcomWeb.Components.TripPlanner.TransitLeg do
"""
end

defp stop_url(_, %Stop{} = stop) when is_nil(stop.id), do: nil

defp stop_url(%Route{external_agency_name: nil}, %Stop{} = stop) do
~p"/stops/#{stop}"
end
Expand Down
Loading

0 comments on commit 9b28737

Please sign in to comment.