feat(3 U5.1+U5.2): per-recipe latest-level badge endpoint /badge/<recipe>.svg (R6, level-coloured, status fallback) + complete docs/results-ux.md §3-5 (card/screenshot/PR-comment/badge-embedding, R8); +2 badge unit tests
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
@ -326,16 +326,31 @@ def render_history(recipe, rows):
|
||||
return _page(f"{recipe} — cc-ci history", inner)
|
||||
|
||||
|
||||
def render_badge(recipe, status):
|
||||
color = _COLORS.get(status, "#8b949e")
|
||||
label, msg = "cc-ci", status
|
||||
lw, mw = 44, max(40, 7 * len(msg) + 10)
|
||||
def _badge_svg(label, msg, color):
|
||||
"""Two-box shields-style SVG (grey label | coloured message). Stdlib-only, deterministic sizing."""
|
||||
lw = max(44, 7 * len(label) + 12)
|
||||
mw = max(40, 7 * len(msg) + 12)
|
||||
w = lw + mw
|
||||
return f"""<svg xmlns="http://www.w3.org/2000/svg" width="{w}" height="20" role="img">
|
||||
<rect width="{lw}" height="20" fill="#555"/><rect x="{lw}" width="{mw}" height="20" fill="{color}"/>
|
||||
<g fill="#fff" font-family="Verdana,sans-serif" font-size="11">
|
||||
<text x="6" y="14">{html.escape(label)}</text>
|
||||
<text x="{lw + 6}" y="14">{html.escape(msg)}</text></g></svg>"""
|
||||
return (
|
||||
f'<svg xmlns="http://www.w3.org/2000/svg" width="{w}" height="20" role="img" '
|
||||
f'aria-label="{html.escape(label)}: {html.escape(msg)}">'
|
||||
f'<rect width="{lw}" height="20" fill="#555"/>'
|
||||
f'<rect x="{lw}" width="{mw}" height="20" fill="{color}"/>'
|
||||
f'<g fill="#fff" font-family="Verdana,Geneva,sans-serif" font-size="11">'
|
||||
f'<text x="6" y="14">{html.escape(label)}</text>'
|
||||
f'<text x="{lw + 6}" y="14">{html.escape(msg)}</text></g></svg>'
|
||||
)
|
||||
|
||||
|
||||
def render_badge(recipe, status):
|
||||
"""Status fallback badge (used when a recipe has no results.json level yet)."""
|
||||
return _badge_svg("cc-ci", status, _COLORS.get(status, "#8b949e"))
|
||||
|
||||
|
||||
def render_level_badge(recipe, level):
|
||||
"""Per-recipe latest-LEVEL badge (R6): 'cc-ci: <recipe> | level N', coloured by level —
|
||||
embeddable in a recipe README (`/badge/<recipe>.svg`) and shown on the dashboard."""
|
||||
return _badge_svg(f"cc-ci: {recipe}", f"level {int(level)}", level_color(level))
|
||||
|
||||
|
||||
def serve_run_file(run_id, fname):
|
||||
@ -363,8 +378,11 @@ class Handler(BaseHTTPRequestHandler):
|
||||
if path.startswith("/badge/") and path.endswith(".svg"):
|
||||
recipe = path[len("/badge/") : -len(".svg")]
|
||||
row = next((r for r in recipes_cached() if r["recipe"] == recipe), None)
|
||||
status = row["status"] if row else "unknown"
|
||||
return 200, render_badge(recipe, status), "image/svg+xml"
|
||||
# R6: per-recipe LATEST-LEVEL badge (from results.json). Fall back to a status badge when
|
||||
# the recipe has no level yet (never ran / failed before emitting results.json).
|
||||
if row and row.get("level") is not None:
|
||||
return 200, render_level_badge(recipe, row["level"]), "image/svg+xml"
|
||||
return 200, render_badge(recipe, row["status"] if row else "unknown"), "image/svg+xml"
|
||||
if path.startswith("/runs/"):
|
||||
# /runs/<run_id>/<file> — stable URL for a run's results.json / summary.png / screenshot /
|
||||
# badge (R3/R6). Whitelisted + traversal-guarded by serve_run_file.
|
||||
|
||||
Reference in New Issue
Block a user