feat(studio): query details metadata tidy up (#38867)
* 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 * feat: tidying up metadata values in query details * feat: tidy up ms values * fix: query pattern heading
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { Lightbulb } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import dynamic from 'next/dynamic'
|
||||
import dayjs from 'dayjs'
|
||||
import duration from 'dayjs/plugin/duration'
|
||||
|
||||
import { formatSql } from 'lib/formatSql'
|
||||
import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, Button, cn } from 'ui'
|
||||
@@ -37,10 +39,26 @@ export const QueryDetail = ({ selectedRow, onClickViewSuggestion }: QueryDetailP
|
||||
}
|
||||
}, [selectedRow])
|
||||
|
||||
const formatDuration = (seconds: number) => {
|
||||
const dur = dayjs.duration(seconds, 'seconds')
|
||||
|
||||
const minutes = Math.floor(dur.asMinutes())
|
||||
const remainingSeconds = dur.seconds() + dur.milliseconds() / 1000
|
||||
|
||||
const parts = []
|
||||
if (minutes > 0) parts.push(`${minutes}m`)
|
||||
if (remainingSeconds > 0) {
|
||||
const formattedSeconds = remainingSeconds.toFixed(2)
|
||||
parts.push(`${formattedSeconds}s`)
|
||||
}
|
||||
|
||||
return parts.join(' ')
|
||||
}
|
||||
|
||||
return (
|
||||
<QueryPanelContainer>
|
||||
<QueryPanelSection>
|
||||
<p className="text-sm">Query pattern</p>
|
||||
<h4 className="mb-2">Query pattern</h4>
|
||||
<SqlMonacoBlock value={query} height={310} lineNumbers="off" wrapperClassName="pl-3" />
|
||||
{isLinterWarning && (
|
||||
<Alert_Shadcn_
|
||||
@@ -61,28 +79,125 @@ export const QueryDetail = ({ selectedRow, onClickViewSuggestion }: QueryDetailP
|
||||
)}
|
||||
</QueryPanelSection>
|
||||
<div className="border-t" />
|
||||
<QueryPanelSection className="gap-y-1">
|
||||
{report
|
||||
.filter((x) => x.id !== 'query')
|
||||
.map((x) => {
|
||||
const rawValue = selectedRow?.[x.id]
|
||||
const isTime = x.name.includes('time')
|
||||
<QueryPanelSection className="pb-3">
|
||||
<h4 className="mb-2">Metadata</h4>
|
||||
<ul className="flex flex-col gap-y-3 divide-y divide-dashed">
|
||||
{report
|
||||
.filter((x) => x.id !== 'query')
|
||||
.map((x) => {
|
||||
const rawValue = selectedRow?.[x.id]
|
||||
const isTime = x.name.includes('time')
|
||||
|
||||
const formattedValue = isTime
|
||||
? typeof rawValue === 'number' && !isNaN(rawValue) && isFinite(rawValue)
|
||||
? `${rawValue.toFixed(2)}ms`
|
||||
: 'N/A'
|
||||
: rawValue != null
|
||||
? String(rawValue)
|
||||
: 'N/A'
|
||||
const formattedValue = isTime
|
||||
? typeof rawValue === 'number' && !isNaN(rawValue) && isFinite(rawValue)
|
||||
? `${Math.round(rawValue).toLocaleString()}ms`
|
||||
: 'n/a'
|
||||
: rawValue != null
|
||||
? String(rawValue)
|
||||
: 'n/a'
|
||||
|
||||
return (
|
||||
<div key={x.id} className="flex gap-x-2">
|
||||
<p className="text-foreground-lighter text-sm w-32">{x.name}</p>
|
||||
<p className="text-sm w-32">{formattedValue}</p>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
if (x.id === 'prop_total_time') {
|
||||
return (
|
||||
<li key={x.id} className="flex justify-between pt-3 text-sm">
|
||||
<p className="text-foreground-light">{x.name}</p>
|
||||
{rawValue ? (
|
||||
<p
|
||||
className={cn(
|
||||
'tabular-nums',
|
||||
rawValue.toFixed(1) === '0.0' && 'text-foreground-lighter'
|
||||
)}
|
||||
>
|
||||
{rawValue.toFixed(1)}%
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-muted">–</p>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
if (x.id == 'total_time') {
|
||||
return (
|
||||
<li key={x.id} className="flex justify-between pt-3 text-sm">
|
||||
<p className="text-foreground-light">
|
||||
{x.name + ' '}
|
||||
<span className="text-foreground-lighter">latency</span>
|
||||
</p>
|
||||
{isTime &&
|
||||
typeof rawValue === 'number' &&
|
||||
!isNaN(rawValue) &&
|
||||
isFinite(rawValue) ? (
|
||||
<p
|
||||
className={cn(
|
||||
'tabular-nums',
|
||||
formatDuration(rawValue / 1000) === '0.0s' && 'text-foreground-lighter'
|
||||
)}
|
||||
>
|
||||
{formatDuration(rawValue / 1000)}
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-muted">–</p>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
if (x.id == 'rows_read') {
|
||||
return (
|
||||
<li key={x.id} className="flex justify-between pt-3 text-sm">
|
||||
<p className="text-foreground-light">{x.name}</p>
|
||||
{typeof rawValue === 'number' && !isNaN(rawValue) && isFinite(rawValue) ? (
|
||||
<p
|
||||
className={cn('tabular-nums', rawValue === 0 && 'text-foreground-lighter')}
|
||||
>
|
||||
{rawValue.toLocaleString()}
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-muted">–</p>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
const cacheHitRateToNumber = (value: number | string) => {
|
||||
if (typeof value === 'number') return value
|
||||
return parseFloat(value.toString().replace('%', '')) || 0
|
||||
}
|
||||
|
||||
if (x.id === 'cache_hit_rate') {
|
||||
return (
|
||||
<li key={x.id} className="flex justify-between pt-3 text-sm">
|
||||
<p className="text-foreground-light">{x.name}</p>
|
||||
{typeof rawValue === 'string' ? (
|
||||
<p
|
||||
className={cn(
|
||||
cacheHitRateToNumber(rawValue).toFixed(2) === '0.00' &&
|
||||
'text-foreground-lighter'
|
||||
)}
|
||||
>
|
||||
{cacheHitRateToNumber(rawValue).toLocaleString(undefined, {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
})}
|
||||
%
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-muted">–</p>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={x.id} className="flex justify-between pt-3 text-sm">
|
||||
<p className="text-foreground-light">{x.name}</p>
|
||||
<p className={cn('tabular-nums', x.id === 'rolname' && 'font-mono')}>
|
||||
{formattedValue}
|
||||
</p>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</QueryPanelSection>
|
||||
</QueryPanelContainer>
|
||||
)
|
||||
|
||||
@@ -14,7 +14,7 @@ export const QueryPanelSection = ({
|
||||
children,
|
||||
className,
|
||||
}: PropsWithChildren<{ className?: string }>) => (
|
||||
<div className={cn('px-5 flex flex-col gap-y-2', className)}>{children}</div>
|
||||
<div className={cn('px-6 flex flex-col gap-y-0', className)}>{children}</div>
|
||||
)
|
||||
|
||||
export const QueryPanelScoreSection = ({
|
||||
|
||||
Reference in New Issue
Block a user