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:
@@ -3,4 +3,5 @@ export interface DrilldownDialogProps {
|
||||
onOpenChange: (open: boolean) => void
|
||||
agentId: string
|
||||
agentName: string
|
||||
isAgentRunning?: boolean
|
||||
}
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user