UtilityTabExplain.tsx124 lines · main
1import { Loader2 } from 'lucide-react'
2import { useState } from 'react'
3import { Tooltip, TooltipContent, TooltipTrigger } from 'ui'
4
5import Results from './Results'
6import { ExplainVisualizer } from '@/components/interfaces/ExplainVisualizer/ExplainVisualizer'
7import { ExplainHeader } from '@/components/interfaces/ExplainVisualizer/ExplainVisualizer.Header'
8import {
9 isExplainQuery,
10 isTextFormatExplain,
11} from '@/components/interfaces/ExplainVisualizer/ExplainVisualizer.utils'
12import CopyButton from '@/components/ui/CopyButton'
13import { useSqlEditorV2StateSnapshot } from '@/state/sql-editor-v2'
14
15export type UtilityTabExplainProps = {
16 id: string
17 isExecuting?: boolean
18}
19
20export function UtilityTabExplain({ id, isExecuting }: UtilityTabExplainProps) {
21 const snapV2 = useSqlEditorV2StateSnapshot()
22 const explainResult = snapV2.explainResults[id]
23 const [mode, setMode] = useState<'visual' | 'raw'>('visual')
24
25 if (isExecuting) {
26 return (
27 <div className="flex items-center gap-x-4 px-6 py-4 bg-table-header-light in-data-[theme*=dark]:bg-table-header-dark">
28 <Loader2 size={14} className="animate-spin" />
29 <p className="m-0 border-0 font-mono text-sm">Running EXPLAIN ANALYZE...</p>
30 </div>
31 )
32 }
33
34 if (explainResult?.error) {
35 const formattedError = (explainResult.error?.formattedError?.split('\n') ?? []).filter(
36 (x: string) => x.length > 0
37 )
38
39 return (
40 <div className="bg-table-header-light in-data-[theme*=dark]:bg-table-header-dark overflow-y-auto">
41 <div className="flex flex-row justify-between items-start py-4 px-6 gap-x-4 pb-9">
42 <div className="flex flex-col gap-y-1">
43 {formattedError.length > 0 ? (
44 formattedError.map((x: string, i: number) => (
45 <pre key={`error-${i}`} className="font-mono text-sm text-wrap">
46 {x}
47 </pre>
48 ))
49 ) : (
50 <p className="font-mono text-sm tracking-tight">
51 Error: {explainResult.error?.message}
52 </p>
53 )}
54 </div>
55
56 <div className="flex items-center gap-x-2">
57 {formattedError.length > 0 && (
58 <Tooltip>
59 <TooltipTrigger>
60 <CopyButton iconOnly type="default" text={formattedError.join('\n')} />
61 </TooltipTrigger>
62 <TooltipContent side="bottom" align="center">
63 <span>Copy error</span>
64 </TooltipContent>
65 </Tooltip>
66 )}
67 </div>
68 </div>
69 </div>
70 )
71 }
72
73 if (!explainResult || explainResult.rows.length === 0) {
74 return (
75 <div className="bg-table-header-light in-data-[theme*=dark]:bg-table-header-dark overflow-y-auto">
76 <p className="m-0 border-0 px-4 py-4 text-sm text-foreground-light">
77 No execution plan available. The query will be analyzed when you switch to this tab.
78 </p>
79 </div>
80 )
81 }
82
83 const isValidExplain = isExplainQuery(explainResult.rows)
84 const isTextFormat = isTextFormatExplain(explainResult.rows)
85
86 if (!isValidExplain) {
87 return (
88 <div className="bg-table-header-light in-data-[theme*=dark]:bg-table-header-dark overflow-y-auto">
89 <p className="m-0 border-0 px-4 py-4 text-sm text-foreground-light">
90 Unable to parse explain results. Please try running the query again.
91 </p>
92 </div>
93 )
94 }
95
96 // Handle non-TEXT formats (JSON, YAML, XML) - show raw output only
97 if (!isTextFormat) {
98 return (
99 <div className="h-full flex flex-col pb-9">
100 <div className="px-4 py-3 bg-surface-100 border-b border-default flex items-center gap-2">
101 <span className="text-sm text-foreground-light">
102 Visual execution plan is only available for TEXT format. Showing raw output.
103 </span>
104 </div>
105 <Results rows={explainResult.rows} />
106 </div>
107 )
108 }
109
110 const toggleMode = () => setMode(mode === 'visual' ? 'raw' : 'visual')
111
112 return (
113 <div className="h-full flex flex-col pb-9">
114 {mode === 'visual' ? (
115 <ExplainVisualizer rows={explainResult.rows} onShowRaw={toggleMode} id={id} />
116 ) : (
117 <>
118 <ExplainHeader mode="raw" onToggleMode={toggleMode} id={id} rows={explainResult.rows} />
119 <Results rows={explainResult.rows} />
120 </>
121 )}
122 </div>
123 )
124}