|
@@ -1729,6 +1729,115 @@ def bundle_pages(ctx):
|
|
|
< queue_page.index("<table"),
|
|
< queue_page.index("<table"),
|
|
|
"the New Task expander sits above the filter controls and the table")
|
|
"the New Task expander sits above the filter controls and the table")
|
|
|
|
|
|
|
|
|
|
+ print("== yeehaw: ingestion, festive card, badge, timeline ==")
|
|
|
|
|
+ # `saddle yeehaw <message>` arrives as a round-results entry with the
|
|
|
|
|
+ # message in the output field: it stores like any entry type, earns
|
|
|
|
|
+ # the task its 🤠 badge (derived, nothing new stored), renders as the
|
|
|
|
|
+ # task page's festive card (last 3, newest first, "+N more") and
|
|
|
|
|
+ # surfaces on the dashboard timeline as its own event kind.
|
|
|
|
|
+ conn = db()
|
|
|
|
|
+ conn.execute("insert into tasks (project_id, title, status, priority, created_at)"
|
|
|
|
|
+ " values (1, 'Yeehaw task', 'working', 5, strftime('%s','now'))")
|
|
|
|
|
+ yeehaw_task = conn.execute("select max(id) from tasks").fetchone()[0]
|
|
|
|
|
+ conn.execute("insert into sessions (task_id, saddle_id, started_at)"
|
|
|
|
|
+ " values (?, ?, strftime('%s','now'))", (yeehaw_task, fake_id))
|
|
|
|
|
+ yeehaw_session = conn.execute("select max(id) from sessions").fetchone()[0]
|
|
|
|
|
+ conn.commit()
|
|
|
|
|
+ conn.close()
|
|
|
|
|
+ now_epoch = int(time.time())
|
|
|
|
|
+
|
|
|
|
|
+ def push_yeehaw(entry_id, message, offset=0):
|
|
|
|
|
+ """One celebration riding a round-results push, timestamped
|
|
|
|
|
+ `offset` seconds from now (distinct epochs keep the timeline's
|
|
|
|
|
+ newest-first ordering observable)."""
|
|
|
|
|
+ return saddle_api("POST", "/api/saddle/results", fake_token, {
|
|
|
|
|
+ "session_id": yeehaw_session, "state": "working",
|
|
|
|
|
+ "entries": [{
|
|
|
|
|
+ "id": entry_id, "command": "saddle yeehaw", "output": message,
|
|
|
|
|
+ "exit_code": 0, "type": "yeehaw",
|
|
|
|
|
+ "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ",
|
|
|
|
|
+ time.gmtime(now_epoch + offset)),
|
|
|
|
|
+ }]})[0]
|
|
|
|
|
+
|
|
|
|
|
+ def yeehaw_rows():
|
|
|
|
|
+ conn = db()
|
|
|
|
|
+ try:
|
|
|
|
|
+ return conn.execute(
|
|
|
|
|
+ "select count(*) from session_commands where session_id = ?"
|
|
|
|
|
+ " and type = 'yeehaw'", (yeehaw_session,)).fetchone()[0]
|
|
|
|
|
+ finally:
|
|
|
|
|
+ conn.close()
|
|
|
|
|
+
|
|
|
|
|
+ check(push_yeehaw(9101, "Shipped the flange") == 200,
|
|
|
|
|
+ "yeehaw entries ingest through the results push like any entry type")
|
|
|
|
|
+ check(yeehaw_rows() == 1,
|
|
|
|
|
+ "the yeehaw entry stores as a session_commands row (message in output)")
|
|
|
|
|
+ check(push_yeehaw(9101, "Shipped the flange") == 200 and yeehaw_rows() == 1,
|
|
|
|
|
+ "a redelivered yeehaw push is idempotent on the daemon entry id")
|
|
|
|
|
+ check(task_state(auth, yeehaw_task)["status"] == "working",
|
|
|
|
|
+ "a yeehaw entry never derails the task state")
|
|
|
|
|
+
|
|
|
|
|
+ push_yeehaw(9102, "Second hoedown", 1)
|
|
|
|
|
+ push_yeehaw(9103, "Third hoedown\nsecond line carries the detail", 2)
|
|
|
|
|
+ push_yeehaw(9104, "Fourth hoedown", 3)
|
|
|
|
|
+ push_yeehaw(9105, "Fifth hoedown", 4)
|
|
|
|
|
+ detail = task_state(auth, yeehaw_task)
|
|
|
|
|
+ check(detail.get("yeehaw") is True and detail.get("yeehaw_total") == 5,
|
|
|
|
|
+ "the detail state flags the badge presence and the uncapped total")
|
|
|
|
|
+ check([y["message"] for y in detail.get("yeehaws", [])]
|
|
|
|
|
+ == ["Fifth hoedown", "Fourth hoedown",
|
|
|
|
|
+ "Third hoedown\nsecond line carries the detail"],
|
|
|
|
|
+ "the festive card keeps the last 3 yeehaws, newest first (full messages)")
|
|
|
|
|
+ check(detail.get("yeehaw_more") == 2,
|
|
|
|
|
+ "the '+N more' hint counts the capped tail")
|
|
|
|
|
+ status, page = http("GET", "/tasks/%d" % yeehaw_task, timeout=15)
|
|
|
|
|
+ # The template compiler HTML-escapes the emoji (🤠 = 🤠).
|
|
|
|
|
+ check(status == 200 and "yeehaw-card" in page and "🤠 Yeehaw!" in page,
|
|
|
|
|
+ "the task page renders the festive yeehaw card")
|
|
|
|
|
+ check(page.index("<h3>Progress</h3>") < page.index("yeehaw-card")
|
|
|
|
|
+ < page.index("status-head"),
|
|
|
|
|
+ "the card sits between the progress feed and the status grid")
|
|
|
|
|
+ check('stm-text="task.yeehaw_more"' in page and "more yeehaw" in page,
|
|
|
|
|
+ "the card carries the +N more hint")
|
|
|
|
|
+ check("badge-yeehaw" in page and "status-corner" in page,
|
|
|
|
|
+ "the status card corner carries the earned 🤠 badge")
|
|
|
|
|
+ plain_detail = task_state(auth, layout_task)
|
|
|
|
|
+ check(plain_detail.get("yeehaw") is False and plain_detail.get("yeehaw_total") == 0
|
|
|
|
|
+ and plain_detail.get("yeehaws", []) == [],
|
|
|
|
|
+ "tasks without yeehaw entries carry no card state")
|
|
|
|
|
+ status, plain_page = http("GET", "/tasks/%d" % layout_task, timeout=15)
|
|
|
|
|
+ check(status == 200 and 'section class="card yeehaw-card" stm-if="task.yeehaw"' in plain_page,
|
|
|
|
|
+ "the card template only renders under state gating (stm-if task.yeehaw)")
|
|
|
|
|
+
|
|
|
|
|
+ queue_rows = slot_public(auth, "/tasks", "tasks")["tasks"]
|
|
|
|
|
+ check([t for t in queue_rows if int(t["id"]) == yeehaw_task][0].get("has_yeehaw") is True,
|
|
|
|
|
+ "queue rows badge yeehaw tasks")
|
|
|
|
|
+ check([t for t in queue_rows if int(t["id"]) == layout_task][0].get("has_yeehaw") is False,
|
|
|
|
|
+ "queue rows leave yeehaw-less tasks bare")
|
|
|
|
|
+ status, queue_page = http("GET", "/tasks", timeout=15)
|
|
|
|
|
+ check(status == 200 and 'stm-if="task.has_yeehaw"' in queue_page,
|
|
|
|
|
+ "the queue template renders the row badge")
|
|
|
|
|
+
|
|
|
|
|
+ dash = slot_public(auth, "/", "dashboard")
|
|
|
|
|
+ yeehaw_events = [e for e in dash.get("activity", []) if e["kind"] == "yeehaw"]
|
|
|
|
|
+ check(len(yeehaw_events) == 5
|
|
|
|
|
+ and all(int(e["task_id"]) == yeehaw_task for e in yeehaw_events),
|
|
|
|
|
+ "every yeehaw entry surfaces as its own timeline event kind")
|
|
|
|
|
+ check(yeehaw_events[0]["text"] == "🤖 yeehaw'd: Fifth hoedown",
|
|
|
|
|
+ "the newest yeehaw event leads the timeline rows")
|
|
|
|
|
+ check(all("second line" not in e["text"] for e in yeehaw_events),
|
|
|
|
|
+ "multiline yeehaw messages snippet to their first line")
|
|
|
|
|
+ referenced = [e for e in dash.get("activity", []) if int(e["task_id"]) == yeehaw_task]
|
|
|
|
|
+ check(len(referenced) > 0 and all(e.get("task_yeehaw") is True for e in referenced),
|
|
|
|
|
+ "dashboard timeline task references carry the badge flag")
|
|
|
|
|
+ plain_refs = [e for e in dash.get("activity", []) if int(e["task_id"]) == layout_task]
|
|
|
|
|
+ check(len(plain_refs) > 0 and all(e.get("task_yeehaw") is False for e in plain_refs),
|
|
|
|
|
+ "references to yeehaw-less tasks stay bare")
|
|
|
|
|
+ status, home_page = http("GET", "/", timeout=15)
|
|
|
|
|
+ check(status == 200 and 'stm-if="item.task_yeehaw"' in home_page
|
|
|
|
|
+ and "badge-yeehaw" in home_page,
|
|
|
|
|
+ "the dashboard renders the badge on timeline task references")
|
|
|
|
|
+
|
|
|
print("== R10: branch_advanced on the /poll verdict channel ==")
|
|
print("== R10: branch_advanced on the /poll verdict channel ==")
|
|
|
conn = db()
|
|
conn = db()
|
|
|
conn.execute("insert into tasks (project_id, title, status, priority,"
|
|
conn.execute("insert into tasks (project_id, title, status, priority,"
|