feat(email): delivery you can see: each alert's email in history, outgoing email on Settings -> System (#5509)
#5509 "Delivery you can see: each alert's email state in history, and outgoing email on Settings -> System" (milestone 501). - Alerts -> History: an alert's email reads from the outbox (#5506): "gathering" with when it sends, "sending", "retrying" with the attempt, next try and the server's last answer, "sent", or "failed" naming each reader it didn't reach and why (outbox.delivery / email_states). Events from before the outbox keep what they recorded. The Notified column shows once an alert has a queued email. - Settings -> System gets "Outgoing email": every email of the last 7 days not yet sent, failures first, then the longest retrying, with recipient, kind, state and last error, and a count of what was sent. Failures and anything still retrying after 5 minutes lead the page's verdict (outbox.outgoing). - get_alert_history (MCP) adds `email`: the same delivery state. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ from datetime import datetime, timedelta, timezone
|
||||
from sqlalchemy import select
|
||||
|
||||
from steward.alerts import service as alert_svc
|
||||
from steward.core import outbox
|
||||
from steward.mcp._context import current_caller, mcp_app
|
||||
from steward.models.alerts import AlertOperator, AlertRule, AlertState, MaintenanceWindow
|
||||
from steward.models.users import UserRole
|
||||
@@ -82,7 +83,11 @@ async def get_alert_history(rule: str = "", range: str = "7d", limit: int = 50)
|
||||
|
||||
Each transition carries the metric value that caused it and whether the
|
||||
email/webhook notification went out (with the error if not), plus the id
|
||||
of any Ansible run the rule's action triggered. `by` names the person who
|
||||
of any Ansible run the rule's action triggered. `email` is the email's
|
||||
delivery: state "held" (gathering other alerts; `at` is when it sends),
|
||||
"sending", "retrying" (`attempts`, next try `at`, last `error`), "sent"
|
||||
or "failed" (`error` names each reader not reached); null when no email
|
||||
was queued for it. `by` names the person who
|
||||
made it happen (an acknowledgement, or a rule edit that reset the alert);
|
||||
null when a reading or Steward itself did.
|
||||
"""
|
||||
@@ -93,6 +98,7 @@ async def get_alert_history(rule: str = "", range: str = "7d", limit: int = 50)
|
||||
chosen = pick(rule, rules, kind="alert rule", list_tool="list_alert_rules") if rule else None
|
||||
rows = await alert_svc.history(db, since, chosen.id if chosen else None, limit)
|
||||
names = await alert_svc.usernames(db, (e.actor_id for e, _ in rows))
|
||||
emails = await outbox.email_states(db, [e.id for e, _ in rows])
|
||||
events = [
|
||||
{
|
||||
"rule_id": r.id,
|
||||
@@ -106,6 +112,7 @@ async def get_alert_history(rule: str = "", range: str = "7d", limit: int = 50)
|
||||
"age_s": age_s(e.transitioned_at),
|
||||
"email_sent": e.email_sent,
|
||||
"email_error": e.email_error,
|
||||
"email": ({**emails[e.id], "at": iso(emails[e.id]["at"])} if e.id in emails else None),
|
||||
"webhook_sent": e.webhook_sent,
|
||||
"webhook_error": e.webhook_error,
|
||||
"ansible_run_id": e.ansible_run_id,
|
||||
|
||||
Reference in New Issue
Block a user