UtilityPanel.tsx213 lines · main
1// @ts-nocheck
2import { useParams } from 'common'
3import { toast } from 'sonner'
4import { Tabs_Shadcn_, TabsContent_Shadcn_, TabsList_Shadcn_, TabsTrigger_Shadcn_ } from 'ui'
5
6import { ChartConfig } from './ChartConfig'
7import { UtilityActions } from './UtilityActions'
8import { UtilityTabExplain } from './UtilityTabExplain'
9import { UtilityTabResults } from './UtilityTabResults'
10import { DownloadResultsButton } from '@/components/ui/DownloadResultsButton'
11import { useContentUpsertMutation } from '@/data/content/content-upsert-mutation'
12import { Snippet } from '@/data/content/sql-folders-query'
13import { useSendEventMutation } from '@/data/telemetry/send-event-mutation'
14import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
15import { useSqlEditorV2StateSnapshot } from '@/state/sql-editor-v2'
16
17export type UtilityPanelProps = {
18 id: string
19 isExecuting?: boolean
20 isExplainExecuting?: boolean
21 isDebugging?: boolean
22 isDisabled?: boolean
23 hasSelection: boolean
24 prettifyQuery: () => void
25 executeQuery: () => void
26 executeExplainQuery: () => void
27 onDebug: () => void
28 buildDebugPrompt: () => string
29 activeTab?: string
30 onActiveTabChange?: (tab: string) => void
31}
32
33const DEFAULT_CHART_CONFIG: ChartConfig = {
34 type: 'bar',
35 cumulative: false,
36 xKey: '',
37 yKey: '',
38 showLabels: false,
39 showGrid: false,
40}
41
42export const UtilityPanel = ({
43 id,
44 isExecuting,
45 isExplainExecuting,
46 isDebugging,
47 isDisabled,
48 hasSelection,
49 prettifyQuery,
50 executeQuery,
51 executeExplainQuery,
52 onDebug,
53 buildDebugPrompt,
54 activeTab = 'results',
55 onActiveTabChange,
56}: UtilityPanelProps) => {
57 const { ref } = useParams()
58 const { data: org } = useSelectedOrganizationQuery()
59 const snapV2 = useSqlEditorV2StateSnapshot()
60
61 const snippet = snapV2.snippets[id]?.snippet
62 const result = snapV2.results[id]?.[0]
63
64 const handleTabChange = (tab: string) => {
65 // When switching to the explain tab, trigger the explain query
66 if (tab === 'explain') {
67 executeExplainQuery()
68 }
69 onActiveTabChange?.(tab)
70 }
71
72 const { mutate: sendEvent } = useSendEventMutation()
73
74 const { mutate: upsertContent } = useContentUpsertMutation({
75 invalidateQueriesOnSuccess: false,
76 // Optimistic update to the cache
77 onMutate: async (newContentSnippet) => {
78 const { payload } = newContentSnippet
79
80 // No need to update the cache for non-SQL content
81 if (payload.type !== 'sql') return
82 if (!('chart' in payload.content)) return
83
84 const newSnippet = {
85 ...snippet,
86 content: {
87 ...snippet.content,
88 chart: payload.content.chart,
89 },
90 }
91
92 snapV2.updateSnippet({ id, snippet: newSnippet as unknown as Snippet })
93 },
94 onError: async (_err, _newContent, _context) => {
95 toast.error(`Failed to update chart. Please try again.`)
96 },
97 })
98
99 function getChartConfig() {
100 if (!snippet || snippet.type !== 'sql') {
101 return DEFAULT_CHART_CONFIG
102 }
103
104 if (!snippet.content?.chart) {
105 return DEFAULT_CHART_CONFIG
106 }
107
108 return snippet.content.chart
109 }
110
111 const chartConfig = getChartConfig()
112
113 function onConfigChange(config: ChartConfig) {
114 if (!ref || !snippet?.id) return
115
116 upsertContent({
117 projectRef: ref,
118 payload: {
119 ...snippet,
120 id: snippet.id,
121 description: snippet.description || '',
122 project_id: snippet.project_id || 0,
123 content: {
124 ...snippet.content,
125 content_id: id,
126 chart: config,
127 },
128 },
129 })
130 }
131
132 return (
133 <Tabs_Shadcn_
134 value={activeTab}
135 onValueChange={handleTabChange}
136 className="w-full h-full flex flex-col"
137 >
138 <TabsList_Shadcn_ className="flex justify-between gap-2 px-4 overflow-x-auto min-h-[42px]">
139 <div className="flex items-center gap-4">
140 <TabsTrigger_Shadcn_ className="py-3 text-xs" value="results">
141 <span className="translate-y-px">Results</span>
142 </TabsTrigger_Shadcn_>
143 <TabsTrigger_Shadcn_ className="py-3 text-xs" value="explain">
144 <span className="translate-y-px">Explain</span>
145 </TabsTrigger_Shadcn_>
146 <TabsTrigger_Shadcn_ className="py-3 text-xs" value="chart">
147 <span className="translate-y-px">Chart</span>
148 </TabsTrigger_Shadcn_>
149
150 {result?.rows && (
151 <DownloadResultsButton
152 type="text"
153 results={result.rows as any[]}
154 fileName={`Briven Snippet ${snippet.name}`}
155 onDownloadAsCSV={() =>
156 sendEvent({
157 action: 'sql_editor_result_download_csv_clicked',
158 groups: { project: ref ?? '', organization: org?.slug ?? '' },
159 })
160 }
161 onCopyAsMarkdown={() => {
162 sendEvent({
163 action: 'sql_editor_result_copy_markdown_clicked',
164 groups: { project: ref ?? '', organization: org?.slug ?? '' },
165 })
166 }}
167 onCopyAsJSON={() => {
168 sendEvent({
169 action: 'sql_editor_result_copy_json_clicked',
170 groups: { project: ref ?? '', organization: org?.slug ?? '' },
171 })
172 }}
173 onCopyAsCSV={() => {
174 sendEvent({
175 action: 'sql_editor_result_copy_csv_clicked',
176 groups: { project: ref ?? '', organization: org?.slug ?? '' },
177 })
178 }}
179 />
180 )}
181 </div>
182
183 <UtilityActions
184 id={id}
185 isExecuting={isExecuting}
186 isDisabled={isDisabled}
187 hasSelection={hasSelection}
188 prettifyQuery={prettifyQuery}
189 executeQuery={executeQuery}
190 />
191 </TabsList_Shadcn_>
192
193 <TabsContent_Shadcn_ asChild value="results" className="mt-0 grow">
194 <UtilityTabResults
195 id={id}
196 isExecuting={isExecuting}
197 isDisabled={isDisabled}
198 onDebug={onDebug}
199 buildDebugPrompt={buildDebugPrompt}
200 isDebugging={isDebugging}
201 />
202 </TabsContent_Shadcn_>
203
204 <TabsContent_Shadcn_ asChild value="explain" className="mt-0 grow">
205 <UtilityTabExplain id={id} isExecuting={isExplainExecuting} />
206 </TabsContent_Shadcn_>
207
208 <TabsContent_Shadcn_ asChild value="chart" className="mt-0 grow">
209 <ChartConfig results={result} config={chartConfig} onConfigChange={onConfigChange} />
210 </TabsContent_Shadcn_>
211 </Tabs_Shadcn_>
212 )
213}