feat(prometheus): widget template format, checker and renderer (#5690)
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 1m17s
CI / unit (push) Successful in 1m27s
CI / publish (push) Successful in 21s

#5690 "Widget template format: YAML schema, validator and renderer", step 2
of milestone 508 "Prometheus widget templates".

- template_format.check(text) -> Template, or TemplateError with every
  problem, each with its line and what to change. Blocks: states, values
  (of -> share and bar, thresholds as numbers or shares, sparkline),
  times (warn_after / crit_after), table (two labels pivoted), info.
  Anchors/aliases refused, 64 KB cap, YAML's on/off/yes/no flagged.
- template_view.render(template, target, latest, now, history) -> View:
  a verdict naming the worst problem, then each block with its problems
  first (crit, warn, ok, then quiet states). Missing series show as "—"
  and say so; a template whose `expects` prefix the target lacks says so.
- shown_value moves to service.py so the renderer can use it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 15:34:15 -04:00
co-authored by Claude Opus 5.5
parent ec3e5e7e0d
commit 4d95c5d0c2
5 changed files with 1007 additions and 16 deletions
+15 -1
View File
@@ -5,11 +5,12 @@ from __future__ import annotations
import math
from dataclasses import dataclass, field
from datetime import datetime
from datetime import datetime, timezone
from fnmatch import fnmatchcase
from steward.core import prometheus
from steward.core.crypto import encrypt_secret
from steward.core.formatting import metric_value
from .models import PrometheusTarget
@@ -121,6 +122,19 @@ def fold_states(metric: str, rows: list[dict]) -> list[dict] | None:
return folded
def shown_value(metric: str, value: float | None) -> dict:
"""A series' value in its unit, from the Prometheus naming conventions.
A *_timestamp_seconds is a moment: it comes back as `moment` for the page
to show as "3m ago" in the reader's zone; 0 there means "never"."""
if value is None:
return {"text": "—", "moment": None}
if metric.endswith("_timestamp_seconds"):
if value <= 0:
return {"text": "never", "moment": None}
return {"text": "", "moment": datetime.fromtimestamp(value, timezone.utc)}
return {"text": metric_value(metric, value), "moment": None}
@dataclass
class Series:
resource: str