From 2800e63206e2477946dad27dc7767860c5000b39 Mon Sep 17 00:00:00 2001 From: Cristen Jones Date: Thu, 1 Jan 2026 16:20:16 -0500 Subject: [PATCH 01/11] upgrade erlang and elixir --- .tool-versions | 4 ++-- deploy/dotcom/dev/Dockerfile | 2 +- deploy/dotcom/prod/Dockerfile | 2 +- lib/alerts/alert.ex | 16 ++++++++-------- lib/dotcom/alerts/subway/disruptions.ex | 4 ++-- .../trip_plan/open_street_map_reconciler.ex | 4 ++-- .../controllers/schedule/line/helpers.ex | 4 ++-- lib/location_service/address.ex | 4 ++-- lib/predictions/repo.ex | 4 ++-- lib/stops/route_stop.ex | 4 ++-- mix.exs | 19 +++++++++++++------ .../controllers/schedule/trip_info_test.exs | 16 ++++++++-------- .../plugs/canonical_hostname_test.exs | 8 ++++---- test/predicted_schedule_test.exs | 5 +---- test/support/conn_case.ex | 6 ++---- 15 files changed, 52 insertions(+), 50 deletions(-) diff --git a/.tool-versions b/.tool-versions index ddc820de4a..8401caa2e2 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,3 +1,3 @@ nodejs 24.5.0 -erlang 28.0.2 -elixir 1.18.4-otp-28 +erlang 28.2 +elixir 1.19.4-otp-28 diff --git a/deploy/dotcom/dev/Dockerfile b/deploy/dotcom/dev/Dockerfile index 12f76a5bc5..8c8c851e73 100644 --- a/deploy/dotcom/dev/Dockerfile +++ b/deploy/dotcom/dev/Dockerfile @@ -1,4 +1,4 @@ -FROM hexpm/elixir:1.18.4-erlang-28.0.2-debian-trixie-20250811-slim +FROM hexpm/elixir:1.19.4-erlang-28.2-debian-trixie-20250811-slim RUN apt-get update && apt-get install -y curl git make build-essential inotify-tools RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - diff --git a/deploy/dotcom/prod/Dockerfile b/deploy/dotcom/prod/Dockerfile index 549d2ac22a..6d7b382571 100644 --- a/deploy/dotcom/prod/Dockerfile +++ b/deploy/dotcom/prod/Dockerfile @@ -1,4 +1,4 @@ -FROM hexpm/elixir:1.18.4-erlang-28.0.2-alpine-3.22.1 AS builder +FROM hexpm/elixir:1.19.4-erlang-28.2-alpine-3.22.1 AS builder # ENVS AND ARGS ENV LANG="C.UTF-8" MIX_ENV="prod" diff --git a/lib/alerts/alert.ex b/lib/alerts/alert.ex index a44cc11b22..c7cfcbd239 100644 --- a/lib/alerts/alert.ex +++ b/lib/alerts/alert.ex @@ -180,8 +180,8 @@ defmodule Alerts.Alert do defp build_struct(keywords), do: struct!(__MODULE__, keywords) @spec ensure_entity_set(map) :: t() - defp ensure_entity_set(alert) do - %__MODULE__{alert | informed_entity: InformedEntitySet.new(alert.informed_entity)} + defp ensure_entity_set(%__MODULE__{} = alert) do + %{alert | informed_entity: InformedEntitySet.new(alert.informed_entity)} end @spec all_types :: [effect] @@ -333,13 +333,13 @@ defmodule Alerts.Alert do end defimpl Poison.Encoder, for: Alerts.Alert do - def encode(%Alerts.Alert{active_period: active_period_pairs} = alert, options) do - active_period = Enum.map(active_period_pairs, &alert_active_period/1) + def encode(%Alerts.Alert{} = alert, options) do + alert = + Map.update!(alert, :active_period, fn active_period_pairs -> + Enum.map(active_period_pairs, &alert_active_period/1) + end) - Poison.Encoder.Map.encode( - %{alert | active_period: active_period}, - options - ) + Poison.Encoder.Map.encode(alert, options) end @spec alert_active_period(Alerts.Alert.period_pair()) :: [nil | binary] diff --git a/lib/dotcom/alerts/subway/disruptions.ex b/lib/dotcom/alerts/subway/disruptions.ex index 0d664e5936..1bdd37d5cc 100644 --- a/lib/dotcom/alerts/subway/disruptions.ex +++ b/lib/dotcom/alerts/subway/disruptions.ex @@ -60,11 +60,11 @@ defmodule Dotcom.Alerts.Subway.Disruptions do # result is then two alerts, both equivalent to the alert passed in, # except that one has a single active period of {Sun, Tue}, and the # other has a single active period of {Thu, Fri}. - defp split_by_discontiguous_active_periods(alert) do + defp split_by_discontiguous_active_periods(%Alerts.Alert{} = alert) do alert.active_period |> combine_contiguous_active_periods() |> Enum.map(fn active_period -> - %Alerts.Alert{alert | active_period: [active_period]} + %{alert | active_period: [active_period]} end) end diff --git a/lib/dotcom/trip_plan/open_street_map_reconciler.ex b/lib/dotcom/trip_plan/open_street_map_reconciler.ex index 291955d010..813362b3e3 100644 --- a/lib/dotcom/trip_plan/open_street_map_reconciler.ex +++ b/lib/dotcom/trip_plan/open_street_map_reconciler.ex @@ -58,8 +58,8 @@ defmodule Dotcom.TripPlan.OpenStreetMapReconciler do """ @spec reconcile(InputForm.t()) :: InputForm.t() @spec reconcile(arg) :: arg when arg: var - def reconcile(%{from: from, to: to} = form) do - %InputForm{form | from: reconcile_location(from), to: reconcile_location(to)} + def reconcile(%InputForm{from: from, to: to} = form) do + %{form | from: reconcile_location(from), to: reconcile_location(to)} end def reconcile(form), do: form diff --git a/lib/dotcom_web/controllers/schedule/line/helpers.ex b/lib/dotcom_web/controllers/schedule/line/helpers.ex index 8d1d528783..d7914b11c5 100644 --- a/lib/dotcom_web/controllers/schedule/line/helpers.ex +++ b/lib/dotcom_web/controllers/schedule/line/helpers.ex @@ -293,9 +293,9 @@ defmodule DotcomWeb.ScheduleController.Line.Helpers do @spec do_nil_out_shared_stop_branches([RouteStop.t()], MapSet.t(Stop.id_t())) :: [RouteStop.t()] defp do_nil_out_shared_stop_branches(route_pattern_group, shared_ids) do - Enum.map(route_pattern_group, fn route_stop -> + Enum.map(route_pattern_group, fn %RouteStop{} = route_stop -> if MapSet.member?(shared_ids, route_stop.id) do - %RouteStop{ + %{ route_stop | branch: nil } diff --git a/lib/location_service/address.ex b/lib/location_service/address.ex index f63deb6b2e..113f26f10e 100644 --- a/lib/location_service/address.ex +++ b/lib/location_service/address.ex @@ -46,7 +46,7 @@ defmodule LocationService.Address do @spec parse_label(String.t(), %__MODULE__{}) :: %__MODULE__{} defp parse_label("", address), do: address - defp parse_label(label, address) do + defp parse_label(label, %__MODULE__{} = address) do %AddressUS.Address{ street: %AddressUS.Street{ name: street_name, @@ -62,7 +62,7 @@ defmodule LocationService.Address do |> String.trim() |> with_place_name(label) - %__MODULE__{address | street_address: street_address, municipality: city, state: state} + %{address | street_address: street_address, municipality: city, state: state} rescue _ -> # Remove first section of label, try again! diff --git a/lib/predictions/repo.ex b/lib/predictions/repo.ex index ea70ce8e35..4d8aa5d407 100644 --- a/lib/predictions/repo.ex +++ b/lib/predictions/repo.ex @@ -134,8 +134,8 @@ defmodule Predictions.Repo do defp has_departure_time?( {_id, _trip_id, _stop_id, _route_id, _direction_id, _arrival, departure, _time, - _stop_sequence, _schedule_relationship, _track, _status, _departing?, - _vehicle_id} = _prediction + _stop_sequence, _schedule_relationship, _track, _status, _departing?, _vehicle_id} = + _prediction ) do departure != nil end diff --git a/lib/stops/route_stop.ex b/lib/stops/route_stop.ex index 13f5fe98ce..44fad736bc 100644 --- a/lib/stops/route_stop.ex +++ b/lib/stops/route_stop.ex @@ -349,8 +349,8 @@ defmodule Stops.RouteStop do {first_last, first_body} = List.pop_at(first, -1) first_body ++ - [%RouteStop{first_last | terminus?: false}] ++ - (second |> tl() |> Enum.map(&%RouteStop{&1 | branch: branch(first)})) + [%{first_last | terminus?: false}] ++ + (second |> tl() |> Enum.map(&%{&1 | branch: branch(first)})) end @spec branch([RouteStop.t()]) :: RouteStop.branch_name_t() diff --git a/mix.exs b/mix.exs index 936352da75..81a582104b 100644 --- a/mix.exs +++ b/mix.exs @@ -15,12 +15,6 @@ defmodule DotCom.Mixfile do # used by `mix app.start` to start the application and children in permanent mode, which guarantees the node will shut down if the application terminates (typically because its root supervisor has terminated). start_permanent: Mix.env() == :prod, test_coverage: [tool: ExCoveralls], - preferred_cli_env: [ - coveralls: :test, - "coveralls.html": :test, - "gettext.extract": :prod, - "gettext.translate": :prod - ], dialyzer: [ plt_add_apps: [:mix, :phoenix_live_reload, :mbta_metro], flags: [:unmatched_returns] @@ -36,6 +30,17 @@ defmodule DotCom.Mixfile do ] end + def cli do + [ + preferred_envs: [ + coveralls: :test, + "coveralls.html": :test, + "gettext.extract": :prod, + "gettext.translate": :prod + ] + ] + end + # Specifies which paths to compile per environment. defp elixirc_paths(:dev), do: [ @@ -53,6 +58,8 @@ defmodule DotCom.Mixfile do extra_apps = [ :logger, :runtime_tools, + :wx, + :observer, :os_mon ] diff --git a/test/dotcom_web/controllers/schedule/trip_info_test.exs b/test/dotcom_web/controllers/schedule/trip_info_test.exs index bfa03fb95d..e637d0eb60 100644 --- a/test/dotcom_web/controllers/schedule/trip_info_test.exs +++ b/test/dotcom_web/controllers/schedule/trip_info_test.exs @@ -157,7 +157,7 @@ defmodule DotcomWeb.ScheduleController.TripInfoTest do time: List.last(@schedules).time } ]) - |> Enum.map(&%Schedule{&1 | trip: %Trip{id: "long_trip"}}) + |> Enum.map(&%{&1 | trip: %Trip{id: "long_trip"}}) end defp trip_fn("not_in_schedule", date: @date) do @@ -218,7 +218,7 @@ defmodule DotcomWeb.ScheduleController.TripInfoTest do test "assigns trip_info when origin/destination are selected", %{conn: conn} do expect(Predictions.Repo.Mock, :all, fn trip: trip_id -> - Enum.map(@predictions, &%Prediction{&1 | trip: %Trip{id: trip_id}}) + Enum.map(@predictions, &%{&1 | trip: %Trip{id: trip_id}}) end) expected_stops = ["after_first", "1", "2", "3", "new_last"] @@ -240,7 +240,7 @@ defmodule DotcomWeb.ScheduleController.TripInfoTest do test "returns nil if we can't generate a trip info", %{conn: conn} do expect(Predictions.Repo.Mock, :all, fn trip: trip_id -> - Enum.map(@predictions, &%Prediction{&1 | trip: %Trip{id: trip_id}}) + Enum.map(@predictions, &%{&1 | trip: %Trip{id: trip_id}}) end) conn = @@ -258,7 +258,7 @@ defmodule DotcomWeb.ScheduleController.TripInfoTest do test "does not redirect if we didn't have a trip already", %{conn: conn} do expect(Predictions.Repo.Mock, :all, fn trip: trip_id -> - Enum.map(@predictions, &%Prediction{&1 | trip: %Trip{id: trip_id}}) + Enum.map(@predictions, &%{&1 | trip: %Trip{id: trip_id}}) end) conn = conn_builder(conn, @schedules, origin: "fake", destination: "fake") @@ -296,7 +296,7 @@ defmodule DotcomWeb.ScheduleController.TripInfoTest do test "Trip predictions are fetched if date is service day", %{conn: conn} do expect(Predictions.Repo.Mock, :all, fn trip: trip_id -> - Enum.map(@predictions, &%Prediction{&1 | trip: %Trip{id: trip_id}}) + Enum.map(@predictions, &%{&1 | trip: %Trip{id: trip_id}}) end) conn = @@ -348,7 +348,7 @@ defmodule DotcomWeb.ScheduleController.TripInfoTest do test "Default Trip id is taken from journeys if one is not provided", %{conn: conn} do expect(Predictions.Repo.Mock, :all, fn trip: trip_id -> - Enum.map(@predictions, &%Prediction{&1 | trip: %Trip{id: trip_id}}) + Enum.map(@predictions, &%{&1 | trip: %Trip{id: trip_id}}) end) schedules = [ @@ -388,7 +388,7 @@ defmodule DotcomWeb.ScheduleController.TripInfoTest do test "does assign trips for the subway if the date is today", %{conn: conn} do expect(Predictions.Repo.Mock, :all, fn trip: trip_id -> - Enum.map(@predictions, &%Prediction{&1 | trip: %Trip{id: trip_id}}) + Enum.map(@predictions, &%{&1 | trip: %Trip{id: trip_id}}) end) schedules = [ @@ -561,7 +561,7 @@ defmodule DotcomWeb.ScheduleController.TripInfoTest do describe "test that wollaston station is properly inserted when expected" do test "Does not add Wollaston to non Red line routes", %{conn: conn} do expect(Predictions.Repo.Mock, :all, fn trip: "non-red-trip" -> - Enum.map(@non_red_predictions, &%Prediction{&1 | trip: %Trip{id: "non-red-trip"}}) + Enum.map(@non_red_predictions, &%{&1 | trip: %Trip{id: "non-red-trip"}}) end) init = init(trip_fn: &trip_fn/2, vehicle_fn: &vehicle_fn/1) diff --git a/test/dotcom_web/plugs/canonical_hostname_test.exs b/test/dotcom_web/plugs/canonical_hostname_test.exs index 2a05ea8113..433291f3c2 100644 --- a/test/dotcom_web/plugs/canonical_hostname_test.exs +++ b/test/dotcom_web/plugs/canonical_hostname_test.exs @@ -7,26 +7,26 @@ defmodule DotcomWeb.Plugs.CanonicalHostnameTest do describe "call/2" do test "with a local IP address, does nothing" do # Class A - conn = %Plug.Conn{default_conn() | host: "10.127.127.127"} + conn = %{default_conn() | host: "10.127.127.127"} assert conn.status != 301 conn = CanonicalHostname.call(conn, nil) assert conn.status != 301 # Class B - conn = %Plug.Conn{default_conn() | host: "172.24.127.127"} + conn = %{default_conn() | host: "172.24.127.127"} assert conn.status != 301 conn = CanonicalHostname.call(conn, nil) assert conn.status != 301 # Class C - conn = %Plug.Conn{default_conn() | host: "192.168.127.127"} + conn = %{default_conn() | host: "192.168.127.127"} assert conn.status != 301 conn = CanonicalHostname.call(conn, nil) assert conn.status != 301 end test "when the hostname doesn't match the canonical hostname, redirects" do - conn = %Plug.Conn{default_conn() | host: "example.com"} + conn = %{default_conn() | host: "example.com"} assert conn.status != 301 conn = CanonicalHostname.call(conn, nil) diff --git a/test/predicted_schedule_test.exs b/test/predicted_schedule_test.exs index b87c11170f..c76da632c7 100644 --- a/test/predicted_schedule_test.exs +++ b/test/predicted_schedule_test.exs @@ -318,10 +318,7 @@ defmodule PredictedScheduleTest do modified_trip_schedules = @trip_schedules |> Enum.map(fn schedule -> - %Schedule{ - schedule - | stop: nil - } + %{schedule | stop: nil} end) assert group(@trip_predictions, modified_trip_schedules) diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index 24dbbb8798..1312a555dd 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -16,10 +16,8 @@ defmodule DotcomWeb.ConnCase do use ExUnit.CaseTemplate def default_conn do - %Plug.Conn{ - Phoenix.ConnTest.build_conn() - | host: "localhost" - } + Phoenix.ConnTest.build_conn() + |> Map.put(:host, "localhost") end using do From 57fdafd11a2e52b01f56ed093b76a784926a2f2a Mon Sep 17 00:00:00 2001 From: Cristen Jones Date: Thu, 1 Jan 2026 16:20:24 -0500 Subject: [PATCH 02/11] upgrade phoenix --- lib/dotcom_web/controllers/cache_controller.ex | 2 +- mix.exs | 2 +- mix.lock | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/dotcom_web/controllers/cache_controller.ex b/lib/dotcom_web/controllers/cache_controller.ex index 16cb26ce2b..fc510d3be0 100644 --- a/lib/dotcom_web/controllers/cache_controller.ex +++ b/lib/dotcom_web/controllers/cache_controller.ex @@ -180,7 +180,7 @@ defmodule DotcomWeb.CacheController do } end) - assigns = assign(assigns, links: links) + assigns = Phoenix.Controller.assign(assigns, links: links) ~H"""