feat(card): show skipped rungs as rows — INTENTIONAL SKIP (green) with reason below
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
Per operator: intentional skips now render like a pass row but labelled 'INTENTIONAL SKIP' (muted green) with the declared reason on the line beneath; unintentional skips render amber 'UNINTENTIONAL SKIP' with a prompt to add a test or declare them. The cap line is back to just the level-cap reason (the per-rung reason now lives in the rows). Labelled, so it never reads as a PASS. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -141,6 +141,43 @@ def _stage_rows(stages: list[dict]) -> str:
|
||||
return "\n".join(rows) or '<tr><td colspan="3">no stages</td></tr>'
|
||||
|
||||
|
||||
# Friendly rung labels for the skip rows.
|
||||
RUNG_LABEL = {
|
||||
"install": "install",
|
||||
"upgrade": "upgrade",
|
||||
"backup_restore": "backup/restore",
|
||||
"functional": "functional",
|
||||
"integration": "integration",
|
||||
"recipe_local": "recipe-local",
|
||||
}
|
||||
SKIP_GREEN = "#57ab5a" # muted green — an intentional skip reads like a pass (but labelled, never inflating)
|
||||
|
||||
|
||||
def _skip_rows(skips: dict) -> str:
|
||||
"""Render SKIPPED rungs as stage-like rows. An intentional (declared) skip looks like a pass row
|
||||
but its status says 'INTENTIONAL SKIP' (muted green) with the declared reason on the line below;
|
||||
an unintentional skip is amber 'UNINTENTIONAL SKIP' with a prompt to add a test or declare it."""
|
||||
rows = []
|
||||
for rung, reason in (skips.get("intentional") or {}).items():
|
||||
rows.append(
|
||||
f'<tr class="stage"><td colspan="2"><span class="mark" style="color:{SKIP_GREEN}">⊘</span>'
|
||||
f'<b>{html.escape(RUNG_LABEL.get(rung, rung))}</b></td>'
|
||||
f'<td class="st" style="color:{SKIP_GREEN}">intentional skip</td></tr>'
|
||||
)
|
||||
rows.append(f'<tr class="skipreason"><td></td><td colspan="2">{html.escape(reason)}</td></tr>')
|
||||
for rung in skips.get("unintentional") or []:
|
||||
rows.append(
|
||||
f'<tr class="stage"><td colspan="2"><span class="mark" style="color:{GAP_COLOR}">⊘</span>'
|
||||
f'<b>{html.escape(RUNG_LABEL.get(rung, rung))}</b></td>'
|
||||
f'<td class="st" style="color:{GAP_COLOR}">unintentional skip</td></tr>'
|
||||
)
|
||||
rows.append(
|
||||
'<tr class="skipreason"><td></td><td colspan="2">not declared in EXPECTED_NA — add the '
|
||||
"missing test/label, or declare the skip with a reason</td></tr>"
|
||||
)
|
||||
return "\n".join(rows)
|
||||
|
||||
|
||||
def render_card_html(data: dict, screenshot_rel: str | None = "screenshot.png") -> str:
|
||||
"""Build the summary-card HTML from a results.json dict. `screenshot_rel` is the relative path to
|
||||
the screenshot PNG (same dir as the card) — omitted from the card if None / absent.
|
||||
@ -151,15 +188,8 @@ def render_card_html(data: dict, screenshot_rel: str | None = "screenshot.png")
|
||||
version = html.escape(str(data.get("version") or data.get("ref") or ""))
|
||||
level = int(data.get("level", 0))
|
||||
cap_reason = str(data.get("level_cap_reason") or "")
|
||||
# Annotate the cap line by whether the capping rung was an intentional skip (declared, with its
|
||||
# reason) or an unintentional one (skipped but not declared).
|
||||
capped = data.get("level_cap_rung")
|
||||
sk = data.get("skips", {}) or {}
|
||||
if capped and capped in (sk.get("intentional") or {}):
|
||||
cap_reason += f" · intentional: {sk['intentional'][capped]}"
|
||||
elif capped and capped in (sk.get("unintentional") or []):
|
||||
cap_reason += " · unintentional skip (no EXPECTED_NA — add a test or declare it)"
|
||||
cap = html.escape(cap_reason)
|
||||
sk = data.get("skips", {}) or {}
|
||||
color = level_color(level)
|
||||
flags = data.get("flags", {}) or {}
|
||||
flag_bits = []
|
||||
@ -175,7 +205,7 @@ def render_card_html(data: dict, screenshot_rel: str | None = "screenshot.png")
|
||||
if show_shot
|
||||
else '<div class="shot noshot">no screenshot</div>'
|
||||
)
|
||||
rows = _stage_rows(data.get("stages", []))
|
||||
rows = _stage_rows(data.get("stages", [])) + "\n" + _skip_rows(sk)
|
||||
return f"""<!doctype html><html><head><meta charset="utf-8"><style>
|
||||
*{{box-sizing:border-box}}
|
||||
body{{margin:0;font-family:system-ui,-apple-system,Segoe UI,sans-serif;background:#0d1117;color:#c9d1d9}}
|
||||
@ -200,6 +230,7 @@ tr.stage td{{padding-top:.5rem;border-bottom:1px solid #30363d}}
|
||||
.test .tmark{{width:1.4rem;text-align:center}}
|
||||
.test .tname{{color:#c9d1d9;font-family:ui-monospace,monospace;font-size:.8rem}}
|
||||
.test .tms{{text-align:right;color:#8b949e;font-size:.74rem;width:5rem}}
|
||||
tr.skipreason td{{color:#8b949e;font-size:.78rem;font-style:italic;padding-top:0;padding-bottom:.45rem;border-bottom:1px solid #21262d}}
|
||||
.shot{{width:360px;flex:none;border:1px solid #30363d;border-radius:8px;overflow:hidden;background:#0d1117}}
|
||||
.shot img{{width:100%;display:block}}
|
||||
.shot.noshot{{display:flex;align-items:center;justify-content:center;height:225px;color:#8b949e;font-size:.85rem}}
|
||||
|
||||
Reference in New Issue
Block a user