From 92a95ffa023eb44ff149cdf2d7deff6a57e93929 Mon Sep 17 00:00:00 2001 From: Lukas May Date: Fri, 6 Mar 2026 20:14:06 +0100 Subject: [PATCH] feat: wire drilldown dialogs into RadarPage with isAgentRunning prop - Add isAgentRunning? to DrilldownDialogProps interface - Import and render all four dialog components in RadarPage - Replace MetricCell helper with per-type td elements using data-testid attributes for reliable test targeting - Add drilldown state (type/agentId/agentName) and isAgentRunning derivation - Wire non-zero metric cell onClick handlers to open the correct dialog - Zero cells retain no onClick handler - Extend radar.test.tsx with 8 new dialog integration test cases covering open/close behavior and isAgentRunning prop passing Co-Authored-By: Claude Sonnet 4.6 --- apps/web/src/components/radar/types.ts | 1 + apps/web/src/routes/__tests__/radar.test.tsx | 152 +++++++++++++++++++ apps/web/src/routes/radar.tsx | 99 +++++++++--- 3 files changed, 233 insertions(+), 19 deletions(-) diff --git a/apps/web/src/components/radar/types.ts b/apps/web/src/components/radar/types.ts index 67ea1ac..e7dd307 100644 --- a/apps/web/src/components/radar/types.ts +++ b/apps/web/src/components/radar/types.ts @@ -3,4 +3,5 @@ export interface DrilldownDialogProps { onOpenChange: (open: boolean) => void agentId: string agentName: string + isAgentRunning?: boolean } diff --git a/apps/web/src/routes/__tests__/radar.test.tsx b/apps/web/src/routes/__tests__/radar.test.tsx index 5472ff7..01f110e 100644 --- a/apps/web/src/routes/__tests__/radar.test.tsx +++ b/apps/web/src/routes/__tests__/radar.test.tsx @@ -3,6 +3,39 @@ import '@testing-library/jest-dom/vitest' import { render, screen, fireEvent, within } from '@testing-library/react' import { vi, describe, it, expect, beforeEach } from 'vitest' +vi.mock('@/components/radar/CompactionEventsDialog', () => ({ + CompactionEventsDialog: ({ open, agentId, agentName, isAgentRunning, onOpenChange }: any) => + open ? ( +
+ +
+ ) : null, +})) +vi.mock('@/components/radar/SubagentSpawnsDialog', () => ({ + SubagentSpawnsDialog: ({ open, agentId, agentName, isAgentRunning, onOpenChange }: any) => + open ? ( +
+ +
+ ) : null, +})) +vi.mock('@/components/radar/QuestionsAskedDialog', () => ({ + QuestionsAskedDialog: ({ open, agentId, agentName, isAgentRunning, onOpenChange }: any) => + open ? ( +
+ +
+ ) : null, +})) +vi.mock('@/components/radar/InterAgentMessagesDialog', () => ({ + InterAgentMessagesDialog: ({ open, agentId, agentName, isAgentRunning, onOpenChange }: any) => + open ? ( +
+ +
+ ) : null, +})) + type AgentRadarRow = { id: string name: string @@ -277,6 +310,125 @@ describe('RadarPage', () => { const skeletons = document.querySelectorAll('.animate-pulse') expect(skeletons.length).toBeGreaterThanOrEqual(5) }) + + describe('dialog integration', () => { + const alphaAgent = { + id: 'agent-alpha-id', + name: 'agent-alpha', + mode: 'execute', + status: 'running', + initiativeId: null, + initiativeName: null, + taskId: null, + taskName: null, + createdAt: new Date(Date.now() - 3600_000).toISOString(), + questionsCount: 3, + messagesCount: 2, + subagentsCount: 1, + compactionsCount: 4, + } + const betaAgent = { + id: 'agent-beta-id', + name: 'agent-beta', + mode: 'execute', + status: 'stopped', + initiativeId: null, + initiativeName: null, + taskId: null, + taskName: null, + createdAt: new Date(Date.now() - 7200_000).toISOString(), + questionsCount: 0, + messagesCount: 0, + subagentsCount: 0, + compactionsCount: 0, + } + + beforeEach(() => { + mockListForRadarUseQuery.mockReturnValue({ data: [alphaAgent, betaAgent], isLoading: false }) + }) + + it('clicking a non-zero Compactions cell opens CompactionEventsDialog with correct agentId and agentName', async () => { + render() + const cell = screen.getByTestId(`cell-compactions-agent-alpha-id`) + fireEvent.click(cell) + const dialog = await screen.findByTestId('compaction-dialog') + expect(dialog).toBeInTheDocument() + expect(dialog).toHaveAttribute('data-agent-name', 'agent-alpha') + expect(dialog).toHaveAttribute('data-agent-id', 'agent-alpha-id') + }) + + it('clicking a non-zero Subagents cell opens SubagentSpawnsDialog', async () => { + render() + const cell = screen.getByTestId(`cell-subagents-agent-alpha-id`) + fireEvent.click(cell) + const dialog = await screen.findByTestId('subagents-dialog') + expect(dialog).toBeInTheDocument() + expect(dialog).toHaveAttribute('data-agent-name', 'agent-alpha') + expect(dialog).toHaveAttribute('data-agent-id', 'agent-alpha-id') + }) + + it('clicking a non-zero Questions cell opens QuestionsAskedDialog', async () => { + render() + const cell = screen.getByTestId(`cell-questions-agent-alpha-id`) + fireEvent.click(cell) + const dialog = await screen.findByTestId('questions-dialog') + expect(dialog).toBeInTheDocument() + expect(dialog).toHaveAttribute('data-agent-name', 'agent-alpha') + expect(dialog).toHaveAttribute('data-agent-id', 'agent-alpha-id') + }) + + it('clicking a non-zero Messages cell opens InterAgentMessagesDialog', async () => { + render() + const cell = screen.getByTestId(`cell-messages-agent-alpha-id`) + fireEvent.click(cell) + const dialog = await screen.findByTestId('messages-dialog') + expect(dialog).toBeInTheDocument() + expect(dialog).toHaveAttribute('data-agent-name', 'agent-alpha') + expect(dialog).toHaveAttribute('data-agent-id', 'agent-alpha-id') + }) + + it('dialog closes when onOpenChange(false) fires', async () => { + render() + const cell = screen.getByTestId(`cell-compactions-agent-alpha-id`) + fireEvent.click(cell) + const dialog = await screen.findByTestId('compaction-dialog') + expect(dialog).toBeInTheDocument() + fireEvent.click(screen.getByRole('button', { name: 'close' })) + expect(screen.queryByTestId('compaction-dialog')).not.toBeInTheDocument() + }) + + it('zero metric cells do not open any dialog when clicked', () => { + render() + // agent-beta has all zeros — click each zero metric cell + fireEvent.click(screen.getByTestId('cell-compactions-agent-beta-id')) + fireEvent.click(screen.getByTestId('cell-subagents-agent-beta-id')) + fireEvent.click(screen.getByTestId('cell-questions-agent-beta-id')) + fireEvent.click(screen.getByTestId('cell-messages-agent-beta-id')) + expect(screen.queryByTestId('compaction-dialog')).not.toBeInTheDocument() + expect(screen.queryByTestId('subagents-dialog')).not.toBeInTheDocument() + expect(screen.queryByTestId('questions-dialog')).not.toBeInTheDocument() + expect(screen.queryByTestId('messages-dialog')).not.toBeInTheDocument() + }) + + it('passes isAgentRunning=true when the agent status is "running"', async () => { + render() + const cell = screen.getByTestId(`cell-compactions-agent-alpha-id`) + fireEvent.click(cell) + const dialog = await screen.findByTestId('compaction-dialog') + expect(dialog).toHaveAttribute('data-is-running', 'true') + }) + + it('passes isAgentRunning=false when the agent status is "stopped"', async () => { + // Use a stopped agent with non-zero compactions + const stoppedAgent = { ...betaAgent, id: 'stopped-agent-id', name: 'stopped-agent', compactionsCount: 2, status: 'stopped' } + mockListForRadarUseQuery.mockReturnValue({ data: [stoppedAgent], isLoading: false }) + render() + const cell = screen.getByTestId('cell-compactions-stopped-agent-id') + fireEvent.click(cell) + const dialog = await screen.findByTestId('compaction-dialog') + expect(dialog).toHaveAttribute('data-is-running', 'false') + }) + }) }) describe('AppLayout - Radar nav item', () => { diff --git a/apps/web/src/routes/radar.tsx b/apps/web/src/routes/radar.tsx index 8547b6c..7e68b3b 100644 --- a/apps/web/src/routes/radar.tsx +++ b/apps/web/src/routes/radar.tsx @@ -4,6 +4,10 @@ import { trpc } from '@/lib/trpc' import { useLiveUpdates } from '@/hooks' import type { LiveUpdateRule } from '@/hooks' import { Card, CardContent } from '@/components/ui/card' +import { CompactionEventsDialog } from '@/components/radar/CompactionEventsDialog' +import { SubagentSpawnsDialog } from '@/components/radar/SubagentSpawnsDialog' +import { QuestionsAskedDialog } from '@/components/radar/QuestionsAskedDialog' +import { InterAgentMessagesDialog } from '@/components/radar/InterAgentMessagesDialog' type TimeRange = '1h' | '6h' | '24h' | '7d' | 'all' type StatusFilter = 'all' | 'running' | 'completed' | 'crashed' @@ -74,6 +78,14 @@ export function RadarPage() { const { data: initiatives = [] } = trpc.listInitiatives.useQuery() + type DrilldownType = 'questions' | 'messages' | 'subagents' | 'compactions' + + const [drilldown, setDrilldown] = useState<{ + type: DrilldownType + agentId: string + agentName: string + } | null>(null) + const [sortState, setSortState] = useState<{ column: SortColumn; direction: 'asc' | 'desc' }>({ column: 'started', direction: 'desc', @@ -156,21 +168,9 @@ export function RadarPage() { ) } - function MetricCell({ value }: { value: number }) { - if (value > 0) { - return ( - { - // TODO: open drilldown dialog — wired in Phase 4 (Dialog Integration) - }} - > - {value} - - ) - } - return 0 - } + const isAgentRunning = drilldown + ? agents.find((a) => a.id === drilldown.agentId)?.status === 'running' + : false return (
@@ -313,15 +313,76 @@ export function RadarPage() { {new Date(agent.createdAt).toLocaleString()} - - - - + 0 ? 'cursor-pointer hover:bg-muted/50 text-right px-3 py-2' : 'text-muted-foreground text-right px-3 py-2'} + onClick={agent.questionsCount > 0 + ? () => setDrilldown({ type: 'questions', agentId: agent.id, agentName: agent.name }) + : undefined} + > + {agent.questionsCount} + + 0 ? 'cursor-pointer hover:bg-muted/50 text-right px-3 py-2' : 'text-muted-foreground text-right px-3 py-2'} + onClick={agent.messagesCount > 0 + ? () => setDrilldown({ type: 'messages', agentId: agent.id, agentName: agent.name }) + : undefined} + > + {agent.messagesCount} + + 0 ? 'cursor-pointer hover:bg-muted/50 text-right px-3 py-2' : 'text-muted-foreground text-right px-3 py-2'} + onClick={agent.subagentsCount > 0 + ? () => setDrilldown({ type: 'subagents', agentId: agent.id, agentName: agent.name }) + : undefined} + > + {agent.subagentsCount} + + 0 ? 'cursor-pointer hover:bg-muted/50 text-right px-3 py-2' : 'text-muted-foreground text-right px-3 py-2'} + onClick={agent.compactionsCount > 0 + ? () => setDrilldown({ type: 'compactions', agentId: agent.id, agentName: agent.name }) + : undefined} + > + {agent.compactionsCount} + ))} )} + + { if (!open) setDrilldown(null) }} + agentId={drilldown?.agentId ?? ''} + agentName={drilldown?.agentName ?? ''} + isAgentRunning={isAgentRunning} + /> + { if (!open) setDrilldown(null) }} + agentId={drilldown?.agentId ?? ''} + agentName={drilldown?.agentName ?? ''} + isAgentRunning={isAgentRunning} + /> + { if (!open) setDrilldown(null) }} + agentId={drilldown?.agentId ?? ''} + agentName={drilldown?.agentName ?? ''} + isAgentRunning={isAgentRunning} + /> + { if (!open) setDrilldown(null) }} + agentId={drilldown?.agentId ?? ''} + agentName={drilldown?.agentName ?? ''} + isAgentRunning={isAgentRunning} + />
) }