From 2e9f28a0470b2e24d609f13d2cbb9293daf1ae25 Mon Sep 17 00:00:00 2001 From: Luc Vachon Date: Tue, 23 Jun 2026 11:36:21 -0400 Subject: [PATCH 01/10] Added filter to remove alerts older than five weeks from the line diagram page. --- lib/dotcom_web/templates/schedule/_line.html.heex | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/dotcom_web/templates/schedule/_line.html.heex b/lib/dotcom_web/templates/schedule/_line.html.heex index 5d1491f46d..520780d18a 100644 --- a/lib/dotcom_web/templates/schedule/_line.html.heex +++ b/lib/dotcom_web/templates/schedule/_line.html.heex @@ -31,7 +31,11 @@
{DotcomWeb.AlertView.group( - alerts: @alerts, + alerts: + @alerts + |> Enum.filter(fn %{created_at: created_at} -> + DateTime.after?(created_at, DateTime.add(@date_time, -5 * 7, :day)) + end), route: @route, date_time: @date_time, priority_filter: :high From db38f5856238657343a09f80692c9be22b3d27a7 Mon Sep 17 00:00:00 2001 From: Luc Vachon Date: Fri, 26 Jun 2026 17:34:02 -0400 Subject: [PATCH 02/10] Moved stale calculation to inside the alert module --- lib/alerts/alert.ex | 26 +++++++++++++++++++ .../templates/schedule/_line.html.heex | 4 +-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/lib/alerts/alert.ex b/lib/alerts/alert.ex index cf1351d999..483da9e14d 100644 --- a/lib/alerts/alert.ex +++ b/lib/alerts/alert.ex @@ -58,6 +58,7 @@ defmodule Alerts.Alert do lifecycle: :unknown, priority: :low, severity: 5, + stale?: false, updated_at: Timex.now(), url: "" @@ -116,6 +117,7 @@ defmodule Alerts.Alert do lifecycle: lifecycle, priority: Priority.priority_level(), severity: severity, + stale?: boolean(), updated_at: DateTime.t(), url: String.t() | nil } @@ -135,6 +137,7 @@ defmodule Alerts.Alert do |> set_priority() |> set_direction_ids() |> ensure_entity_set() + |> check_freshness() end @spec update(t(), Keyword.t()) :: t() @@ -178,6 +181,29 @@ defmodule Alerts.Alert do %__MODULE__{alert | priority: Priority.priority(alert)} end + defp check_freshness(%__MODULE__{} = alert) do + now = Timex.now() + five_weeks_ago = DateTime.add(now, -5 * 7, :day) + current_active_period = current_active_period(alert, now) + + stale? = + if is_nil(current_active_period) do + false + else + current_active_period |> elem(0) |> DateTime.before?(five_weeks_ago) + end + + %__MODULE__{alert | stale?: stale?} + end + + def current_active_period(%__MODULE__{} = alert, now) do + alert.active_period + |> Enum.find(fn {start, stop} -> + (DateTime.before?(start, now) and (is_nil(stop) or DateTime.after?(stop, now))) or + (is_nil(start) and DateTime.after?(stop, now)) + end) + end + @spec build_struct(Keyword.t()) :: t() defp build_struct(keywords), do: struct!(__MODULE__, keywords) diff --git a/lib/dotcom_web/templates/schedule/_line.html.heex b/lib/dotcom_web/templates/schedule/_line.html.heex index 520780d18a..e9f7169395 100644 --- a/lib/dotcom_web/templates/schedule/_line.html.heex +++ b/lib/dotcom_web/templates/schedule/_line.html.heex @@ -33,9 +33,7 @@ {DotcomWeb.AlertView.group( alerts: @alerts - |> Enum.filter(fn %{created_at: created_at} -> - DateTime.after?(created_at, DateTime.add(@date_time, -5 * 7, :day)) - end), + |> Enum.filter(fn %{stale?: stale?} -> !stale? end), route: @route, date_time: @date_time, priority_filter: :high From a5722f58c7212e9b19041b7e4b37a51ef62be279 Mon Sep 17 00:00:00 2001 From: Luc Vachon Date: Fri, 26 Jun 2026 18:20:28 -0400 Subject: [PATCH 03/10] Updated logic to determine which active_period is current --- lib/alerts/alert.ex | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/alerts/alert.ex b/lib/alerts/alert.ex index 483da9e14d..9693cf21fa 100644 --- a/lib/alerts/alert.ex +++ b/lib/alerts/alert.ex @@ -199,8 +199,25 @@ defmodule Alerts.Alert do def current_active_period(%__MODULE__{} = alert, now) do alert.active_period |> Enum.find(fn {start, stop} -> - (DateTime.before?(start, now) and (is_nil(stop) or DateTime.after?(stop, now))) or - (is_nil(start) and DateTime.after?(stop, now)) + cond do + is_nil(start) and DateTime.after?(stop, now) -> + true + + is_nil(start) -> + false + + is_nil(stop) and DateTime.before?(start, now) -> + true + + is_nil(stop) -> + false + + DateTime.before?(start, now) and DateTime.after?(stop, now) -> + true + + true -> + false + end end) end From 2b19d41932cd836e4b34f5240b1791192deee5e8 Mon Sep 17 00:00:00 2001 From: Luc Vachon Date: Fri, 26 Jun 2026 18:21:44 -0400 Subject: [PATCH 04/10] Converted test date format to DateTime instead of NaiveDateTime --- test/alerts/match_test.exs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/test/alerts/match_test.exs b/test/alerts/match_test.exs index 785ece920f..27c14cf411 100644 --- a/test/alerts/match_test.exs +++ b/test/alerts/match_test.exs @@ -67,10 +67,9 @@ defmodule Alerts.MatchTest do %InformedEntity{stop: "1"} ], active_period: [ - {nil, ~N[2016-06-01T00:00:00]}, - {NaiveDateTime.from_erl!({{2016, 6, 2}, {0, 0, 0}}), - NaiveDateTime.from_erl!({{2016, 6, 2}, {1, 0, 0}})}, - {NaiveDateTime.from_erl!({{2016, 6, 3}, {0, 0, 0}}), nil} + {nil, ~U(2016-06-01 00:00:00Z)}, + {~U(2016-06-02 00:00:00Z), ~U(2016-06-02 01:00:00Z)}, + {~U(2016-06-03 00:00:00Z), nil} ] ) ] @@ -79,28 +78,28 @@ defmodule Alerts.MatchTest do assert Alerts.Match.match(alerts, ie) == alerts - assert Alerts.Match.match(alerts, ie, NaiveDateTime.from_erl!({{2016, 6, 1}, {0, 0, 0}})) == + assert Alerts.Match.match(alerts, ie, ~U(2016-06-01 00:00:00Z)) == alerts - assert Alerts.Match.match(alerts, ie, NaiveDateTime.from_erl!({{2016, 6, 2}, {0, 0, 0}})) == + assert Alerts.Match.match(alerts, ie, ~U(2016-06-02 00:00:00Z)) == alerts - assert Alerts.Match.match(alerts, ie, NaiveDateTime.from_erl!({{2016, 6, 2}, {0, 30, 0}})) == + assert Alerts.Match.match(alerts, ie, ~U(2016-06-02 00:30:00Z)) == alerts - assert Alerts.Match.match(alerts, ie, NaiveDateTime.from_erl!({{2016, 6, 3}, {0, 0, 0}})) == + assert Alerts.Match.match(alerts, ie, ~U(2016-06-03 00:00:00Z)) == alerts - assert Alerts.Match.match(alerts, ie, NaiveDateTime.from_erl!({{2016, 6, 4}, {0, 0, 0}})) == + assert Alerts.Match.match(alerts, ie, ~U(2016-06-04 00:00:00Z)) == alerts - assert Alerts.Match.match(alerts, ie, NaiveDateTime.from_erl!({{2016, 5, 20}, {0, 0, 0}})) == + assert Alerts.Match.match(alerts, ie, ~U(2016-05-20 00:00:00Z)) == alerts - assert Alerts.Match.match(alerts, ie, NaiveDateTime.from_erl!({{2016, 6, 1}, {12, 0, 0}})) == + assert Alerts.Match.match(alerts, ie, ~U(2016-06-01 12:00:00Z)) == [] - assert Alerts.Match.match(alerts, ie, NaiveDateTime.from_erl!({{2016, 6, 2}, {12, 0, 0}})) == + assert Alerts.Match.match(alerts, ie, ~U(2016-06-02 12:00:00Z)) == [] end From 236732e991bd7929cfa89e3f19f8cc203f88fca8 Mon Sep 17 00:00:00 2001 From: Luc Vachon Date: Mon, 29 Jun 2026 09:25:46 -0400 Subject: [PATCH 05/10] I disagree with the robot that this is too complex --- lib/alerts/alert.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/alerts/alert.ex b/lib/alerts/alert.ex index 9693cf21fa..d46b2632c2 100644 --- a/lib/alerts/alert.ex +++ b/lib/alerts/alert.ex @@ -196,6 +196,7 @@ defmodule Alerts.Alert do %__MODULE__{alert | stale?: stale?} end + # credo:disable-for-next-line def current_active_period(%__MODULE__{} = alert, now) do alert.active_period |> Enum.find(fn {start, stop} -> From 1f1086abd055d163e89e931bc6d6717bcd3cc2e4 Mon Sep 17 00:00:00 2001 From: Luc Vachon Date: Mon, 29 Jun 2026 13:37:59 -0400 Subject: [PATCH 06/10] Whoops, revered the logic here --- lib/alerts/alert.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/alerts/alert.ex b/lib/alerts/alert.ex index d46b2632c2..e709850c45 100644 --- a/lib/alerts/alert.ex +++ b/lib/alerts/alert.ex @@ -214,7 +214,7 @@ defmodule Alerts.Alert do false DateTime.before?(start, now) and DateTime.after?(stop, now) -> - true + false true -> false From 872026aab8256728f1d5966a7f519e29bca429d5 Mon Sep 17 00:00:00 2001 From: Luc Vachon Date: Mon, 29 Jun 2026 13:49:30 -0400 Subject: [PATCH 07/10] Maybe it is too complex... reverted inversion of logic. --- lib/alerts/alert.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/alerts/alert.ex b/lib/alerts/alert.ex index e709850c45..d46b2632c2 100644 --- a/lib/alerts/alert.ex +++ b/lib/alerts/alert.ex @@ -214,7 +214,7 @@ defmodule Alerts.Alert do false DateTime.before?(start, now) and DateTime.after?(stop, now) -> - false + true true -> false From ba7b068303267acd4052fe4cf521d2392d063180 Mon Sep 17 00:00:00 2001 From: Luc Vachon Date: Mon, 29 Jun 2026 15:16:41 -0400 Subject: [PATCH 08/10] Added tests, committed these to the wrong (but related) branch. --- test/alerts/alerts_test.exs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/test/alerts/alerts_test.exs b/test/alerts/alerts_test.exs index 8e34e0edc2..c6219132f7 100644 --- a/test/alerts/alerts_test.exs +++ b/test/alerts/alerts_test.exs @@ -25,6 +25,43 @@ defmodule AlertsTest do end end + describe "check_freshness/1" do + test "Marks alerts with no end older than 5 weeks as stale" do + stale_start_date = Timex.now() |> DateTime.add(Enum.random(-10..-6) * 7, :day) + alert = new(effect: :delay, active_period: [{stale_start_date, nil}]) + + assert alert.stale?, + "Alert with no end was expected to be stale, but was not" + end + + test "Marks alerts with an end older than 5 weeks as stale" do + stale_start_date = Timex.now() |> DateTime.add(Enum.random(-10..-6) * 7, :day) + stale_end_date = Timex.now() |> DateTime.add(Enum.random(1..10), :day) + alert = new(effect: :delay, active_period: [{stale_start_date, stale_end_date}]) + + assert alert.stale?, + "Alert with an end was expected to be stale, but was not #{stale_start_date}" + end + + test "Marks alerts with no end younger than 5 weeks as fresh" do + fresh_start_date = Timex.now() |> DateTime.add(Enum.random(-4..-1) * 7, :day) + alert = new(effect: :delay, active_period: [{fresh_start_date, nil}]) + + assert !alert.stale?, + "Alert with no end was expected to be fresh, but was not" + end + + test "Marks alerts with an end younger than 5 weeks as fresh" do + fresh_start_date = Timex.now() |> DateTime.add(Enum.random(-4..-1) * 7, :day) + fresh_end_date = Timex.now() |> DateTime.add(Enum.random(1..10), :day) + + alert = new(effect: :delay, active_period: [{fresh_start_date, fresh_end_date}]) + + assert !alert.stale?, + "Alert with an end was expected to be fresh, but was not" + end + end + describe "ongoing_effects/0" do test "returns a list" do assert is_list(ongoing_effects()) From cfe334ed385ace3adf85341f8c82ea70a6e869ca Mon Sep 17 00:00:00 2001 From: Luc Vachon Date: Tue, 30 Jun 2026 13:20:48 -0400 Subject: [PATCH 09/10] I have code that allows for a nil start date earlier in the chain, might as well follow through --- lib/alerts/alert.ex | 26 +++++++------------------- test/alerts/alerts_test.exs | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/lib/alerts/alert.ex b/lib/alerts/alert.ex index d46b2632c2..1d5bc9fc7c 100644 --- a/lib/alerts/alert.ex +++ b/lib/alerts/alert.ex @@ -190,7 +190,7 @@ defmodule Alerts.Alert do if is_nil(current_active_period) do false else - current_active_period |> elem(0) |> DateTime.before?(five_weeks_ago) + (current_active_period |> elem(0) || now) |> DateTime.before?(five_weeks_ago) end %__MODULE__{alert | stale?: stale?} @@ -200,24 +200,12 @@ defmodule Alerts.Alert do def current_active_period(%__MODULE__{} = alert, now) do alert.active_period |> Enum.find(fn {start, stop} -> - cond do - is_nil(start) and DateTime.after?(stop, now) -> - true - - is_nil(start) -> - false - - is_nil(stop) and DateTime.before?(start, now) -> - true - - is_nil(stop) -> - false - - DateTime.before?(start, now) and DateTime.after?(stop, now) -> - true - - true -> - false + case {start, stop} do + # nil start should never happen, but some tests include it + {nil, stop} -> DateTime.after?(stop, now) + {start, nil} -> DateTime.before?(start, now) + {start, stop} -> DateTime.before?(start, now) and DateTime.after?(stop, now) + _ -> false end end) end diff --git a/test/alerts/alerts_test.exs b/test/alerts/alerts_test.exs index c6219132f7..920a83b1a2 100644 --- a/test/alerts/alerts_test.exs +++ b/test/alerts/alerts_test.exs @@ -43,6 +43,25 @@ defmodule AlertsTest do "Alert with an end was expected to be stale, but was not #{stale_start_date}" end + test "Handles alerts with multiple active periods correctly" do + stale_start_date = Timex.now() |> DateTime.add(Enum.random(-10..-6) * 7, :day) + stale_end_date = Timex.now() |> DateTime.add(Enum.random(1..10), :day) + future_start_date = Timex.now() |> DateTime.add(Enum.random(1..30), :day) + future_end_date = future_start_date |> DateTime.add(Enum.random(1..30), :day) + + alert = + new( + effect: :delay, + active_period: [ + {stale_start_date, stale_end_date}, + {future_start_date, future_end_date} + ] + ) + + assert alert.stale?, + "Alert with an end was expected to be stale, but was not #{stale_start_date}" + end + test "Marks alerts with no end younger than 5 weeks as fresh" do fresh_start_date = Timex.now() |> DateTime.add(Enum.random(-4..-1) * 7, :day) alert = new(effect: :delay, active_period: [{fresh_start_date, nil}]) From a0aa65a338b981edbdeafcc33cb29549d152ac1b Mon Sep 17 00:00:00 2001 From: Luc Vachon Date: Tue, 30 Jun 2026 13:31:28 -0400 Subject: [PATCH 10/10] Lint --- lib/alerts/alert.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/alerts/alert.ex b/lib/alerts/alert.ex index 1d5bc9fc7c..48ab04d089 100644 --- a/lib/alerts/alert.ex +++ b/lib/alerts/alert.ex @@ -205,7 +205,6 @@ defmodule Alerts.Alert do {nil, stop} -> DateTime.after?(stop, now) {start, nil} -> DateTime.before?(start, now) {start, stop} -> DateTime.before?(start, now) and DateTime.after?(stop, now) - _ -> false end end) end