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 <noreply@anthropic.com>
This commit is contained in:
Lukas May
2026-03-06 20:14:06 +01:00
parent 9bd56ce4a1
commit 92a95ffa02
3 changed files with 233 additions and 19 deletions

View File

@@ -3,4 +3,5 @@ export interface DrilldownDialogProps {
onOpenChange: (open: boolean) => void
agentId: string
agentName: string
isAgentRunning?: boolean
}

View File

@@ -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 ? (
<div data-testid="compaction-dialog" data-agent-id={agentId} data-agent-name={agentName} data-is-running={String(isAgentRunning)}>
<button onClick={() => onOpenChange(false)}>close</button>
</div>
) : null,
}))
vi.mock('@/components/radar/SubagentSpawnsDialog', () => ({
SubagentSpawnsDialog: ({ open, agentId, agentName, isAgentRunning, onOpenChange }: any) =>
open ? (
<div data-testid="subagents-dialog" data-agent-id={agentId} data-agent-name={agentName} data-is-running={String(isAgentRunning)}>
<button onClick={() => onOpenChange(false)}>close</button>
</div>
) : null,
}))
vi.mock('@/components/radar/QuestionsAskedDialog', () => ({
QuestionsAskedDialog: ({ open, agentId, agentName, isAgentRunning, onOpenChange }: any) =>
open ? (
<div data-testid="questions-dialog" data-agent-id={agentId} data-agent-name={agentName} data-is-running={String(isAgentRunning)}>
<button onClick={() => onOpenChange(false)}>close</button>
</div>
) : null,
}))
vi.mock('@/components/radar/InterAgentMessagesDialog', () => ({
InterAgentMessagesDialog: ({ open, agentId, agentName, isAgentRunning, onOpenChange }: any) =>
open ? (
<div data-testid="messages-dialog" data-agent-id={agentId} data-agent-name={agentName} data-is-running={String(isAgentRunning)}>
<button onClick={() => onOpenChange(false)}>close</button>
</div>
) : 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(<RadarPage />)
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(<RadarPage />)
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(<RadarPage />)
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(<RadarPage />)
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(<RadarPage />)
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(<RadarPage />)
// 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(<RadarPage />)
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(<RadarPage />)
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', () => {

View File

@@ -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 (
<td
className="cursor-pointer hover:bg-muted/50 text-right px-3 py-2"
onClick={() => {
// TODO: open drilldown dialog — wired in Phase 4 (Dialog Integration)
}}
>
{value}
</td>
)
}
return <td className="text-muted-foreground text-right px-3 py-2">0</td>
}
const isAgentRunning = drilldown
? agents.find((a) => a.id === drilldown.agentId)?.status === 'running'
: false
return (
<div className="space-y-6">
@@ -313,15 +313,76 @@ export function RadarPage() {
<td className="px-3 py-2">
{new Date(agent.createdAt).toLocaleString()}
</td>
<MetricCell value={agent.questionsCount} />
<MetricCell value={agent.messagesCount} />
<MetricCell value={agent.subagentsCount} />
<MetricCell value={agent.compactionsCount} />
<td
data-testid={`cell-questions-${agent.id}`}
className={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.questionsCount > 0
? () => setDrilldown({ type: 'questions', agentId: agent.id, agentName: agent.name })
: undefined}
>
{agent.questionsCount}
</td>
<td
data-testid={`cell-messages-${agent.id}`}
className={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.messagesCount > 0
? () => setDrilldown({ type: 'messages', agentId: agent.id, agentName: agent.name })
: undefined}
>
{agent.messagesCount}
</td>
<td
data-testid={`cell-subagents-${agent.id}`}
className={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.subagentsCount > 0
? () => setDrilldown({ type: 'subagents', agentId: agent.id, agentName: agent.name })
: undefined}
>
{agent.subagentsCount}
</td>
<td
data-testid={`cell-compactions-${agent.id}`}
className={agent.compactionsCount > 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}
</td>
</tr>
))}
</tbody>
</table>
)}
<CompactionEventsDialog
open={drilldown?.type === 'compactions'}
onOpenChange={(open) => { if (!open) setDrilldown(null) }}
agentId={drilldown?.agentId ?? ''}
agentName={drilldown?.agentName ?? ''}
isAgentRunning={isAgentRunning}
/>
<SubagentSpawnsDialog
open={drilldown?.type === 'subagents'}
onOpenChange={(open) => { if (!open) setDrilldown(null) }}
agentId={drilldown?.agentId ?? ''}
agentName={drilldown?.agentName ?? ''}
isAgentRunning={isAgentRunning}
/>
<QuestionsAskedDialog
open={drilldown?.type === 'questions'}
onOpenChange={(open) => { if (!open) setDrilldown(null) }}
agentId={drilldown?.agentId ?? ''}
agentName={drilldown?.agentName ?? ''}
isAgentRunning={isAgentRunning}
/>
<InterAgentMessagesDialog
open={drilldown?.type === 'messages'}
onOpenChange={(open) => { if (!open) setDrilldown(null) }}
agentId={drilldown?.agentId ?? ''}
agentName={drilldown?.agentName ?? ''}
isAgentRunning={isAgentRunning}
/>
</div>
)
}