feat(devices): auto-resolve active alerts when monitoring is disabled
When a user flips monitoring_enabled true → false on a device, any unresolved alerts on that device are now resolved silently — the operator has deliberately stopped monitoring it, so leaving stale "device down" pages around is noise. - Alerts.resolve_active_alerts_for_device/1: bulk-resolves via resolve_alert_silent so PagerDuty/email don't fire - Devices.handle_monitoring_changes hooks it on the disable transition
This commit is contained in:
parent
8f45057ba5
commit
4937570c2a
3 changed files with 71 additions and 0 deletions
|
|
@ -370,6 +370,23 @@ defmodule Towerops.Alerts do
|
|||
do_acknowledge_alert(alert, nil)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Resolves every active (unresolved) alert for the given device.
|
||||
|
||||
Used when the user takes a deliberate action that makes the alerts
|
||||
meaningless — e.g. disabling monitoring on a device. Resolves silently
|
||||
so downstream notification channels (PagerDuty, email) don't fire a
|
||||
spurious "resolved" event.
|
||||
"""
|
||||
def resolve_active_alerts_for_device(device_id) do
|
||||
from(a in Alert,
|
||||
where: a.device_id == ^device_id,
|
||||
where: is_nil(a.resolved_at)
|
||||
)
|
||||
|> Repo.all()
|
||||
|> Enum.each(&resolve_alert_silent/1)
|
||||
end
|
||||
|
||||
# Performs the database update for resolving an alert
|
||||
defp do_resolve_alert(alert) do
|
||||
alert
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ defmodule Towerops.Devices do
|
|||
import Ecto.Query
|
||||
|
||||
alias Towerops.Agents
|
||||
alias Towerops.Alerts
|
||||
alias Towerops.Devices.CredentialResolver
|
||||
alias Towerops.Devices.Device, as: DeviceSchema
|
||||
alias Towerops.Devices.DeviceQuery
|
||||
|
|
@ -982,6 +983,10 @@ defmodule Towerops.Devices do
|
|||
|
||||
!device.monitoring_enabled && old_monitoring ->
|
||||
_ = DeviceMonitorWorker.stop_monitoring(device.id)
|
||||
# Active alerts on this device are no longer meaningful — the user
|
||||
# has deliberately stopped monitoring it. Resolve silently so we
|
||||
# don't page anyone about the resolution.
|
||||
_ = Alerts.resolve_active_alerts_for_device(device.id)
|
||||
|
||||
true ->
|
||||
:ok
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ defmodule Towerops.EquipmentTest do
|
|||
use Towerops.DataCase
|
||||
use ExUnitProperties
|
||||
|
||||
alias Towerops.Alerts.Alert
|
||||
alias Towerops.Devices
|
||||
alias Towerops.Devices.Device
|
||||
|
||||
|
|
@ -131,6 +132,54 @@ defmodule Towerops.EquipmentTest do
|
|||
assert device.monitoring_enabled == false
|
||||
end
|
||||
|
||||
test "update_device/2 disabling monitoring auto-resolves active alerts on the device",
|
||||
%{organization: organization, site: site} do
|
||||
{:ok, device} =
|
||||
Devices.create_device(Map.merge(@valid_attrs, %{site_id: site.id, organization_id: organization.id}))
|
||||
|
||||
{:ok, alert} =
|
||||
Towerops.Alerts.create_alert(%{
|
||||
device_id: device.id,
|
||||
organization_id: organization.id,
|
||||
alert_type: "device_down",
|
||||
severity: 2,
|
||||
message: "Device unreachable",
|
||||
triggered_at: DateTime.utc_now()
|
||||
})
|
||||
|
||||
assert is_nil(alert.resolved_at)
|
||||
|
||||
assert {:ok, updated} =
|
||||
Devices.update_device(device, %{monitoring_enabled: false})
|
||||
|
||||
assert updated.monitoring_enabled == false
|
||||
|
||||
reloaded_alert = Towerops.Repo.get!(Alert, alert.id)
|
||||
assert reloaded_alert.resolved_at, "alert should be auto-resolved"
|
||||
end
|
||||
|
||||
test "update_device/2 leaves alerts alone when monitoring stays enabled",
|
||||
%{organization: organization, site: site} do
|
||||
{:ok, device} =
|
||||
Devices.create_device(Map.merge(@valid_attrs, %{site_id: site.id, organization_id: organization.id}))
|
||||
|
||||
{:ok, alert} =
|
||||
Towerops.Alerts.create_alert(%{
|
||||
device_id: device.id,
|
||||
organization_id: organization.id,
|
||||
alert_type: "device_down",
|
||||
severity: 2,
|
||||
message: "Device unreachable",
|
||||
triggered_at: DateTime.utc_now()
|
||||
})
|
||||
|
||||
# Updating something unrelated should not touch alerts
|
||||
assert {:ok, _updated} = Devices.update_device(device, %{name: "Renamed"})
|
||||
|
||||
reloaded_alert = Towerops.Repo.get!(Alert, alert.id)
|
||||
assert is_nil(reloaded_alert.resolved_at)
|
||||
end
|
||||
|
||||
test "update_device/2 with invalid data returns error changeset", %{organization: organization, site: site} do
|
||||
{:ok, device} =
|
||||
Devices.create_device(Map.merge(@valid_attrs, %{site_id: site.id, organization_id: organization.id}))
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue