ExplainVisualizer.tsx51 lines · main
1import { useMemo } from 'react'
2
3import { ExplainHeader } from './ExplainVisualizer.Header'
4import { ExplainNodeRow } from './ExplainVisualizer.NodeRow'
5import { calculateMaxDuration, calculateSummary, createNodeTree } from './ExplainVisualizer.parser'
6import type { QueryPlanRow } from './ExplainVisualizer.types'
7
8export interface ExplainVisualizerProps {
9 rows: readonly QueryPlanRow[]
10 onShowRaw?: () => void
11 id?: string
12}
13
14export function ExplainVisualizer({ rows, onShowRaw, id }: ExplainVisualizerProps) {
15 const parsedTree = useMemo(() => createNodeTree(rows), [rows])
16 const maxDuration = useMemo(() => calculateMaxDuration(parsedTree), [parsedTree])
17 const summary = useMemo(() => calculateSummary(parsedTree), [parsedTree])
18
19 if (parsedTree.length === 0) {
20 return (
21 <div className="bg-studio">
22 <p className="m-0 border-0 px-4 py-3 font-mono text-sm text-foreground-light">
23 No execution plan data available
24 </p>
25 </div>
26 )
27 }
28
29 return (
30 <div className="bg-studio h-full flex flex-col min-h-0">
31 {onShowRaw && (
32 <ExplainHeader
33 mode="visual"
34 onToggleMode={onShowRaw}
35 summary={summary}
36 id={id}
37 rows={rows}
38 />
39 )}
40
41 {/* Plan nodes */}
42 <div className="flex-1 overflow-auto min-h-0">
43 <div className="flex flex-col min-w-max pb-1 divide-y divide-border-muted">
44 {parsedTree.map((node, idx) => (
45 <ExplainNodeRow key={idx} node={node} depth={0} maxDuration={maxDuration} />
46 ))}
47 </div>
48 </div>
49 </div>
50 )
51}