towerops/lib/towerops/on_call/notifier.ex
Graham McIntire 0cd2ed3567
feat: add on-call schedules and escalation policies
Built-in PagerDuty-equivalent system for on-call scheduling and alert
escalation. Users can now manage schedules, rotation layers, overrides,
and escalation policies directly in the app alongside PagerDuty.

- On-call schedules with rotation layers (daily/weekly/custom), member
  management, and temporary overrides
- Escalation policies with ordered rules, timeout-based escalation,
  and user/schedule targets
- Automatic escalation via Oban worker with configurable repeat count
- Email notifications via Swoosh for on-call alerts
- Resolver computes who's on-call from layer stacking and overrides
- AlertNotificationWorker integration: starts escalation alongside
  PagerDuty, acknowledges/resolves incidents on alert state changes
- Device and organization schemas support escalation_policy_id
- Escalation policy picker on device form
- Schedules nav item with tabbed index (schedules + escalation policies)
- Full CRUD UI for schedules, layers, members, overrides, rules, targets
- 62 LiveView tests, 56 context/schema/resolver/escalation tests
- 26 E2E Playwright tests for smoke and critical path coverage
2026-03-11 12:32:54 -05:00

44 lines
1.1 KiB
Elixir

defmodule Towerops.OnCall.Notifier do
@moduledoc """
Sends on-call alert notifications to users via email.
"""
import Swoosh.Email
alias Towerops.Mailer
require Logger
@doc """
Sends an alert notification email to the given user about an incident.
"""
def notify(user, incident) do
from_address = Application.get_env(:towerops, :mailer_from, {"Towerops", "hi@towerops.net"})
email =
new()
|> to(user.email)
|> from(from_address)
|> subject("[Towerops Alert] Incident triggered")
|> text_body("""
An alert has been triggered and you are on-call.
Incident ID: #{incident.id}
Status: #{incident.status}
Triggered at: #{incident.triggered_at}
Please acknowledge or resolve this incident in Towerops.
""")
case Mailer.deliver(email) do
{:ok, _} ->
Logger.info("On-call notification sent to #{user.email} for incident #{incident.id}")
:ok
{:error, reason} ->
Logger.error("Failed to send on-call notification to #{user.email}: #{inspect(reason)}")
{:error, reason}
end
end
end