Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions apps/dashboard/src/components/RunList.mobile.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,22 @@ describe('buildRunListItemView', () => {
expect(view.isActive).toBe(true);
expect(view.passing).toBe(false);
});

it('uses compact run display without duplicating the pass-rate column', () => {
const view = buildRunListItemView(
runMeta({
display_name: '2026-03-27T05-00-00-000Z',
filename: 'remote::2026-03-27T05-00-00-000Z',
target: 'remote-target',
timestamp: '2026-03-27T05:00:00.000Z',
pass_rate: 1,
source: 'remote',
}),
0.8,
);

expect(view.display.primary).toBe('27/03 05:00');
expect(view.display.secondary).toBe('remote-target');
expect(view.label).toBe('27/03 05:00 · remote-target');
});
});
64 changes: 48 additions & 16 deletions apps/dashboard/src/components/RunList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
useStudioConfig,
} from '~/lib/api';
import { executionErrorCount } from '~/lib/result-summary';
import { formatRunLabel } from '~/lib/run-label';
import { type RunDisplay, formatRunDisplay } from '~/lib/run-label';
import {
buildCombineSuccessMessage,
buildDeleteSuccessMessage,
Expand Down Expand Up @@ -63,6 +63,7 @@ interface RunListItemView {
ts: { date: string; full: string };
isActive: boolean;
label: string;
display: RunDisplay;
errors: number;
qualityCount: number;
passing: boolean;
Expand Down Expand Up @@ -94,7 +95,8 @@ function formatDate(ts: string | undefined | null): { date: string; full: string
export function buildRunListItemView(run: RunMeta, passThreshold: number): RunListItemView {
const ts = formatDate(run.timestamp);
const isActive = run.status === 'starting' || run.status === 'running';
const label = formatRunLabel(run);
const display = formatRunDisplay(run, { includePassRate: false });
const label = display.label;
const errors = executionErrorCount(run);
const qualityCount = Math.max(0, run.test_count - errors);
const passing = qualityCount > 0 ? run.pass_rate >= passThreshold : errors === 0;
Expand All @@ -107,6 +109,7 @@ export function buildRunListItemView(run: RunMeta, passThreshold: number): RunLi
ts,
isActive,
label,
display,
errors,
qualityCount,
passing,
Expand Down Expand Up @@ -380,8 +383,17 @@ export function RunList({
)}
<div className="space-y-2 sm:hidden">
{runViews.map((view) => {
const { run, ts, label, errors, qualityCount, passedCount, failedCount, metadataDirty } =
view;
const {
run,
ts,
label,
display,
errors,
qualityCount,
passedCount,
failedCount,
metadataDirty,
} = view;
const selectionDisabledReason = runSelectionDisabledReason(run);
const selectable = !selectionDisabledReason && selectableRunIds.includes(run.filename);

Expand Down Expand Up @@ -411,9 +423,15 @@ export function RunList({
<RunNameLink
projectId={projectId}
runId={run.filename}
label={label}
className="block break-all text-sm font-medium text-cyan-400 hover:text-cyan-300 hover:underline"
label={display.primary}
title={display.title}
className="block truncate text-sm font-medium text-cyan-400 hover:text-cyan-300 hover:underline"
/>
{display.secondary ? (
<p className="mt-0.5 truncate text-xs text-gray-500" title={display.title}>
{display.secondary}
</p>
) : null}
<div className="mt-2 flex flex-wrap items-center gap-2">
<SourceBadge source={run.source} />
{metadataDirty ? <PendingSyncBadge /> : null}
Expand Down Expand Up @@ -479,6 +497,7 @@ export function RunList({
run,
ts,
label,
display,
errors,
qualityCount,
passedCount,
Expand Down Expand Up @@ -514,14 +533,25 @@ export function RunList({

{/* Run name */}
<td className="w-[22rem] max-w-[22rem] px-4 py-3">
<div className="flex min-w-0 items-center gap-2">
<RunNameLink
projectId={projectId}
runId={run.filename}
label={label}
className="block min-w-0 truncate font-medium text-cyan-400 hover:text-cyan-300 hover:underline"
/>
{metadataDirty ? <PendingSyncBadge /> : null}
<div className="min-w-0">
<div className="flex min-w-0 items-center gap-2">
<RunNameLink
projectId={projectId}
runId={run.filename}
label={display.primary}
title={display.title}
className="block min-w-0 truncate font-medium text-cyan-400 hover:text-cyan-300 hover:underline"
/>
{metadataDirty ? <PendingSyncBadge /> : null}
</div>
{display.secondary ? (
<div
className="mt-0.5 truncate text-xs text-gray-500"
title={display.title}
>
{display.secondary}
</div>
) : null}
</div>
</td>

Expand Down Expand Up @@ -581,24 +611,26 @@ function RunNameLink({
projectId,
runId,
label,
title,
className,
}: {
projectId?: string;
runId: string;
label: string;
title: string;
className: string;
}) {
return projectId ? (
<Link
to="/projects/$projectId/runs/$runId"
params={{ projectId, runId }}
className={className}
title={label}
title={title}
>
{label}
</Link>
) : (
<Link to="/runs/$runId" params={{ runId }} className={className} title={label}>
<Link to="/runs/$runId" params={{ runId }} className={className} title={title}>
{label}
</Link>
);
Expand Down
28 changes: 20 additions & 8 deletions apps/dashboard/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
useStudioConfig,
} from '~/lib/api';
import { resolveProjectDisplayName } from '~/lib/project-display-name';
import { formatRunLabel, timeAgo } from '~/lib/run-label';
import { formatRunDisplay } from '~/lib/run-label';
import { useSidebarContext } from '~/lib/sidebar-context';

import { BrandName } from './BrandName';
Expand Down Expand Up @@ -88,6 +88,17 @@ function BrandHeader({ projectId }: { projectId?: string }) {
);
}

function SidebarRunText({ display }: { display: ReturnType<typeof formatRunDisplay> }) {
return (
<>
<span className="block truncate">{display.primary}</span>
{display.secondary ? (
<span className="block truncate text-xs text-gray-600">{display.secondary}</span>
) : null}
</>
);
}

function useProjectDisplayName(projectId: string): string {
const { data } = useProjectList();
return resolveProjectDisplayName(projectId, data?.projects);
Expand Down Expand Up @@ -274,6 +285,7 @@ function RunSidebar() {
</div>

{data?.runs.map((run) => {
const display = formatRunDisplay(run);
const isActive =
isHome === false &&
runMatch &&
Expand All @@ -289,10 +301,9 @@ function RunSidebar() {
to="/projects/$projectId/runs/$runId"
params={{ projectId: run.project_id, runId: run.filename }}
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"
title={run.project_name}
title={`${display.title}\nProject: ${run.project_name}`}
>
<span className="block truncate">{formatRunLabel(run)}</span>
<span className="block text-xs text-gray-600">{timeAgo(run.timestamp)}</span>
<SidebarRunText display={display} />
</Link>
);
}
Expand All @@ -307,9 +318,9 @@ function RunSidebar() {
? 'bg-gray-800 text-cyan-400'
: 'text-gray-400 hover:bg-gray-800/50 hover:text-gray-200'
}`}
title={display.title}
>
<span className="block truncate">{formatRunLabel(run)}</span>
<span className="block text-xs text-gray-600">{timeAgo(run.timestamp)}</span>
<SidebarRunText display={display} />
</Link>
);
})}
Expand Down Expand Up @@ -507,6 +518,7 @@ function ProjectRunDetailSidebar({
Runs
</div>
{data?.runs.map((run) => {
const display = formatRunDisplay(run);
const isActive = currentRunId === run.filename;
return (
<Link
Expand All @@ -518,9 +530,9 @@ function ProjectRunDetailSidebar({
? 'bg-gray-800 text-cyan-400'
: 'text-gray-400 hover:bg-gray-800/50 hover:text-gray-200'
}`}
title={display.title}
>
<span className="block truncate">{formatRunLabel(run)}</span>
<span className="block text-xs text-gray-600">{timeAgo(run.timestamp)}</span>
<SidebarRunText display={display} />
</Link>
);
})}
Expand Down
65 changes: 62 additions & 3 deletions apps/dashboard/src/lib/run-label.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'bun:test';

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

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

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

it('omits experiment when it is the default', () => {
Expand Down Expand Up @@ -55,4 +55,63 @@ describe('formatRunLabel', () => {
}),
).toBe('07/05 10:56 · wtalms-stg · 0%');
});

it('uses one compact timestamp for remote timestamp-only run names', () => {
const display = formatRunDisplay({
display_name: '2026-03-27T05-00-00-000Z',
filename: 'remote::2026-03-27T05-00-00-000Z',
target: 'av-fis-target',
timestamp: '2026-03-27T05:00:00.000Z',
pass_rate: 1,
});

expect(display.primary).toBe('27/03 05:00');
expect(display.secondary).toBe('av-fis-target · 100%');
expect(display.label).toBe('27/03 05:00 · av-fis-target · 100%');
expect(display.label.match(/27\/03 05:00/g)).toHaveLength(1);
expect(display.title).toContain('Run ID: remote::2026-03-27T05-00-00-000Z');
expect(display.title).toContain('Display name: 2026-03-27T05-00-00-000Z');
});

it('keeps a local human display name as the primary label', () => {
const display = formatRunDisplay({
display_name: 'local fixture run',
filename: '2026-06-08T20-00-00-000Z',
target: 'local-target',
timestamp: '2026-06-08T20:00:00.000Z',
pass_rate: 1,
});

expect(display.primary).toBe('local fixture run');
expect(display.secondary).toBe('08/06 20:00 · local-target · 100%');
expect(display.label).toBe('local fixture run · 08/06 20:00 · local-target · 100%');
});

it('falls back to a non-default experiment before timestamp-only run IDs', () => {
const display = formatRunDisplay({
display_name: '2026-03-27T05-00-00-000Z',
filename: 'remote::smoke-regression::2026-03-27T05-00-00-000Z',
experiment: 'smoke-regression',
target: 'azure',
timestamp: '2026-03-27T05:00:00.000Z',
pass_rate: 0.5,
});

expect(display.primary).toBe('smoke-regression');
expect(display.secondary).toBe('27/03 05:00 · azure · 50%');
});

it('can omit pass rate when another UI column already shows it', () => {
const display = formatRunDisplay(
{
display_name: 'local fixture run',
target: 'local-target',
timestamp: '2026-06-08T20:00:00.000Z',
pass_rate: 1,
},
{ includePassRate: false },
);

expect(display.label).toBe('local fixture run · 08/06 20:00 · local-target');
});
});
Loading
Loading