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
|
onOpenChange: (open: boolean) => void
|
||||||
agentId: string
|
agentId: string
|
||||||
agentName: 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 { render, screen, fireEvent, within } from '@testing-library/react'
|
||||||
import { vi, describe, it, expect, beforeEach } from 'vitest'
|
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 = {
|
type AgentRadarRow = {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
@@ -277,6 +310,125 @@ describe('RadarPage', () => {
|
|||||||
const skeletons = document.querySelectorAll('.animate-pulse')
|
const skeletons = document.querySelectorAll('.animate-pulse')
|
||||||
expect(skeletons.length).toBeGreaterThanOrEqual(5)
|
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', () => {
|
describe('AppLayout - Radar nav item', () => {
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import { trpc } from '@/lib/trpc'
|
|||||||
import { useLiveUpdates } from '@/hooks'
|
import { useLiveUpdates } from '@/hooks'
|
||||||
import type { LiveUpdateRule } from '@/hooks'
|
import type { LiveUpdateRule } from '@/hooks'
|
||||||
import { Card, CardContent } from '@/components/ui/card'
|
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 TimeRange = '1h' | '6h' | '24h' | '7d' | 'all'
|
||||||
type StatusFilter = 'all' | 'running' | 'completed' | 'crashed'
|
type StatusFilter = 'all' | 'running' | 'completed' | 'crashed'
|
||||||
@@ -74,6 +78,14 @@ export function RadarPage() {
|
|||||||
|
|
||||||
const { data: initiatives = [] } = trpc.listInitiatives.useQuery()
|
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' }>({
|
const [sortState, setSortState] = useState<{ column: SortColumn; direction: 'asc' | 'desc' }>({
|
||||||
column: 'started',
|
column: 'started',
|
||||||
direction: 'desc',
|
direction: 'desc',
|
||||||
@@ -156,21 +168,9 @@ export function RadarPage() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function MetricCell({ value }: { value: number }) {
|
const isAgentRunning = drilldown
|
||||||
if (value > 0) {
|
? agents.find((a) => a.id === drilldown.agentId)?.status === 'running'
|
||||||
return (
|
: false
|
||||||
<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>
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@@ -313,15 +313,76 @@ export function RadarPage() {
|
|||||||
<td className="px-3 py-2">
|
<td className="px-3 py-2">
|
||||||
{new Date(agent.createdAt).toLocaleString()}
|
{new Date(agent.createdAt).toLocaleString()}
|
||||||
</td>
|
</td>
|
||||||
<MetricCell value={agent.questionsCount} />
|
<td
|
||||||
<MetricCell value={agent.messagesCount} />
|
data-testid={`cell-questions-${agent.id}`}
|
||||||
<MetricCell value={agent.subagentsCount} />
|
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'}
|
||||||
<MetricCell value={agent.compactionsCount} />
|
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>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user