feat(ansible): runs named by what ran where, with ago, duration and one status badge (#5125)
Runs read "update.yml on web-1" (run_title over the scope label, beside describe_scope) on the runs list, the run page's title and breadcrumb, and the run-started panel. The list shows started |ago (full time on hover), |duration (counting "so far" while a run is going) and a dry-run tag. Run statuses join STATES, so the list and the run page use status_badge: queued dim, running info (blue, so work in progress doesn't read as a problem), success ok "Succeeded", failed crit, interrupted warn, cancelled dim. run_started.html leaves its hard-coded colours for tokens; the run page's facts move to the .kv list and its failures to var(--red). Template hex colours: 54 -> 47. Also fixes #5124's sidebar count, which CI run 8389 failed on: the stylesheet guard allows no weight above 500, and .nav-count asked for 600. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
"""Ansible runs are named by what ran where, say how long ago and for how
|
||||
long, and wear the one status badge every page uses (#5125)."""
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
from quart import render_template, session
|
||||
|
||||
from steward.ansible.inventory_gen import run_title
|
||||
from steward.ansible.routes import run_duration, run_view
|
||||
from steward.models.ansible import AnsibleRunStatus
|
||||
from tests.test_style_tokens import hex_colours
|
||||
|
||||
|
||||
@pytest.mark.parametrize("label, title", [
|
||||
("target web-1", "update.yml on web-1"),
|
||||
("group Docker Swarm", "update.yml on group Docker Swarm"),
|
||||
("all inventory targets", "update.yml on all inventory targets"),
|
||||
("homelab/hosts.ini", "update.yml on homelab/hosts.ini"),
|
||||
("", "update.yml"),
|
||||
])
|
||||
def test_a_run_is_named_by_what_ran_where(label, title):
|
||||
assert run_title("update.yml", label) == title
|
||||
|
||||
|
||||
def _run(rid, status, started_min_ago, took_s=None, scope="steward:target:t1", check=False):
|
||||
now = datetime.now(timezone.utc)
|
||||
started = now - timedelta(minutes=started_min_ago)
|
||||
return SimpleNamespace(
|
||||
id=rid, playbook_path="update.yml", source_name="builtin", inventory_scope=scope,
|
||||
status=status, started_at=started,
|
||||
finished_at=started + timedelta(seconds=took_s) if took_s is not None else None,
|
||||
params={"check": True} if check else None, results=None, output="")
|
||||
|
||||
|
||||
def test_a_running_run_counts_its_duration_so_far():
|
||||
now = datetime.now(timezone.utc)
|
||||
run = _run("r", AnsibleRunStatus.running, 3)
|
||||
assert run_duration(run, now) == pytest.approx(180, abs=2)
|
||||
assert run_duration(_run("f", AnsibleRunStatus.success, 10, took_s=95), now) == 95
|
||||
|
||||
|
||||
async def test_the_runs_list_names_each_run_with_one_badge(app):
|
||||
now = datetime.now(timezone.utc)
|
||||
labels = {"steward:target:t1": "target web-1", "steward:all": "all inventory targets"}
|
||||
runs = [run_view(r, labels, now) for r in (
|
||||
_run("aaaa1111", AnsibleRunStatus.running, 2),
|
||||
_run("bbbb2222", AnsibleRunStatus.failed, 60, took_s=125, scope="steward:all", check=True),
|
||||
_run("cccc3333", AnsibleRunStatus.success, 120, took_s=40),
|
||||
)]
|
||||
async with app.test_request_context("/ansible/"):
|
||||
session["user_role"] = "viewer"
|
||||
page = await render_template("ansible/index.html", runs=runs)
|
||||
|
||||
main = page[page.index("<main"):]
|
||||
assert '<a href="/ansible/runs/aaaa1111" title="steward:target:t1">update.yml on web-1</a>' in main
|
||||
assert ">update.yml on all inventory targets</a>" in main
|
||||
assert '<span class="badge badge-info">Running</span>' in main
|
||||
assert '<span class="badge badge-crit">Failed</span>' in main
|
||||
assert '<span class="badge badge-ok">Succeeded</span>' in main
|
||||
assert "2m 5s" in main and "40s" in main and " so far" in main
|
||||
assert "dry run" in main and "ago" in main
|
||||
assert "RUNNING" not in main and "FAILED" not in main # the old shouted status text is gone
|
||||
assert not hex_colours(main), hex_colours(main)
|
||||
|
||||
|
||||
async def test_run_detail_titles_the_run_and_keeps_the_trail(app):
|
||||
now = datetime.now(timezone.utc)
|
||||
run = _run("dddd4444", AnsibleRunStatus.interrupted, 30, took_s=600)
|
||||
async with app.test_request_context("/ansible/runs/dddd4444"):
|
||||
session["user_role"] = "operator"
|
||||
page = await render_template("ansible/run_detail.html", run=run, triggered_label="system",
|
||||
row=run_view(run, {"steward:target:t1": "target web-1"}, now))
|
||||
|
||||
assert "<title>update.yml on web-1 — Steward</title>" in page
|
||||
assert 'href="/ansible/"' in page # breadcrumb back to the runs
|
||||
assert '<span class="badge badge-warn">Interrupted</span>' in page
|
||||
assert "<dd>10m</dd>" in page
|
||||
assert "/cancel" not in page # an interrupted run has nothing to cancel
|
||||
main = page[page.index("<main"):]
|
||||
assert not hex_colours(main), hex_colours(main)
|
||||
|
||||
|
||||
async def test_run_started_is_on_the_palette(app):
|
||||
run = _run("eeee5555", AnsibleRunStatus.running, 0)
|
||||
async with app.test_request_context("/ansible/runs"):
|
||||
page = await render_template("ansible/run_started.html", run=run, title="update.yml on web-1")
|
||||
assert '<span class="badge badge-info">Running</span>' in page
|
||||
assert "update.yml on web-1" in page and 'href="/ansible/runs/eeee5555"' in page
|
||||
assert not hex_colours(page), hex_colours(page)
|
||||
Reference in New Issue
Block a user