johnandersen777.bsky.social/knotless
git that lives in your Atmosphere account
Forked from chadtmiller.com/knotless
0
0
<script lang="ts">
import type * as listRuns from '#lib/lexicons/dev/knotless/listRuns.ts';
import { ago, capitalized, checkHref, overall, plural, shortRef, took } from '#lib/format.ts';
import { checkIcon } from '#lib/icons.ts';
import Icon from '#lib/atoms/Icon.svelte';
import Time from '#lib/atoms/Time.svelte';
/** Runs of a repository's workflows, newest first, one row for each commit they ran on (a pull's head, a merge queue's batch, a push): how they went and how long the longest took, what ran, and what failed or is still going, each linked to its steps and log. */
let { runs }: { runs: listRuns.RunView[] } = $props();
/** A check's key, less its workflow: the same for every run of one commit, as one push, pull head or batch ran it. */
const key = (r: listRuns.RunView) => r.check.uri.slice(0, r.check.uri.lastIndexOf('.'));
/** The runs, in order, each with those after it of the same commit. */
const groups = $derived.by(() => {
const by = new Map<string, listRuns.RunView[]>();
for (const r of runs) by.set(key(r), [...(by.get(key(r)) ?? []), r]);
return [...by.values()];
});
const words: Record<string, string> = {
success: 'Passed',
failure: 'Failed',
running: 'Running',
queued: 'Queued',
cancelled: 'Cancelled'
};
/** Where a run ran: a pull, a merge queue's batch, or the ref pushed to. */
const on = (r: listRuns.RunView) => (r.pull ? 'pull' : r.batch ? 'queue' : shortRef(r.ref));
/** What it ran: the pull's title, the batch, or the commit's subject. */
const what = (r: listRuns.RunView) =>
r.pull?.title ??
(r.batch
? `a batch of ${plural(r.batch, 'pull', 'pulls')}`
: (r.subject ?? r.check.commit.slice(0, 7)));
const failed = (rs: listRuns.RunView[]) =>
rs.filter((r) => r.check.status === 'failure' || r.check.status === 'error');
const going = (rs: listRuns.RunView[]) =>
rs.filter((r) => r.check.status === 'running' || r.check.status === 'queued');
/** Under how it went: how many are still going, or how long the longest that ran took. */
function under(rs: listRuns.RunView[]): string {
if (going(rs).length) return `${going(rs).length} of ${rs.length} going`;
const ms = rs
.filter((r) => r.check.finishedAt && !r.check.skipped)
.map((r) => Date.parse(r.check.finishedAt ?? '') - Date.parse(r.check.startedAt));
return ms.length ? took(Math.max(...ms)) : '';
}
/** The run a row opens: the first that failed, else one still going, else the first. */
const lead = (rs: listRuns.RunView[]) => failed(rs)[0] ?? going(rs)[0] ?? rs[0];
/** Why runs could not run, said once: a conflict fails them all alike. */
const reason = (rs: listRuns.RunView[]) => rs.find((r) => r.check.error)?.check.error;
</script>
<div class="box run-list">
{#each groups as rs (key(rs[0]))}
{@const r = rs[0]}
{@const state = overall(rs.map((r) => r.check.status))}
<div class="row">
<div class="went">
<span class="mark {state}"><Icon name={checkIcon(state)} /></span><b
>{words[state] ?? capitalized(state)}</b
><small>{under(rs)}</small>
</div>
<div class="what">
<a class="title" href={checkHref(lead(rs).check.repo, lead(rs).check.uri)}>{what(r)}</a>
<div class="sub">
<span>{on(r)}</span><span class="sha">{r.check.commit.slice(0, 7)}</span><span
>{plural(rs.length, 'workflow', 'workflows')}</span
>
</div>
{#if reason(rs)}
<p class="why" title={reason(rs)}>{reason(rs)}</p>
{:else if failed(rs).length}
<p class="why">
{#each failed(rs) as f, i (f.check.uri)}{i ? ', ' : ''}<a
href={checkHref(f.check.repo, f.check.uri)}>{f.check.workflow}</a
>{/each} failed
</p>
{:else if going(rs).length}
<p class="sub">
{#each going(rs) as g, i (g.check.uri)}{i ? ', ' : ''}<a
href={checkHref(g.check.repo, g.check.uri)}>{g.check.workflow}</a
>{/each} still going
</p>
{/if}
</div>
<span class="when"><Time at={r.check.startedAt} show={ago} /></span>
</div>
{/each}
</div>