QueryInsightsDetailSheet.tsx162 lines · main
| 1 | import { Loader2 } from 'lucide-react' |
| 2 | import { |
| 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 | |
| 15 | import { QueryDetail } from '../../QueryPerformance/QueryDetail' |
| 16 | import { QueryIndexes } from '../../QueryPerformance/QueryIndexes' |
| 17 | import { buildExplainOptimizationPrompt } from '../../QueryPerformance/QueryPerformance.ai' |
| 18 | import type { ClassifiedQuery } from '../QueryInsightsHealth/QueryInsightsHealth.types' |
| 19 | import { ExplainVisualizer } from '@/components/interfaces/ExplainVisualizer/ExplainVisualizer' |
| 20 | import type { QueryPlanRow } from '@/components/interfaces/ExplainVisualizer/ExplainVisualizer.types' |
| 21 | import { SIDEBAR_KEYS } from '@/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider' |
| 22 | import { useAiAssistantStateSnapshot } from '@/state/ai-assistant-state' |
| 23 | import { useSidebarManagerSnapshot } from '@/state/sidebar-manager-state' |
| 24 | |
| 25 | interface 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 | |
| 38 | export 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 & 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 | } |