mirror of
https://github.com/BradGroux/veritas-kanban.git
synced 2026-10-06 19:57:50 +00:00
feat(web): consolidate task attempts in History mode (#1348)
This commit is contained in:
parent
0779a11079
commit
8eb306a19e
4 changed files with 81 additions and 10 deletions
|
|
@ -506,7 +506,11 @@ describe('agent run timeline Mantine surface', () => {
|
|||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Run Timeline')).toBeDefined();
|
||||
expect(screen.getByText('Attempt Timeline')).toBeDefined();
|
||||
expect(screen.getByText('Latest attempt')).toBeDefined();
|
||||
expect(screen.getByRole('combobox', { name: 'Attempt' }).getAttribute('value')).toBe(
|
||||
'Latest | attempt-1 | complete'
|
||||
);
|
||||
expect(screen.getByText('Stored replay')).toBeDefined();
|
||||
expect(screen.getByText('command.completed')).toBeDefined();
|
||||
expect(screen.getByText('stream.stdout')).toBeDefined();
|
||||
|
|
@ -538,6 +542,29 @@ describe('agent run timeline Mantine surface', () => {
|
|||
expect(mocks.onOpenTab).toHaveBeenCalledWith('work-products');
|
||||
});
|
||||
|
||||
it('distinguishes an earlier attempt from the latest task execution', () => {
|
||||
const taskWithHistory = createMockTask({
|
||||
...task,
|
||||
attempt: {
|
||||
id: 'attempt-current',
|
||||
agent: 'veritas',
|
||||
status: 'running',
|
||||
started: '2026-06-01T11:00:00.000Z',
|
||||
},
|
||||
attempts: task.attempt ? [task.attempt] : [],
|
||||
});
|
||||
|
||||
renderWithProviders(
|
||||
<AgentRunTimelinePanel task={taskWithHistory} initialAttemptId="attempt-1" />
|
||||
);
|
||||
|
||||
expect(screen.getByText('Historical attempt')).toBeDefined();
|
||||
expect(screen.getByRole('combobox', { name: 'Attempt' }).getAttribute('value')).toBe(
|
||||
'Historical | attempt-1 | completed'
|
||||
);
|
||||
expect(screen.queryByText(/live polling/)).toBeNull();
|
||||
});
|
||||
|
||||
it('highlights a deep-linked telemetry event', () => {
|
||||
renderWithProviders(
|
||||
<AgentRunTimelinePanel
|
||||
|
|
|
|||
|
|
@ -116,7 +116,19 @@ vi.mock('@/components/task/AgentPanel', () => ({
|
|||
}));
|
||||
|
||||
vi.mock('@/components/task/AgentRunTimelinePanel', () => ({
|
||||
AgentRunTimelinePanel: () => <div>Run timeline panel</div>,
|
||||
AgentRunTimelinePanel: ({
|
||||
initialAttemptId,
|
||||
initialEventId,
|
||||
}: {
|
||||
initialAttemptId?: string | null;
|
||||
initialEventId?: string | null;
|
||||
}) => (
|
||||
<div>
|
||||
Run timeline panel
|
||||
<span>Attempt target: {initialAttemptId ?? 'none'}</span>
|
||||
<span>Event target: {initialEventId ?? 'none'}</span>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('@/components/task/DiffViewer', () => ({
|
||||
|
|
@ -537,7 +549,11 @@ describe('task detail Mantine migration', () => {
|
|||
task={codeTask}
|
||||
open
|
||||
onOpenChange={mocks.onOpenChange}
|
||||
navigationTarget={{ tab: 'timeline', timelineAttemptId: 'attempt-1' }}
|
||||
navigationTarget={{
|
||||
tab: 'timeline',
|
||||
timelineAttemptId: 'attempt-1',
|
||||
timelineEventId: 'event-1',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
@ -549,6 +565,8 @@ describe('task detail Mantine migration', () => {
|
|||
expect(screen.getByRole('tab', { name: 'Timeline' }).getAttribute('aria-selected')).toBe(
|
||||
'true'
|
||||
);
|
||||
expect(screen.getByText('Attempt target: attempt-1')).toBeDefined();
|
||||
expect(screen.getByText('Event target: event-1')).toBeDefined();
|
||||
|
||||
rerender(
|
||||
<TaskDetailPanel
|
||||
|
|
|
|||
|
|
@ -899,12 +899,17 @@ function getRunOptions(
|
|||
attempts.set(id, label);
|
||||
};
|
||||
|
||||
const latestAttemptLabel =
|
||||
task.attempt?.status === 'running' || task.attempt?.status === 'pending' ? 'Current' : 'Latest';
|
||||
addAttempt(
|
||||
task.attempt?.id,
|
||||
`${latestAttemptLabel} | ${task.attempt?.id} | ${task.attempt?.status ?? 'unknown'}`
|
||||
);
|
||||
for (const trace of traces) {
|
||||
addAttempt(trace.traceId, `${trace.traceId} (${trace.status})`);
|
||||
addAttempt(trace.traceId, `Historical | ${trace.traceId} | ${trace.status}`);
|
||||
}
|
||||
addAttempt(task.attempt?.id, `${task.attempt?.id} (${task.attempt?.status ?? 'current'})`);
|
||||
for (const attempt of task.attempts ?? []) {
|
||||
addAttempt(attempt.id, `${attempt.id} (${attempt.status})`);
|
||||
addAttempt(attempt.id, `Historical | ${attempt.id} | ${attempt.status}`);
|
||||
}
|
||||
for (const event of telemetryEvents) {
|
||||
addAttempt(getEventAttemptId(event), `${getEventAttemptId(event)} (telemetry)`);
|
||||
|
|
@ -1222,6 +1227,17 @@ export function AgentRunTimelinePanel({
|
|||
(product) => !selectedAttemptId || product.sourceRunId === selectedAttemptId
|
||||
);
|
||||
const pendingTaskApprovals = approvals.filter((approval) => approval.taskId === task.id);
|
||||
const selectedIsLatestAttempt = Boolean(
|
||||
selectedAttemptId && selectedAttemptId === task.attempt?.id
|
||||
);
|
||||
const selectedIsLiveAttempt = hasLiveAttempt && selectedIsLatestAttempt;
|
||||
const selectedAttemptScope = selectedIsLiveAttempt
|
||||
? 'Current attempt'
|
||||
: selectedIsLatestAttempt
|
||||
? 'Latest attempt'
|
||||
: selectedAttemptId
|
||||
? 'Historical attempt'
|
||||
: 'Derived history';
|
||||
|
||||
useEffect(() => {
|
||||
setVisibleCount(TIMELINE_PAGE_SIZE);
|
||||
|
|
@ -1436,7 +1452,8 @@ export function AgentRunTimelinePanel({
|
|||
<ThemeIcon size="sm" radius="xl" variant="light">
|
||||
<History className="h-4 w-4" />
|
||||
</ThemeIcon>
|
||||
<Text fw={700}>Run Timeline</Text>
|
||||
<Text fw={700}>Attempt Timeline</Text>
|
||||
<Badge variant="outline">{selectedAttemptScope}</Badge>
|
||||
<Badge color={SOURCE_COLORS[source]} variant="light">
|
||||
{source === 'live' ? 'Live' : source === 'stored' ? 'Stored replay' : 'Derived'}
|
||||
</Badge>
|
||||
|
|
@ -1451,7 +1468,7 @@ export function AgentRunTimelinePanel({
|
|||
</Group>
|
||||
<Text size="sm" c="dimmed" mt={6}>
|
||||
{selectedAttemptId || 'No attempt selected'} | {events.length} events
|
||||
{hasLiveAttempt ? ' | live polling' : ''}
|
||||
{selectedIsLiveAttempt ? ' | live polling' : ''}
|
||||
</Text>
|
||||
</div>
|
||||
{isLoading && (
|
||||
|
|
@ -1466,8 +1483,8 @@ export function AgentRunTimelinePanel({
|
|||
|
||||
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="sm">
|
||||
<Select
|
||||
label="Run"
|
||||
aria-label="Run"
|
||||
label="Attempt"
|
||||
aria-label="Attempt"
|
||||
value={selectedAttemptId}
|
||||
onChange={setSelectedAttemptId}
|
||||
data={runOptions}
|
||||
|
|
|
|||
|
|
@ -83,6 +83,15 @@ describe('task workspace navigation', () => {
|
|||
resolveTaskDetailNavigationTab({ workspace: { version: 1, mode: 'run', section } }, tabs)
|
||||
).toBe(section);
|
||||
}
|
||||
for (const section of ['timeline', 'metrics'] as const) {
|
||||
expect(resolveTaskDetailNavigationTab({ tab: section }, tabs)).toBe(section);
|
||||
expect(
|
||||
resolveTaskDetailNavigationTab(
|
||||
{ workspace: { version: 1, mode: 'history', section } },
|
||||
tabs
|
||||
)
|
||||
).toBe(section);
|
||||
}
|
||||
const tabsWithWorktree = getAvailableTaskDetailTabMetadata({
|
||||
isCodeTask: true,
|
||||
hasWorktree: true,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue