feat(tracker-web): collapse heartbeat noise + surface PR on job detail

The job detail timeline was buried under hundreds of lease_renewed rows on
long-running jobs. Collapse consecutive high-frequency events (lease_renewed)
into one "type xN - over Nm" summary row; everything else renders verbatim. Add
a prominent Pull Request banner (link + state) sourced from whichever run opened
the PR, instead of only the per-attempt Runs column.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
saravanakumardb1 2026-06-01 02:06:33 -07:00
parent 5ad521ad4c
commit 5262583e8b

View File

@ -260,6 +260,39 @@ export default function FleetJobDetailPage() {
{/* Prompt (the job body) + PR/target config */}
<PromptCard job={job} onChanged={refresh} />
{/* Pull request (surfaced from whichever run opened it) */}
{(() => {
const prRun = runs.find(r => r.prUrl);
if (!prRun?.prUrl) return null;
return (
<section
className="flex flex-wrap items-center gap-3 rounded-lg border bg-muted/20 p-3"
aria-label="Pull request"
>
<span className="text-sm font-semibold">Pull Request</span>
<a
href={prRun.prUrl}
target="_blank"
rel="noopener noreferrer"
className="text-sm text-primary hover:underline break-all"
>
{prRun.prUrl}
</a>
{prRun.prState && (
<span
className={`rounded-full px-2 py-0.5 text-xs font-medium ${
prRun.prState === 'merged'
? 'bg-green-600/15 text-green-700 dark:text-green-400'
: 'bg-muted text-muted-foreground'
}`}
>
{prRun.prState}
</span>
)}
</section>
);
})()}
{/* Review gate (multi-reviewer human gate) */}
{job.stage === 'review' && (
<ReviewGateCard
@ -309,18 +342,36 @@ export default function FleetJobDetailPage() {
<p className="text-muted-foreground text-sm">No events recorded.</p>
) : (
<ul className="space-y-2">
{events.map(e => (
<li
key={e.id}
className="flex items-start gap-3 text-sm border-l-2 border-muted pl-3 py-1"
>
<span className="text-muted-foreground text-xs whitespace-nowrap">
{new Date(e.at).toLocaleTimeString()}
</span>
<span className="font-medium">{e.type}</span>
{e.actor && <span className="text-muted-foreground">by {e.actor}</span>}
</li>
))}
{groupTimelineEvents(events).map(g =>
g.kind === 'single' ? (
<li
key={g.event.id}
className="flex items-start gap-3 text-sm border-l-2 border-muted pl-3 py-1"
>
<span className="text-muted-foreground text-xs whitespace-nowrap">
{new Date(g.event.at).toLocaleTimeString()}
</span>
<span className="font-medium">{g.event.type}</span>
{g.event.actor && (
<span className="text-muted-foreground">by {g.event.actor}</span>
)}
</li>
) : (
<li
key={g.key}
className="flex items-start gap-3 text-sm border-l-2 border-dashed border-muted pl-3 py-1 text-muted-foreground"
title={`${g.count} ${g.type} events from ${new Date(g.first).toLocaleTimeString()} to ${new Date(g.last).toLocaleTimeString()}`}
>
<span className="text-xs whitespace-nowrap">
{new Date(g.first).toLocaleTimeString()}
</span>
<span>
{g.type} <span className="tabular-nums">×{g.count}</span>
<span className="text-xs"> · over {fmtDuration(g.last - g.first)}</span>
</span>
</li>
)
)}
</ul>
)}
</section>
@ -665,6 +716,53 @@ function Stat({ label, value }: { label: string; value: string }) {
);
}
type TimelineGroup =
| { kind: 'single'; event: FleetEvent }
| { kind: 'collapsed'; key: string; type: string; count: number; first: number; last: number };
/** High-frequency event types collapsed into one summary row so a long-running
* job's timeline isn't buried under hundreds of heartbeats. */
const COLLAPSIBLE_EVENT_TYPES = new Set(['lease_renewed']);
/** Fold consecutive runs of a collapsible event type (e.g. lease_renewed) into a
* single "type ×N · over Xm" row; everything else renders verbatim, in order. */
function groupTimelineEvents(events: FleetEvent[]): TimelineGroup[] {
const out: TimelineGroup[] = [];
let run: FleetEvent[] = [];
const flush = () => {
if (run.length === 0) return;
if (run.length === 1) {
out.push({ kind: 'single', event: run[0]! });
} else {
const first = run[0]!;
const last = run[run.length - 1]!;
out.push({
kind: 'collapsed',
key: `grp-${first.id}-${last.id}`,
type: first.type,
count: run.length,
first: Date.parse(first.at),
last: Date.parse(last.at),
});
}
run = [];
};
for (const e of events) {
if (COLLAPSIBLE_EVENT_TYPES.has(e.type)) {
if (run.length > 0 && run[0]!.type === e.type) run.push(e);
else {
flush();
run = [e];
}
} else {
flush();
out.push({ kind: 'single', event: e });
}
}
flush();
return out;
}
/** Sum cost / tokens / wall-time across a job's runs. */
function runTotals(runs: FleetRun[]) {
let costUsd = 0;