feat(studio): move query details to sheet (#38815)
* feat: move query details to sheet This moves the click through on Query Performance to a sheet as opposed to a resizable area. This gives us more space to play with and sets us up for the Query details revamp. * fix: tabs font size * style: expand size of sheet * feat: hasOverlay prop for sheets * feat: add optional overlay for sheets * fix: closing only when clicking outside of rows * style: width of panel on different viewports * fix: horizontal scroll for table * fix: query queries label check in metrics
This commit is contained in:
@@ -22,7 +22,7 @@ export const QUERY_PERFORMANCE_COLUMNS = [
|
|||||||
{ id: 'min_time', name: 'Min time', description: undefined, minWidth: 100 },
|
{ id: 'min_time', name: 'Min time', description: undefined, minWidth: 100 },
|
||||||
{ id: 'rows_read', name: 'Rows processed', description: undefined, minWidth: 130 },
|
{ id: 'rows_read', name: 'Rows processed', description: undefined, minWidth: 130 },
|
||||||
{ id: 'cache_hit_rate', name: 'Cache hit rate', description: undefined, minWidth: 130 },
|
{ id: 'cache_hit_rate', name: 'Cache hit rate', description: undefined, minWidth: 130 },
|
||||||
{ id: 'rolname', name: 'Role', description: undefined, minWidth: 160 },
|
{ id: 'rolname', name: 'Role', description: undefined, minWidth: 200 },
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
export const QUERY_PERFORMANCE_ROLE_DESCRIPTION = [
|
export const QUERY_PERFORMANCE_ROLE_DESCRIPTION = [
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ArrowDown, ArrowUp, ChevronDown, TextSearch, X } from 'lucide-react'
|
import { ArrowDown, ArrowUp, ChevronDown, TextSearch } from 'lucide-react'
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import DataGrid, { Column, DataGridHandle, Row } from 'react-data-grid'
|
import DataGrid, { Column, DataGridHandle, Row } from 'react-data-grid'
|
||||||
|
|
||||||
@@ -10,15 +10,15 @@ import {
|
|||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
ResizableHandle,
|
Sheet,
|
||||||
ResizablePanel,
|
SheetContent,
|
||||||
ResizablePanelGroup,
|
|
||||||
TabsContent_Shadcn_,
|
TabsContent_Shadcn_,
|
||||||
TabsList_Shadcn_,
|
TabsList_Shadcn_,
|
||||||
TabsTrigger_Shadcn_,
|
TabsTrigger_Shadcn_,
|
||||||
Tabs_Shadcn_,
|
Tabs_Shadcn_,
|
||||||
cn,
|
cn,
|
||||||
CodeBlock,
|
CodeBlock,
|
||||||
|
SheetTitle,
|
||||||
} from 'ui'
|
} from 'ui'
|
||||||
import { InfoTooltip } from 'ui-patterns/info-tooltip'
|
import { InfoTooltip } from 'ui-patterns/info-tooltip'
|
||||||
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
|
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
|
||||||
@@ -42,6 +42,7 @@ export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformance
|
|||||||
const gridRef = useRef<DataGridHandle>(null)
|
const gridRef = useRef<DataGridHandle>(null)
|
||||||
const { sort: urlSort, order, roles, search } = useParams()
|
const { sort: urlSort, order, roles, search } = useParams()
|
||||||
const { isLoading, data } = queryPerformanceQuery
|
const { isLoading, data } = queryPerformanceQuery
|
||||||
|
const dataGridContainerRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
const [view, setView] = useState<'details' | 'suggestion'>('details')
|
const [view, setView] = useState<'details' | 'suggestion'>('details')
|
||||||
const [selectedRow, setSelectedRow] = useState<number>()
|
const [selectedRow, setSelectedRow] = useState<number>()
|
||||||
@@ -313,6 +314,7 @@ export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformance
|
|||||||
|
|
||||||
return rawData
|
return rawData
|
||||||
}, [data, sort])
|
}, [data, sort])
|
||||||
|
|
||||||
const selectedQuery = selectedRow !== undefined ? reportData[selectedRow]?.query : undefined
|
const selectedQuery = selectedRow !== undefined ? reportData[selectedRow]?.query : undefined
|
||||||
const query = (selectedQuery ?? '').trim().toLowerCase()
|
const query = (selectedQuery ?? '').trim().toLowerCase()
|
||||||
const showIndexSuggestions =
|
const showIndexSuggestions =
|
||||||
@@ -364,12 +366,8 @@ export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformance
|
|||||||
}, [handleKeyDown])
|
}, [handleKeyDown])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ResizablePanelGroup
|
<div className="relative flex flex-grow bg-alternative min-h-0">
|
||||||
direction="horizontal"
|
<div ref={dataGridContainerRef} className="flex-1 min-w-0 overflow-x-auto">
|
||||||
className="relative flex flex-grow bg-alternative min-h-0"
|
|
||||||
autoSaveId="query-performance-layout-v1"
|
|
||||||
>
|
|
||||||
<ResizablePanel defaultSize={1}>
|
|
||||||
<DataGrid
|
<DataGrid
|
||||||
ref={gridRef}
|
ref={gridRef}
|
||||||
style={{ height: '100%' }}
|
style={{ height: '100%' }}
|
||||||
@@ -393,7 +391,9 @@ export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformance
|
|||||||
<Row
|
<Row
|
||||||
{...props}
|
{...props}
|
||||||
key={`qp-row-${props.rowIdx}`}
|
key={`qp-row-${props.rowIdx}`}
|
||||||
onClick={() => {
|
onClick={(event) => {
|
||||||
|
event.stopPropagation()
|
||||||
|
|
||||||
if (typeof idx === 'number' && idx >= 0) {
|
if (typeof idx === 'number' && idx >= 0) {
|
||||||
setSelectedRow(idx)
|
setSelectedRow(idx)
|
||||||
gridRef.current?.scrollToCell({ idx: 0, rowIdx: idx })
|
gridRef.current?.scrollToCell({ idx: 0, rowIdx: idx })
|
||||||
@@ -424,58 +424,70 @@ export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformance
|
|||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</ResizablePanel>
|
</div>
|
||||||
{selectedRow !== undefined && (
|
|
||||||
<>
|
<Sheet
|
||||||
<ResizableHandle withHandle />
|
open={selectedRow !== undefined}
|
||||||
<ResizablePanel defaultSize={30} maxSize={45} minSize={30} className="bg-studio border-t">
|
onOpenChange={(open) => {
|
||||||
<Button
|
if (!open) {
|
||||||
type="text"
|
setSelectedRow(undefined)
|
||||||
className="absolute top-3 right-3 px-1"
|
}
|
||||||
icon={<X />}
|
}}
|
||||||
onClick={() => setSelectedRow(undefined)}
|
modal={false}
|
||||||
/>
|
>
|
||||||
<Tabs_Shadcn_
|
<SheetTitle className="sr-only">Query details</SheetTitle>
|
||||||
value={view}
|
<SheetContent
|
||||||
className="flex flex-col h-full"
|
side="right"
|
||||||
onValueChange={(value: any) => setView(value)}
|
className="flex flex-col h-full bg-studio border-l lg:!w-[calc(100vw-802px)] max-w-[700px] w-full"
|
||||||
>
|
hasOverlay={false}
|
||||||
<TabsList_Shadcn_ className="px-5 flex gap-x-4 min-h-[46px]">
|
onInteractOutside={(event) => {
|
||||||
|
if (dataGridContainerRef.current?.contains(event.target as Node)) {
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tabs_Shadcn_
|
||||||
|
value={view}
|
||||||
|
className="flex flex-col h-full"
|
||||||
|
onValueChange={(value: any) => setView(value)}
|
||||||
|
>
|
||||||
|
<div className="px-5 border-b">
|
||||||
|
<TabsList_Shadcn_ className="px-0 flex gap-x-4 min-h-[46px] border-b-0 [&>button]:h-[47px]">
|
||||||
<TabsTrigger_Shadcn_
|
<TabsTrigger_Shadcn_
|
||||||
value="details"
|
value="details"
|
||||||
className="px-0 pb-0 h-full text-xs data-[state=active]:bg-transparent !shadow-none"
|
className="px-0 pb-0 data-[state=active]:bg-transparent !shadow-none"
|
||||||
>
|
>
|
||||||
Query details
|
Query details
|
||||||
</TabsTrigger_Shadcn_>
|
</TabsTrigger_Shadcn_>
|
||||||
{showIndexSuggestions && (
|
{showIndexSuggestions && (
|
||||||
<TabsTrigger_Shadcn_
|
<TabsTrigger_Shadcn_
|
||||||
value="suggestion"
|
value="suggestion"
|
||||||
className="px-0 pb-0 h-full text-xs data-[state=active]:bg-transparent !shadow-none"
|
className="px-0 pb-0 data-[state=active]:bg-transparent !shadow-none"
|
||||||
>
|
>
|
||||||
Indexes
|
Indexes
|
||||||
</TabsTrigger_Shadcn_>
|
</TabsTrigger_Shadcn_>
|
||||||
)}
|
)}
|
||||||
</TabsList_Shadcn_>
|
</TabsList_Shadcn_>
|
||||||
<TabsContent_Shadcn_
|
</div>
|
||||||
value="details"
|
|
||||||
className="mt-0 flex-grow min-h-0 overflow-y-auto"
|
<TabsContent_Shadcn_ value="details" className="mt-0 flex-grow min-h-0 overflow-y-auto">
|
||||||
>
|
{selectedRow !== undefined && (
|
||||||
<QueryDetail
|
<QueryDetail
|
||||||
reportType={reportType}
|
reportType={reportType}
|
||||||
selectedRow={reportData[selectedRow]}
|
selectedRow={reportData[selectedRow]}
|
||||||
onClickViewSuggestion={() => setView('suggestion')}
|
onClickViewSuggestion={() => setView('suggestion')}
|
||||||
/>
|
/>
|
||||||
</TabsContent_Shadcn_>
|
)}
|
||||||
<TabsContent_Shadcn_
|
</TabsContent_Shadcn_>
|
||||||
value="suggestion"
|
<TabsContent_Shadcn_
|
||||||
className="mt-0 flex-grow min-h-0 overflow-y-auto"
|
value="suggestion"
|
||||||
>
|
className="mt-0 flex-grow min-h-0 overflow-y-auto"
|
||||||
<QueryIndexes selectedRow={reportData[selectedRow]} />
|
>
|
||||||
</TabsContent_Shadcn_>
|
{selectedRow !== undefined && <QueryIndexes selectedRow={reportData[selectedRow]} />}
|
||||||
</Tabs_Shadcn_>
|
</TabsContent_Shadcn_>
|
||||||
</ResizablePanel>
|
</Tabs_Shadcn_>
|
||||||
</>
|
</SheetContent>
|
||||||
)}
|
</Sheet>
|
||||||
</ResizablePanelGroup>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
|
|
||||||
import { Skeleton } from 'ui'
|
import { Skeleton } from 'ui'
|
||||||
import { useQueryPerformanceQuery } from '../Reports/Reports.queries'
|
import { useQueryPerformanceQuery } from '../Reports/Reports.queries'
|
||||||
@@ -11,7 +11,7 @@ export const QueryPerformanceMetrics = () => {
|
|||||||
const stats = useMemo(() => {
|
const stats = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
title: queryMetrics?.[0]?.slow_queries === '1' ? 'Slow Query' : 'Slow Queries',
|
title: queryMetrics?.[0]?.slow_queries === 1 ? 'Slow Query' : 'Slow Queries',
|
||||||
value: queryMetrics?.[0]?.slow_queries || '0',
|
value: queryMetrics?.[0]?.slow_queries || '0',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -28,11 +28,8 @@ export const QueryPerformanceMetrics = () => {
|
|||||||
return (
|
return (
|
||||||
<section className="px-6 pt-2 pb-4 flex flex-wrap gap-x-6 gap-y-2 w-full">
|
<section className="px-6 pt-2 pb-4 flex flex-wrap gap-x-6 gap-y-2 w-full">
|
||||||
{stats.map((card, i) => (
|
{stats.map((card, i) => (
|
||||||
<>
|
<React.Fragment key={i}>
|
||||||
<div
|
<div className="flex items-baseline gap-2 heading-subSection text-foreground-light">
|
||||||
key={i}
|
|
||||||
className="flex items-baseline gap-2 heading-subSection text-foreground-light"
|
|
||||||
>
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Skeleton className="h-5 w-24" />
|
<Skeleton className="h-5 w-24" />
|
||||||
) : (
|
) : (
|
||||||
@@ -43,7 +40,7 @@ export const QueryPerformanceMetrics = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{i < stats.length - 1 && <span className="text-foreground-muted">/</span>}
|
{i < stats.length - 1 && <span className="text-foreground-muted">/</span>}
|
||||||
</>
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -151,14 +151,15 @@ export interface DialogContentProps
|
|||||||
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
|
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
|
||||||
VariantProps<typeof sheetVariants> {
|
VariantProps<typeof sheetVariants> {
|
||||||
showClose?: boolean
|
showClose?: boolean
|
||||||
|
hasOverlay?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const SheetContent = React.forwardRef<
|
const SheetContent = React.forwardRef<
|
||||||
React.ElementRef<typeof SheetPrimitive.Content>,
|
React.ElementRef<typeof SheetPrimitive.Content>,
|
||||||
DialogContentProps
|
DialogContentProps
|
||||||
>(({ side, size, className, children, showClose = true, ...props }, ref) => (
|
>(({ side, size, className, children, showClose = true, hasOverlay = true, ...props }, ref) => (
|
||||||
<SheetPortal side={side}>
|
<SheetPortal side={side}>
|
||||||
<SheetOverlay />
|
{hasOverlay && <SheetOverlay />}
|
||||||
<SheetPrimitive.Content
|
<SheetPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(sheetVariants({ side, size }), className)}
|
className={cn(sheetVariants({ side, size }), className)}
|
||||||
|
|||||||
Reference in New Issue
Block a user