QueryInsightsDetailSheet.tsx162 lines · main
1import { Loader2 } from 'lucide-react'
2import {
3 AiIconAnimation,
4 Button,
5 Sheet,
6 SheetContent,
7 SheetDescription,
8 SheetTitle,
9 Tabs_Shadcn_,
10 TabsContent_Shadcn_,
11 TabsList_Shadcn_,
12 TabsTrigger_Shadcn_,
13} from 'ui'
14
15import { QueryDetail } from '../../QueryPerformance/QueryDetail'
16import { QueryIndexes } from '../../QueryPerformance/QueryIndexes'
17import { buildExplainOptimizationPrompt } from '../../QueryPerformance/QueryPerformance.ai'
18import type { ClassifiedQuery } from '../QueryInsightsHealth/QueryInsightsHealth.types'
19import { ExplainVisualizer } from '@/components/interfaces/ExplainVisualizer/ExplainVisualizer'
20import type { QueryPlanRow } from '@/components/interfaces/ExplainVisualizer/ExplainVisualizer.types'
21import { SIDEBAR_KEYS } from '@/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
22import { useAiAssistantStateSnapshot } from '@/state/ai-assistant-state'
23import { useSidebarManagerSnapshot } from '@/state/sidebar-manager-state'
24
25interface QueryInsightsDetailSheetProps {
26 open: boolean
27 onOpenChange: (open: boolean) => void
28 activeSheetRow: ClassifiedQuery | undefined
29 sheetView: 'details' | 'indexes' | 'explain'
30 onSheetViewChange: (view: 'details' | 'indexes' | 'explain') => void
31 onClose: () => void
32 dataGridContainerRef: React.RefObject<HTMLDivElement | null>
33 triageContainerRef: React.RefObject<HTMLDivElement | null>
34 explainLoadingQuery: string | null
35 explainResults: Record<string, QueryPlanRow[]>
36}
37
38export const QueryInsightsDetailSheet = ({
39 open,
40 onOpenChange,
41 activeSheetRow,
42 sheetView,
43 onSheetViewChange,
44 onClose,
45 dataGridContainerRef,
46 triageContainerRef,
47 explainLoadingQuery,
48 explainResults,
49}: QueryInsightsDetailSheetProps) => {
50 const { openSidebar } = useSidebarManagerSnapshot()
51 const aiSnap = useAiAssistantStateSnapshot()
52
53 return (
54 <Sheet open={open} onOpenChange={onOpenChange} modal={false}>
55 <SheetTitle className="sr-only">Query details</SheetTitle>
56 <SheetDescription className="sr-only">Query Insights Details &amp; Indexes</SheetDescription>
57 <SheetContent
58 side="right"
59 className="flex flex-col h-full bg-studio border-l lg:w-[calc(100vw-802px)]! max-w-[700px] w-full"
60 hasOverlay={false}
61 onInteractOutside={(event) => {
62 if (
63 dataGridContainerRef.current?.contains(event.target as Node) ||
64 triageContainerRef.current?.contains(event.target as Node)
65 ) {
66 event.preventDefault()
67 }
68 }}
69 >
70 <Tabs_Shadcn_
71 value={sheetView}
72 className="flex flex-col h-full"
73 onValueChange={(v) => onSheetViewChange(v as 'details' | 'indexes' | 'explain')}
74 >
75 <div className="px-5 border-b">
76 <TabsList_Shadcn_ className="px-0 flex gap-x-4 min-h-[46px] border-b-0 [&>button]:h-[47px]">
77 <TabsTrigger_Shadcn_
78 value="details"
79 className="px-0 pb-0 data-[state=active]:bg-transparent shadow-none!"
80 >
81 Query details
82 </TabsTrigger_Shadcn_>
83 <TabsTrigger_Shadcn_
84 value="indexes"
85 className="px-0 pb-0 data-[state=active]:bg-transparent shadow-none!"
86 >
87 Indexes
88 </TabsTrigger_Shadcn_>
89 {activeSheetRow?.issueType !== 'error' && (
90 <TabsTrigger_Shadcn_
91 value="explain"
92 className="px-0 pb-0 data-[state=active]:bg-transparent shadow-none!"
93 >
94 Explain
95 </TabsTrigger_Shadcn_>
96 )}
97 </TabsList_Shadcn_>
98 </div>
99 <TabsContent_Shadcn_ value="details" className="mt-0 grow min-h-0 overflow-y-auto">
100 {activeSheetRow && (
101 <QueryDetail
102 selectedRow={activeSheetRow}
103 onClickViewSuggestion={() => onSheetViewChange('indexes')}
104 onClose={onClose}
105 />
106 )}
107 </TabsContent_Shadcn_>
108 <TabsContent_Shadcn_ value="indexes" className="mt-0 grow min-h-0 overflow-y-auto">
109 {activeSheetRow && <QueryIndexes selectedRow={activeSheetRow} />}
110 </TabsContent_Shadcn_>
111 <TabsContent_Shadcn_
112 value="explain"
113 className="mt-0 grow min-h-0 flex flex-col overflow-hidden"
114 >
115 {explainLoadingQuery ? (
116 <div className="px-6 py-4 flex items-center gap-2 text-sm text-foreground-light">
117 <Loader2 size={14} className="animate-spin" /> Running EXPLAIN ANALYZE...
118 </div>
119 ) : activeSheetRow && explainResults[activeSheetRow.query]?.length > 0 ? (
120 <>
121 <div className="flex items-center justify-between px-5 py-2 border-b shrink-0">
122 <p className="text-xs text-foreground-lighter">EXPLAIN ANALYZE output</p>
123 <Button
124 type="default"
125 size="tiny"
126 icon={<AiIconAnimation size={14} />}
127 onClick={() => {
128 const rows = explainResults[activeSheetRow.query]
129 const { query, prompt } = buildExplainOptimizationPrompt(
130 activeSheetRow.query,
131 rows,
132 {
133 mean_time: activeSheetRow.mean_time,
134 calls: activeSheetRow.calls,
135 total_time: activeSheetRow.total_time,
136 }
137 )
138 openSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
139 aiSnap.newChat({
140 sqlSnippets: [{ label: 'Query', content: query }],
141 initialMessage: prompt,
142 })
143 }}
144 >
145 Optimize with AI
146 </Button>
147 </div>
148 <div className="flex-1 min-h-0 overflow-y-auto">
149 <ExplainVisualizer rows={explainResults[activeSheetRow.query]} />
150 </div>
151 </>
152 ) : (
153 <div className="px-6 py-4 text-sm text-foreground-lighter">
154 No explain results available.
155 </div>
156 )}
157 </TabsContent_Shadcn_>
158 </Tabs_Shadcn_>
159 </SheetContent>
160 </Sheet>
161 )
162}