fix(norms): templates grade by the cutoffs when handed no grade; the parts test reads inside transactions (#5742)
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 1m18s
CI / unit (push) Successful in 1m25s
CI / publish (push) Successful in 1m11s

CI 9128: the host vitals and monitor templates required the new grades, so
callers (and tests) that render them without one failed; they now fall back
to status_class. The new integration test read outside begin(), which
autobegan the session.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 16:20:08 -04:00
co-authored by Claude Opus 5.5
parent f3433fe3ce
commit 7c23360bc7
4 changed files with 17 additions and 11 deletions
@@ -5,6 +5,7 @@
reports (the Agent panel below then shows provisioning). #}
{% from "_macros.html" import status_badge, stat_tile %}
{% macro pct(v) %}{{ '%.0f%%'|format(v) if v is not none else '—' }}{% endmacro %}
{% set grades = grades or {} %}
{% if reporting %}
<section class="card host-health" aria-label="Host health">
<div class="host-health-head">
@@ -22,10 +23,10 @@
</span>
</div>
<div class="stat-strip host-health-tiles" style="margin-bottom:0;">
{{ stat_tile("CPU", pct(cpu), state=grades.cpu[0], hint=sparks.cpu|safe) }}
{{ stat_tile("Memory", pct(mem), state=grades.mem[0], hint=sparks.mem|safe) }}
{{ stat_tile("Disk /", pct(disk_root), state=grades.disk[0], hint=sparks.disk|safe) }}
{{ stat_tile("Load per core", pct(load_per_core), state=grades.load[0], hint=sparks.load|safe) }}
{{ stat_tile("CPU", pct(cpu), state=(grades.cpu or [status_class(cpu, 'cpu')])[0], hint=sparks.cpu|safe) }}
{{ stat_tile("Memory", pct(mem), state=(grades.mem or [status_class(mem, 'mem')])[0], hint=sparks.mem|safe) }}
{{ stat_tile("Disk /", pct(disk_root), state=(grades.disk or [status_class(disk_root, 'disk')])[0], hint=sparks.disk|safe) }}
{{ stat_tile("Load per core", pct(load_per_core), state=(grades.load or [status_class(load_per_core, 'load')])[0], hint=sparks.load|safe) }}
</div>
{% if usual and not stale %}
<p class="field-hint" style="margin-top:0.6rem;">{{ usual | join("; ") | capitalize }}.</p>
+1 -1
View File
@@ -51,7 +51,7 @@
{% if d.state in ('down', 'paused', 'pending') %}{{ status_badge(d.state) }}
{% elif latest.response_ms is not none %}
{% if d.spark %}<span class="mon-spark" title="Response time over the last 30 checks">{{ d.spark|safe }}</span>{% endif %}
<span class="tone-{{ d.latency }}"{% if d.latency_usual %} title="{{ d.latency_usual|capitalize }}"{% endif %}>{{ "%.0f"|format(latest.response_ms) }} ms</span>
<span class="tone-{{ d.latency or status_class(latest.response_ms, 'latency') }}"{% if d.latency_usual %} title="{{ d.latency_usual|capitalize }}"{% endif %}>{{ "%.0f"|format(latest.response_ms) }} ms</span>
{% elif m.type == 'dns' and latest.resolved_ip %}<span class="tone-ok">{{ latest.resolved_ip }}</span>
{% else %}{{ status_badge('up') }}{% endif %}
</div>
+3 -2
View File
@@ -26,10 +26,11 @@
{{- status_dot(d.state) -}}
{% if session.user_id %}<a href="/monitors/{{ m.id }}">{{ m.name }}</a>{% else %}{{ m.name }}{% endif %}
{%- if d.state == 'down' %}<span class="w-sub tone-crit">went down {{ d.down_since|ago if d.down_since else '' }}{% if d.down_reason %} · {{ d.down_reason }}{% endif %}</span>
{%- elif d.state == 'degraded' %}<span class="w-sub tone-warn">{% if d.latency == 'crit' %}slow replies{% else %}checks failing now and then{% endif %}</span>
{%- elif d.state == 'degraded' %}<span class="w-sub tone-warn">{% if (d.latency or status_class(latest.response_ms, 'latency') if latest else none) == 'crit' %}slow replies{% else %}checks failing now and then{% endif %}</span>
{%- elif d.state == 'pending' %}<span class="w-sub">not checked yet</span>{% endif %}
</td>
<td class="{{ 'tone-' ~ d.latency if d.latency in ('warn', 'crit') }}"{% if d.latency_usual %} title="{{ d.latency_usual|capitalize }}"{% endif %}>
{% set lat = (d.latency or status_class(latest.response_ms, 'latency')) if latest and latest.is_up and latest.response_ms is not none else none %}
<td class="{{ 'tone-' ~ lat if lat in ('warn', 'crit') }}"{% if d.latency_usual %} title="{{ d.latency_usual|capitalize }}"{% endif %}>
{%- if latest and latest.is_up and latest.response_ms is not none %}{{ "%.0f"|format(latest.response_ms) }} ms{% else %}—{% endif -%}
</td>
{% for k in ("24h", "7d", "30d") %}{% set u = d.uptime.get(k) %}
+8 -4
View File
@@ -360,15 +360,19 @@ def test_a_metric_learned_without_its_parts_ignores_them(app, src):
s.add_all(rows)
async with s.begin():
await rebuild_norms(s, NOW)
learned = await norms_at(s, [(src, host, METRIC), (src, part, METRIC)], NOW.replace(hour=14))
async with s.begin():
learned = await norms_at(s, [(src, host, METRIC), (src, part, METRIC)],
NOW.replace(hour=14))
async with s.begin():
await _report(s, src, host, at, 90.0)
await _report(s, src, part, at, 90.0)
levels = await levels_now(s, at)
async with s.begin():
levels = await levels_now(s, at)
async with s.begin():
await detect(s, at)
opened = list((await s.execute(select(MetricAnomaly.resource_name).where(
MetricAnomaly.source_module == src))).scalars())
async with s.begin():
opened = list((await s.execute(select(MetricAnomaly.resource_name).where(
MetricAnomaly.source_module == src))).scalars())
return learned, levels, opened
learned, levels, opened = asyncio.run(_go())