Public: anyone can read this; the crew can change it.
A flow card's unblock, who and reason draw §5's card, row and btn, and the .fcard-* classes leave flow_card.ts's markup #4283
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/4268-a-flow-card-s-unblock-who-and-reason-dra → main 1 commit fork
2 files
changed +23
−5
src/web/studio/flow_card.ts +5 −5
| @@ -12,19 +12,19 @@ import { readStalled, type StalledStep } from "../../flow/stalled.ts"; | ||
| 12 | 12 | import type { User } from "../../core/users.ts"; |
| 13 | 13 | import type { Ctx } from "../../http/context.ts"; |
| 14 | 14 | import { html, type Html } from "../html.ts"; |
| 15 | import { btn, card, timeAgo } from "../ui.ts"; | |
| 15 | import { badge, btn, card, timeAgo } from "../ui.ts"; | |
| 16 | 16 | |
| 17 | 17 | const BUTTON: Record<StalledStep["action"], string> = { run: "Run check", rerun: "Re-run" }; |
| 18 | 18 | |
| 19 | 19 | function stalledCard(s: StalledStep): Html { |
| 20 | 20 | const base = `/${s.ownerName}/${s.repoName}/pulls/${s.pull}`; |
| 21 | const head = html`<strong class="fcard-who">${s.stage}</strong> | |
| 21 | const head = html`${badge(s.stage, { variant: "secondary" })} | |
| 22 | 22 | <span class="muted small">${s.ownerName}/${s.repoName} · stuck since ${timeAgo(s.since)}</span>`; |
| 23 | const body = html`<p class="fcard-reason"><a href="${base}">${s.title}</a>${s.issue === null ? null : html` <span class="muted small">for #${String(s.issue)}</span>`}</p> | |
| 24 | <form method="post" action="${base}/checks" class="fcard-unblock"> | |
| 23 | const body = html`<p class="break"><a href="${base}">${s.title}</a>${s.issue === null ? null : html` <span class="muted small">for #${String(s.issue)}</span>`}</p> | |
| 24 | <form method="post" action="${base}/checks" class="bar"> | |
| 25 | 25 | <input type="hidden" name="next" value="/"> |
| 26 | 26 | ${btn(BUTTON[s.action], { size: "sm", icon: "play" })} |
| 27 | <span class="muted small">${s.unblock}</span> | |
| 27 | <span class="muted small break">${s.unblock}</span> | |
| 28 | 28 | </form>`; |
| 29 | 29 | return card(body, { head, attrs: html`data-flow-card="${s.repo}#${String(s.pull)}"` }); |
| 30 | 30 | } |
test/flow-card.test.ts +18
| @@ -266,6 +266,24 @@ describe("stalled steps on the home", () => { | ||
| 266 | 266 | guest.tui.stop(); |
| 267 | 267 | }); |
| 268 | 268 | |
| 269 | test("the card's who, reason and unblock are §5's badge, break and bar: no fcard-* class in its markup (#4268)", async () => { | |
| 270 | const pull = await pullOn(); | |
| 271 | run(pull, "failed"); | |
| 272 | const page = await home(); | |
| 273 | const at = page.indexOf(`data-flow-card="${repo.id}#${pull.number}"`); | |
| 274 | const end = page.indexOf("</section>", at); | |
| 275 | const card = page.slice(at, end === -1 ? undefined : end); | |
| 276 | expect(card).not.toMatch(/fcard-/); | |
| 277 | expect(card).toContain('class="badge"'); | |
| 278 | expect(card).toContain('class="break"'); | |
| 279 | expect(card).toMatch(/<form[^>]*class="bar"/); | |
| 280 | expect(card).toContain(`href="/alice/garden/pulls/${pull.number}"`); | |
| 281 | expect(card).toContain(`action="/alice/garden/pulls/${pull.number}/checks"`); | |
| 282 | expect(card).toContain('name="next" value="/"'); | |
| 283 | expect(card).toContain("Re-run"); | |
| 284 | expect(card).toContain("Change b1"); | |
| 285 | }); | |
| 286 | ||
| 269 | 287 | test("every stalled step is a card, past any cap; the button wraps on its own", async () => { |
| 270 | 288 | for (let i = 0; i < 8; i++) run(await pullOn(), "failed"); |
| 271 | 289 | const page = await home(); |
new above
offline