towerops/lib/towerops/alerts.ex
Graham McIntire aff602e7dd
Fix active alerts filter to exclude recovery notifications
Only equipment_down alerts should be counted as "active alerts that need attention".
Equipment_up alerts are informational recovery notifications and don't require action.
2026-01-03 11:09:13 -06:00

138 lines
3.3 KiB
Elixir

defmodule Towerops.Alerts do
@moduledoc """
The Alerts context.
"""
import Ecto.Query
alias Towerops.Alerts.Alert
alias Towerops.Repo
@doc """
Creates an alert for equipment status change.
"""
def create_alert(attrs) do
%Alert{}
|> Alert.changeset(attrs)
|> Repo.insert()
end
@doc """
Returns the list of alerts for an equipment.
"""
def list_equipment_alerts(equipment_id, limit \\ 100) do
Repo.all(
from(a in Alert,
where: a.equipment_id == ^equipment_id,
order_by: [desc: a.triggered_at],
limit: ^limit,
preload: [:equipment, :acknowledged_by]
)
)
end
@doc """
Returns the list of active (unresolved) alerts for an organization.
Only returns equipment_down alerts, as equipment_up alerts are informational.
"""
def list_organization_active_alerts(organization_id) do
Repo.all(
from(a in Alert,
join: e in assoc(a, :equipment),
join: s in assoc(e, :site),
where: s.organization_id == ^organization_id,
where: a.alert_type == :equipment_down,
where: is_nil(a.resolved_at),
order_by: [desc: a.triggered_at],
preload: [equipment: {e, site: s}, acknowledged_by: :acknowledged_by]
)
)
end
@doc """
Returns the list of all alerts for an organization.
"""
def list_organization_alerts(organization_id, limit \\ 100) do
Repo.all(
from(a in Alert,
join: e in assoc(a, :equipment),
join: s in assoc(e, :site),
where: s.organization_id == ^organization_id,
order_by: [desc: a.triggered_at],
limit: ^limit,
preload: [equipment: {e, site: s}, acknowledged_by: :acknowledged_by]
)
)
end
@doc """
Gets a single alert.
"""
def get_alert!(id) do
Alert
|> Repo.get!(id)
|> Repo.preload([:equipment, :acknowledged_by])
end
@doc """
Acknowledges an alert.
"""
def acknowledge_alert(alert, user_id) do
alert
|> Alert.changeset(%{
acknowledged_at: DateTime.utc_now(),
acknowledged_by_id: user_id
})
|> Repo.update()
end
@doc """
Resolves an alert.
"""
def resolve_alert(alert) do
alert
|> Alert.changeset(%{resolved_at: DateTime.utc_now()})
|> Repo.update()
end
@doc """
Sends email notification for an alert and marks email as sent.
"""
def send_alert_notification(%Alert{} = alert) do
alias Towerops.Alerts.AlertNotifier
{:ok, _results} = AlertNotifier.deliver_alert_notification(alert)
alert
|> Alert.changeset(%{email_sent_at: DateTime.utc_now()})
|> Repo.update()
end
@doc """
Checks if there's an active alert of the same type for the equipment.
"""
def has_active_alert?(equipment_id, alert_type) do
Repo.exists?(
from(a in Alert,
where: a.equipment_id == ^equipment_id,
where: a.alert_type == ^alert_type,
where: is_nil(a.resolved_at)
)
)
end
@doc """
Gets the latest active alert of a specific type for equipment.
"""
def get_active_alert(equipment_id, alert_type) do
Repo.one(
from(a in Alert,
where: a.equipment_id == ^equipment_id,
where: a.alert_type == ^alert_type,
where: is_nil(a.resolved_at),
order_by: [desc: a.triggered_at],
limit: 1
)
)
end
end