| 1 | /** |
| 2 | * Which of a run's buttons its page shows: Cancel run (or Force cancel) |
| 3 | * while any of it is still going, the re-runs once it has finished. Pure; |
| 4 | * the page is routes/repo/actions-run.tsx. |
| 5 | * |
| 6 | * A run is going while its latest attempt is: the attempt list's last |
| 7 | * entry says how it stands now, whichever attempt the page shows, so an |
| 8 | * earlier attempt's page (`?attempt=2`, and its `?job=` links) offers |
| 9 | * Cancel run while a later one runs. On the latest attempt a job that has |
| 10 | * not finished (waiting, pending approval, queued for a runner, running) |
| 11 | * keeps the run going too, whatever the run's own status says. |
| 12 | */ |
| 13 | import type { Job, RunAttempt, WorkflowRun } from "@g1t/contracts"; |
| 14 | |
| 15 | export type RunControls = { |
| 16 | /** The attempt shown is the run's latest. */ |
| 17 | latest: boolean; |
| 18 | /** The run, at its latest attempt, has not finished. */ |
| 19 | going: boolean; |
| 20 | /** Cancelled, while its jobs run their cleanup steps: Force cancel in place of Cancel run. */ |
| 21 | cancelling: boolean; |
| 22 | canCancel: boolean; |
| 23 | canRerun: boolean; |
| 24 | }; |
| 25 | |
| 26 | export function runControls(input: { |
| 27 | run: Pick<WorkflowRun, "attempt" | "status" | "conclusion" | "error">; |
| 28 | jobs: Pick<Job, "status">[]; |
| 29 | attempts: Pick<RunAttempt, "attempt" | "status">[]; |
| 30 | /** The viewer may cancel and re-run (Write). */ |
| 31 | member: boolean; |
| 32 | }): RunControls { |
| 33 | const { run, jobs, attempts, member } = input; |
| 34 | const newest = attempts[attempts.length - 1]; |
| 35 | const latest = newest == null || run.attempt === newest.attempt; |
| 36 | const going = latest ? run.status !== "completed" || jobs.some((job) => job.status !== "completed") : newest.status !== "completed"; |
| 37 | // Only the latest attempt's own status says it is being cancelled; an |
| 38 | // earlier attempt's conclusion is how that attempt ended. |
| 39 | const cancelling = going && latest && run.conclusion === "cancelled"; |
| 40 | const allowed = member && !run.error; |
| 41 | return { latest, going, cancelling, canCancel: allowed && going, canRerun: allowed && !going && latest }; |
| 42 | } |