fix(alerts): an alert on a count says how many of what, and names them (#6400)
"swarm services short on swarm is 6." becomes "6 swarm services are short of their replicas.", with a Which line naming each service and how many of its tasks run. The swarm figures get labels, and the counting figures (swarm, mounts, drives, updates) read as sentences in the email, its subject and the Alerts page. A plugin names what its count counts through core.alerts.names_behind; the Docker plugin does for the three swarm figures, from the stored swarm. Webhooks get alert.which too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -163,7 +163,8 @@ def alert_sentence(rule: AlertRule, label: str, value: float | None,
|
||||
"tail"}, read lead + resource + tail, e.g. "CPU on " + "web-1" +
|
||||
" is 94% (> 90%), firing for 12m". label is alert_label(state); since is
|
||||
when that label began (no_data_since for no_data)."""
|
||||
from steward.core.formatting import human_duration, metric_label, metric_value
|
||||
from steward.core.formatting import (WHOLE_RESOURCES, count_words, human_duration, metric_label,
|
||||
metric_value)
|
||||
metric = rule.metric_name
|
||||
said = metric_label(metric)
|
||||
held = human_duration((now - since).total_seconds()) if since else None
|
||||
@@ -174,6 +175,15 @@ def alert_sentence(rule: AlertRule, label: str, value: float | None,
|
||||
# not how long ago someone acknowledged it (the page says that apart).
|
||||
word = "breaching" if label == "acknowledged" else label
|
||||
for_how_long = f", {word} for {held}" if held else f", {word}"
|
||||
condition = f" ({rule.operator.value} {metric_value(metric, rule.threshold)})"
|
||||
counted = count_words(metric, value)
|
||||
if counted and rule.resource_name in WHOLE_RESOURCES:
|
||||
# The count is the whole of it, and links where the swarm is shown.
|
||||
return {"lead": "", "resource": counted[:1].upper() + counted[1:],
|
||||
"tail": f"{condition}{for_how_long}"}
|
||||
if counted:
|
||||
return {"lead": f"{counted[:1].upper() + counted[1:]} on ", "resource": rule.resource_name,
|
||||
"tail": f"{condition}{for_how_long}"}
|
||||
if value is None:
|
||||
reading = " has no reading yet"
|
||||
elif metric in _BINARY:
|
||||
@@ -181,7 +191,6 @@ def alert_sentence(rule: AlertRule, label: str, value: float | None,
|
||||
"tail": f" is {metric_value(metric, value)}{for_how_long}"}
|
||||
else:
|
||||
reading = f" is {metric_value(metric, value)}"
|
||||
condition = f" ({rule.operator.value} {metric_value(metric, rule.threshold)})"
|
||||
return {"lead": f"{said} on ", "resource": rule.resource_name,
|
||||
"tail": f"{reading}{condition}{for_how_long}"}
|
||||
|
||||
@@ -787,11 +796,14 @@ async def test_alert(db, rule: AlertRule) -> dict:
|
||||
target = (await db.execute(
|
||||
select(AlertRule).where(AlertRule.parent_id == rule.id)
|
||||
.order_by(AlertRule.resource_name).limit(1))).scalar_one_or_none() or rule
|
||||
from steward.core.alerts import which
|
||||
return {"rule_name": rule.name, "state": "TEST", "metric": rule.metric_name,
|
||||
"value": await latest_value(db, target) if target.resource_name else None,
|
||||
"operator": rule.operator.value, "threshold": rule.threshold,
|
||||
"resource": target.resource_name or covers_text(rule),
|
||||
"source_module": rule.source_module,
|
||||
"which": await which(db, rule.source_module, rule.metric_name, target.resource_name)
|
||||
if target.resource_name else [],
|
||||
"note": "A test of this rule's notifications. Nothing has fired.",
|
||||
"timestamp": datetime.now(timezone.utc).isoformat()}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user