fix: deduplicate active issues count when device is down

device_down alerts and active incidents represent the same outage.
Filter device_down alerts from the alerts section for devices already
shown as incidents, so a down device shows as 1 issue not 2.
This commit is contained in:
Graham McIntire 2026-05-04 12:11:54 -05:00
parent 833de33622
commit 20c464137f
3 changed files with 24 additions and 4 deletions

View file

@ -109,7 +109,7 @@ defmodule ToweropsWeb.DashboardLive do
defp load_dashboard_data(socket, organization_id) do
organization = socket.assigns.current_scope.organization
summary = Dashboard.get_dashboard_summary(organization_id)
active_alerts = Alerts.list_organization_active_alerts(organization_id, limit: 20)
active_alerts_all = Alerts.list_organization_active_alerts(organization_id, limit: 20)
total_alert_count = Alerts.count_active_alerts(organization_id)
sites_count = if organization.use_sites, do: Sites.count_organization_sites(organization_id), else: 0
@ -129,6 +129,19 @@ defmodule ToweropsWeb.DashboardLive do
impact_summary = Dashboard.get_impact_summary(organization_id)
active_incidents = Dashboard.list_active_incidents(organization_id)
# Exclude device_down alerts for devices already shown as active incidents
# to avoid showing the same outage twice in the Active Issues section.
incident_device_ids = MapSet.new(active_incidents, & &1.device_id)
active_alerts =
Enum.reject(active_alerts_all, fn alert ->
alert.alert_type == "device_down" and
MapSet.member?(incident_device_ids, alert.device_id)
end)
deduped_alert_count =
total_alert_count - (length(active_alerts_all) - length(active_alerts))
site_impact_summaries =
if organization.use_sites do
Dashboard.get_site_impact_summaries(organization_id)
@ -154,6 +167,7 @@ defmodule ToweropsWeb.DashboardLive do
|> assign(:summary, summary)
|> assign(:active_alerts, active_alerts)
|> assign(:total_alert_count, total_alert_count)
|> assign(:deduped_alert_count, deduped_alert_count)
|> assign(:sites_count, sites_count)
|> assign(:device_count, device_count)
|> assign(:device_up, device_up)

View file

@ -340,7 +340,7 @@
{t("Active Issues")}
</h2>
<span class="font-mono text-xs text-red-500 dark:text-red-400">
{length(@active_incidents) + @total_alert_count}
{length(@active_incidents) + @deduped_alert_count}
</span>
</div>

View file

@ -128,7 +128,11 @@ defmodule ToweropsWeb.DashboardLiveTest do
end
describe "alert feed" do
test "shows alerts with device links", %{conn: conn, organization: organization, site: site} do
test "shows down device as incident (not duplicate in alerts table)", %{
conn: conn,
organization: organization,
site: site
} do
{:ok, device} = create_device(organization, site, "Main Router", "10.0.0.1")
Towerops.Devices.update_device_status(device, :down)
@ -142,9 +146,11 @@ defmodule ToweropsWeb.DashboardLiveTest do
{:ok, _view, html} = live(conn, ~p"/dashboard")
# Device appears in the Active Incidents section
assert html =~ "Main Router"
assert html =~ "Device is unreachable"
assert html =~ ~p"/devices/#{device.id}"
# device_down alert is filtered from the alerts table to avoid double-counting
refute html =~ "Device is unreachable"
end
test "shows acknowledge button for unacknowledged alerts", %{