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: 'rows_read', name: 'Rows processed', 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
|
||||
|
||||
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 DataGrid, { Column, DataGridHandle, Row } from 'react-data-grid'
|
||||
|
||||
@@ -10,15 +10,15 @@ import {
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
Sheet,
|
||||
SheetContent,
|
||||
TabsContent_Shadcn_,
|
||||
TabsList_Shadcn_,
|
||||
TabsTrigger_Shadcn_,
|
||||
Tabs_Shadcn_,
|
||||
cn,
|
||||
CodeBlock,
|
||||
SheetTitle,
|
||||
} from 'ui'
|
||||
import { InfoTooltip } from 'ui-patterns/info-tooltip'
|
||||
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
|
||||
@@ -42,6 +42,7 @@ export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformance
|
||||
const gridRef = useRef<DataGridHandle>(null)
|
||||
const { sort: urlSort, order, roles, search } = useParams()
|
||||
const { isLoading, data } = queryPerformanceQuery
|
||||
const dataGridContainerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const [view, setView] = useState<'details' | 'suggestion'>('details')
|
||||
const [selectedRow, setSelectedRow] = useState<number>()
|
||||
@@ -313,6 +314,7 @@ export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformance
|
||||
|
||||
return rawData
|
||||
}, [data, sort])
|
||||
|
||||
const selectedQuery = selectedRow !== undefined ? reportData[selectedRow]?.query : undefined
|
||||
const query = (selectedQuery ?? '').trim().toLowerCase()
|
||||
const showIndexSuggestions =
|
||||
@@ -364,12 +366,8 @@ export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformance
|
||||
}, [handleKeyDown])
|
||||
|
||||
return (
|
||||
<ResizablePanelGroup
|
||||
direction="horizontal"
|
||||
className="relative flex flex-grow bg-alternative min-h-0"
|
||||
autoSaveId="query-performance-layout-v1"
|
||||
>
|
||||
<ResizablePanel defaultSize={1}>
|
||||
<div className="relative flex flex-grow bg-alternative min-h-0">
|
||||
<div ref={dataGridContainerRef} className="flex-1 min-w-0 overflow-x-auto">
|
||||
<DataGrid
|
||||
ref={gridRef}
|
||||
style={{ height: '100%' }}
|
||||
@@ -393,7 +391,9 @@ export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformance
|
||||
<Row
|
||||
{...props}
|
||||
key={`qp-row-${props.rowIdx}`}
|
||||
onClick={() => {
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
|
||||
if (typeof idx === 'number' && idx >= 0) {
|
||||
setSelectedRow(idx)
|
||||
gridRef.current?.scrollToCell({ idx: 0, rowIdx: idx })
|
||||
@@ -424,58 +424,70 @@ export const QueryPerformanceGrid = ({ queryPerformanceQuery }: QueryPerformance
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</ResizablePanel>
|
||||
{selectedRow !== undefined && (
|
||||
<>
|
||||
<ResizableHandle withHandle />
|
||||
<ResizablePanel defaultSize={30} maxSize={45} minSize={30} className="bg-studio border-t">
|
||||
<Button
|
||||
type="text"
|
||||
className="absolute top-3 right-3 px-1"
|
||||
icon={<X />}
|
||||
onClick={() => setSelectedRow(undefined)}
|
||||
/>
|
||||
<Tabs_Shadcn_
|
||||
value={view}
|
||||
className="flex flex-col h-full"
|
||||
onValueChange={(value: any) => setView(value)}
|
||||
>
|
||||
<TabsList_Shadcn_ className="px-5 flex gap-x-4 min-h-[46px]">
|
||||
</div>
|
||||
|
||||
<Sheet
|
||||
open={selectedRow !== undefined}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
setSelectedRow(undefined)
|
||||
}
|
||||
}}
|
||||
modal={false}
|
||||
>
|
||||
<SheetTitle className="sr-only">Query details</SheetTitle>
|
||||
<SheetContent
|
||||
side="right"
|
||||
className="flex flex-col h-full bg-studio border-l lg:!w-[calc(100vw-802px)] max-w-[700px] w-full"
|
||||
hasOverlay={false}
|
||||
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_
|
||||
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
|
||||
</TabsTrigger_Shadcn_>
|
||||
{showIndexSuggestions && (
|
||||
<TabsTrigger_Shadcn_
|
||||
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
|
||||
</TabsTrigger_Shadcn_>
|
||||
)}
|
||||
</TabsList_Shadcn_>
|
||||
<TabsContent_Shadcn_
|
||||
value="details"
|
||||
className="mt-0 flex-grow min-h-0 overflow-y-auto"
|
||||
>
|
||||
</div>
|
||||
|
||||
<TabsContent_Shadcn_ value="details" className="mt-0 flex-grow min-h-0 overflow-y-auto">
|
||||
{selectedRow !== undefined && (
|
||||
<QueryDetail
|
||||
reportType={reportType}
|
||||
selectedRow={reportData[selectedRow]}
|
||||
onClickViewSuggestion={() => setView('suggestion')}
|
||||
/>
|
||||
</TabsContent_Shadcn_>
|
||||
<TabsContent_Shadcn_
|
||||
value="suggestion"
|
||||
className="mt-0 flex-grow min-h-0 overflow-y-auto"
|
||||
>
|
||||
<QueryIndexes selectedRow={reportData[selectedRow]} />
|
||||
</TabsContent_Shadcn_>
|
||||
</Tabs_Shadcn_>
|
||||
</ResizablePanel>
|
||||
</>
|
||||
)}
|
||||
</ResizablePanelGroup>
|
||||
)}
|
||||
</TabsContent_Shadcn_>
|
||||
<TabsContent_Shadcn_
|
||||
value="suggestion"
|
||||
className="mt-0 flex-grow min-h-0 overflow-y-auto"
|
||||
>
|
||||
{selectedRow !== undefined && <QueryIndexes selectedRow={reportData[selectedRow]} />}
|
||||
</TabsContent_Shadcn_>
|
||||
</Tabs_Shadcn_>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useMemo } from 'react'
|
||||
import React, { useMemo } from 'react'
|
||||
|
||||
import { Skeleton } from 'ui'
|
||||
import { useQueryPerformanceQuery } from '../Reports/Reports.queries'
|
||||
@@ -11,7 +11,7 @@ export const QueryPerformanceMetrics = () => {
|
||||
const stats = useMemo(() => {
|
||||
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',
|
||||
},
|
||||
{
|
||||
@@ -28,11 +28,8 @@ export const QueryPerformanceMetrics = () => {
|
||||
return (
|
||||
<section className="px-6 pt-2 pb-4 flex flex-wrap gap-x-6 gap-y-2 w-full">
|
||||
{stats.map((card, i) => (
|
||||
<>
|
||||
<div
|
||||
key={i}
|
||||
className="flex items-baseline gap-2 heading-subSection text-foreground-light"
|
||||
>
|
||||
<React.Fragment key={i}>
|
||||
<div className="flex items-baseline gap-2 heading-subSection text-foreground-light">
|
||||
{isLoading ? (
|
||||
<Skeleton className="h-5 w-24" />
|
||||
) : (
|
||||
@@ -43,7 +40,7 @@ export const QueryPerformanceMetrics = () => {
|
||||
)}
|
||||
</div>
|
||||
{i < stats.length - 1 && <span className="text-foreground-muted">/</span>}
|
||||
</>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</section>
|
||||
)
|
||||
|
||||
@@ -151,14 +151,15 @@ export interface DialogContentProps
|
||||
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
|
||||
VariantProps<typeof sheetVariants> {
|
||||
showClose?: boolean
|
||||
hasOverlay?: boolean
|
||||
}
|
||||
|
||||
const SheetContent = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Content>,
|
||||
DialogContentProps
|
||||
>(({ side, size, className, children, showClose = true, ...props }, ref) => (
|
||||
>(({ side, size, className, children, showClose = true, hasOverlay = true, ...props }, ref) => (
|
||||
<SheetPortal side={side}>
|
||||
<SheetOverlay />
|
||||
{hasOverlay && <SheetOverlay />}
|
||||
<SheetPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(sheetVariants({ side, size }), className)}
|
||||
|
||||
Reference in New Issue
Block a user