Skip to content

Commit 98665e9

Browse files
authored
fix(dashboard): compact run labels (#1337)
1 parent 6fbf815 commit 98665e9

7 files changed

Lines changed: 304 additions & 41 deletions

File tree

apps/dashboard/src/components/RunList.mobile.spec.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,22 @@ describe('buildRunListItemView', () => {
4545
expect(view.isActive).toBe(true);
4646
expect(view.passing).toBe(false);
4747
});
48+
49+
it('uses compact run display without duplicating the pass-rate column', () => {
50+
const view = buildRunListItemView(
51+
runMeta({
52+
display_name: '2026-03-27T05-00-00-000Z',
53+
filename: 'remote::2026-03-27T05-00-00-000Z',
54+
target: 'remote-target',
55+
timestamp: '2026-03-27T05:00:00.000Z',
56+
pass_rate: 1,
57+
source: 'remote',
58+
}),
59+
0.8,
60+
);
61+
62+
expect(view.display.primary).toBe('27/03 05:00');
63+
expect(view.display.secondary).toBe('remote-target');
64+
expect(view.label).toBe('27/03 05:00 · remote-target');
65+
});
4866
});

apps/dashboard/src/components/RunList.tsx

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import {
3030
useStudioConfig,
3131
} from '~/lib/api';
3232
import { executionErrorCount } from '~/lib/result-summary';
33-
import { formatRunLabel } from '~/lib/run-label';
33+
import { type RunDisplay, formatRunDisplay } from '~/lib/run-label';
3434
import {
3535
buildCombineSuccessMessage,
3636
buildDeleteSuccessMessage,
@@ -63,6 +63,7 @@ interface RunListItemView {
6363
ts: { date: string; full: string };
6464
isActive: boolean;
6565
label: string;
66+
display: RunDisplay;
6667
errors: number;
6768
qualityCount: number;
6869
passing: boolean;
@@ -94,7 +95,8 @@ function formatDate(ts: string | undefined | null): { date: string; full: string
9495
export function buildRunListItemView(run: RunMeta, passThreshold: number): RunListItemView {
9596
const ts = formatDate(run.timestamp);
9697
const isActive = run.status === 'starting' || run.status === 'running';
97-
const label = formatRunLabel(run);
98+
const display = formatRunDisplay(run, { includePassRate: false });
99+
const label = display.label;
98100
const errors = executionErrorCount(run);
99101
const qualityCount = Math.max(0, run.test_count - errors);
100102
const passing = qualityCount > 0 ? run.pass_rate >= passThreshold : errors === 0;
@@ -107,6 +109,7 @@ export function buildRunListItemView(run: RunMeta, passThreshold: number): RunLi
107109
ts,
108110
isActive,
109111
label,
112+
display,
110113
errors,
111114
qualityCount,
112115
passing,
@@ -380,8 +383,17 @@ export function RunList({
380383
)}
381384
<div className="space-y-2 sm:hidden">
382385
{runViews.map((view) => {
383-
const { run, ts, label, errors, qualityCount, passedCount, failedCount, metadataDirty } =
384-
view;
386+
const {
387+
run,
388+
ts,
389+
label,
390+
display,
391+
errors,
392+
qualityCount,
393+
passedCount,
394+
failedCount,
395+
metadataDirty,
396+
} = view;
385397
const selectionDisabledReason = runSelectionDisabledReason(run);
386398
const selectable = !selectionDisabledReason && selectableRunIds.includes(run.filename);
387399

@@ -411,9 +423,15 @@ export function RunList({
411423
<RunNameLink
412424
projectId={projectId}
413425
runId={run.filename}
414-
label={label}
415-
className="block break-all text-sm font-medium text-cyan-400 hover:text-cyan-300 hover:underline"
426+
label={display.primary}
427+
title={display.title}
428+
className="block truncate text-sm font-medium text-cyan-400 hover:text-cyan-300 hover:underline"
416429
/>
430+
{display.secondary ? (
431+
<p className="mt-0.5 truncate text-xs text-gray-500" title={display.title}>
432+
{display.secondary}
433+
</p>
434+
) : null}
417435
<div className="mt-2 flex flex-wrap items-center gap-2">
418436
<SourceBadge source={run.source} />
419437
{metadataDirty ? <PendingSyncBadge /> : null}
@@ -479,6 +497,7 @@ export function RunList({
479497
run,
480498
ts,
481499
label,
500+
display,
482501
errors,
483502
qualityCount,
484503
passedCount,
@@ -514,14 +533,25 @@ export function RunList({
514533

515534
{/* Run name */}
516535
<td className="w-[22rem] max-w-[22rem] px-4 py-3">
517-
<div className="flex min-w-0 items-center gap-2">
518-
<RunNameLink
519-
projectId={projectId}
520-
runId={run.filename}
521-
label={label}
522-
className="block min-w-0 truncate font-medium text-cyan-400 hover:text-cyan-300 hover:underline"
523-
/>
524-
{metadataDirty ? <PendingSyncBadge /> : null}
536+
<div className="min-w-0">
537+
<div className="flex min-w-0 items-center gap-2">
538+
<RunNameLink
539+
projectId={projectId}
540+
runId={run.filename}
541+
label={display.primary}
542+
title={display.title}
543+
className="block min-w-0 truncate font-medium text-cyan-400 hover:text-cyan-300 hover:underline"
544+
/>
545+
{metadataDirty ? <PendingSyncBadge /> : null}
546+
</div>
547+
{display.secondary ? (
548+
<div
549+
className="mt-0.5 truncate text-xs text-gray-500"
550+
title={display.title}
551+
>
552+
{display.secondary}
553+
</div>
554+
) : null}
525555
</div>
526556
</td>
527557

@@ -581,24 +611,26 @@ function RunNameLink({
581611
projectId,
582612
runId,
583613
label,
614+
title,
584615
className,
585616
}: {
586617
projectId?: string;
587618
runId: string;
588619
label: string;
620+
title: string;
589621
className: string;
590622
}) {
591623
return projectId ? (
592624
<Link
593625
to="/projects/$projectId/runs/$runId"
594626
params={{ projectId, runId }}
595627
className={className}
596-
title={label}
628+
title={title}
597629
>
598630
{label}
599631
</Link>
600632
) : (
601-
<Link to="/runs/$runId" params={{ runId }} className={className} title={label}>
633+
<Link to="/runs/$runId" params={{ runId }} className={className} title={title}>
602634
{label}
603635
</Link>
604636
);

apps/dashboard/src/components/Sidebar.tsx

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import {
3434
useStudioConfig,
3535
} from '~/lib/api';
3636
import { resolveProjectDisplayName } from '~/lib/project-display-name';
37-
import { formatRunLabel, timeAgo } from '~/lib/run-label';
37+
import { formatRunDisplay } from '~/lib/run-label';
3838
import { useSidebarContext } from '~/lib/sidebar-context';
3939

4040
import { BrandName } from './BrandName';
@@ -88,6 +88,17 @@ function BrandHeader({ projectId }: { projectId?: string }) {
8888
);
8989
}
9090

91+
function SidebarRunText({ display }: { display: ReturnType<typeof formatRunDisplay> }) {
92+
return (
93+
<>
94+
<span className="block truncate">{display.primary}</span>
95+
{display.secondary ? (
96+
<span className="block truncate text-xs text-gray-600">{display.secondary}</span>
97+
) : null}
98+
</>
99+
);
100+
}
101+
91102
function useProjectDisplayName(projectId: string): string {
92103
const { data } = useProjectList();
93104
return resolveProjectDisplayName(projectId, data?.projects);
@@ -274,6 +285,7 @@ function RunSidebar() {
274285
</div>
275286

276287
{data?.runs.map((run) => {
288+
const display = formatRunDisplay(run);
277289
const isActive =
278290
isHome === false &&
279291
runMatch &&
@@ -289,10 +301,9 @@ function RunSidebar() {
289301
to="/projects/$projectId/runs/$runId"
290302
params={{ projectId: run.project_id, runId: run.filename }}
291303
className="mb-0.5 block rounded-md px-2 py-1.5 text-sm text-gray-400 transition-colors hover:bg-gray-800/50 hover:text-gray-200"
292-
title={run.project_name}
304+
title={`${display.title}\nProject: ${run.project_name}`}
293305
>
294-
<span className="block truncate">{formatRunLabel(run)}</span>
295-
<span className="block text-xs text-gray-600">{timeAgo(run.timestamp)}</span>
306+
<SidebarRunText display={display} />
296307
</Link>
297308
);
298309
}
@@ -307,9 +318,9 @@ function RunSidebar() {
307318
? 'bg-gray-800 text-cyan-400'
308319
: 'text-gray-400 hover:bg-gray-800/50 hover:text-gray-200'
309320
}`}
321+
title={display.title}
310322
>
311-
<span className="block truncate">{formatRunLabel(run)}</span>
312-
<span className="block text-xs text-gray-600">{timeAgo(run.timestamp)}</span>
323+
<SidebarRunText display={display} />
313324
</Link>
314325
);
315326
})}
@@ -507,6 +518,7 @@ function ProjectRunDetailSidebar({
507518
Runs
508519
</div>
509520
{data?.runs.map((run) => {
521+
const display = formatRunDisplay(run);
510522
const isActive = currentRunId === run.filename;
511523
return (
512524
<Link
@@ -518,9 +530,9 @@ function ProjectRunDetailSidebar({
518530
? 'bg-gray-800 text-cyan-400'
519531
: 'text-gray-400 hover:bg-gray-800/50 hover:text-gray-200'
520532
}`}
533+
title={display.title}
521534
>
522-
<span className="block truncate">{formatRunLabel(run)}</span>
523-
<span className="block text-xs text-gray-600">{timeAgo(run.timestamp)}</span>
535+
<SidebarRunText display={display} />
524536
</Link>
525537
);
526538
})}

apps/dashboard/src/lib/run-label.test.ts

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, expect, it } from 'bun:test';
22

3-
import { formatRunLabel } from './run-label';
3+
import { formatRunDisplay, formatRunLabel } from './run-label';
44

55
describe('formatRunLabel', () => {
66
it('starts with the run display name when available', () => {
@@ -15,15 +15,15 @@ describe('formatRunLabel', () => {
1515
).toBe('dogfood-run-a · 01/06 10:00 · codex · 100%');
1616
});
1717

18-
it('shows DD/MM HH:mm · target · experiment · score', () => {
18+
it('uses a non-default experiment as the primary label when no display name is present', () => {
1919
expect(
2020
formatRunLabel({
2121
target: 'llm-dry-run',
2222
experiment: 'issue-1198',
2323
timestamp: '2026-04-29T09:17:30.111Z',
2424
pass_rate: 0.8,
2525
}),
26-
).toBe('29/04 09:17 · llm-dry-run · issue-1198 · 80%');
26+
).toBe('issue-1198 · 29/04 09:17 · llm-dry-run · 80%');
2727
});
2828

2929
it('omits experiment when it is the default', () => {
@@ -55,4 +55,63 @@ describe('formatRunLabel', () => {
5555
}),
5656
).toBe('07/05 10:56 · wtalms-stg · 0%');
5757
});
58+
59+
it('uses one compact timestamp for remote timestamp-only run names', () => {
60+
const display = formatRunDisplay({
61+
display_name: '2026-03-27T05-00-00-000Z',
62+
filename: 'remote::2026-03-27T05-00-00-000Z',
63+
target: 'av-fis-target',
64+
timestamp: '2026-03-27T05:00:00.000Z',
65+
pass_rate: 1,
66+
});
67+
68+
expect(display.primary).toBe('27/03 05:00');
69+
expect(display.secondary).toBe('av-fis-target · 100%');
70+
expect(display.label).toBe('27/03 05:00 · av-fis-target · 100%');
71+
expect(display.label.match(/27\/03 05:00/g)).toHaveLength(1);
72+
expect(display.title).toContain('Run ID: remote::2026-03-27T05-00-00-000Z');
73+
expect(display.title).toContain('Display name: 2026-03-27T05-00-00-000Z');
74+
});
75+
76+
it('keeps a local human display name as the primary label', () => {
77+
const display = formatRunDisplay({
78+
display_name: 'local fixture run',
79+
filename: '2026-06-08T20-00-00-000Z',
80+
target: 'local-target',
81+
timestamp: '2026-06-08T20:00:00.000Z',
82+
pass_rate: 1,
83+
});
84+
85+
expect(display.primary).toBe('local fixture run');
86+
expect(display.secondary).toBe('08/06 20:00 · local-target · 100%');
87+
expect(display.label).toBe('local fixture run · 08/06 20:00 · local-target · 100%');
88+
});
89+
90+
it('falls back to a non-default experiment before timestamp-only run IDs', () => {
91+
const display = formatRunDisplay({
92+
display_name: '2026-03-27T05-00-00-000Z',
93+
filename: 'remote::smoke-regression::2026-03-27T05-00-00-000Z',
94+
experiment: 'smoke-regression',
95+
target: 'azure',
96+
timestamp: '2026-03-27T05:00:00.000Z',
97+
pass_rate: 0.5,
98+
});
99+
100+
expect(display.primary).toBe('smoke-regression');
101+
expect(display.secondary).toBe('27/03 05:00 · azure · 50%');
102+
});
103+
104+
it('can omit pass rate when another UI column already shows it', () => {
105+
const display = formatRunDisplay(
106+
{
107+
display_name: 'local fixture run',
108+
target: 'local-target',
109+
timestamp: '2026-06-08T20:00:00.000Z',
110+
pass_rate: 1,
111+
},
112+
{ includePassRate: false },
113+
);
114+
115+
expect(display.label).toBe('local fixture run · 08/06 20:00 · local-target');
116+
});
58117
});

0 commit comments

Comments
 (0)