Files
supabase/apps/studio/components/interfaces/QueryInsights/hooks/useChartConfig.ts
T
kemal 64794ebbd0 fix: working latency toggles
The toggles for all the percentiles werent actually working right, now fixed.
2025-08-11 17:09:48 +01:00

409 lines
11 KiB
TypeScript

import { useMemo } from 'react'
import { useParams } from 'common'
import {
getQueryLatencyConfig,
getRowsReadConfig,
getCallsConfig,
getCacheHitsConfig,
getIssuesConfig,
ChartConfigResult,
} from '../chartConfigs'
import { QueryInsightsMetric, QueryInsightsQuery } from 'data/query-insights/query-insights-query'
import {
useSingleQueryLatency,
useSingleQueryRows,
useSingleQueryCalls,
} from 'data/query-insights/single-query-latency-query'
import { formatLatency, formatMetricValue } from '../QueryInsights.utils'
import { MetricType } from '../QueryInsights'
// Shared opacity settings for all charts
export const CHART_OPACITY = {
GRADIENT_START: 0.3,
GRADIENT_END: 0.05,
FILL_NORMAL: 0.3,
FILL_SELECTED: 0.05,
STROKE_NORMAL: 0.8,
STROKE_SELECTED: 0.2,
HIGHLIGHT_FILL: 0.6,
HIGHLIGHT_STROKE: 1,
}
interface ChartHookOptions {
data: QueryInsightsMetric[]
startTime: string
endTime: string
selectedQuery: QueryInsightsQuery | null
hoveredQuery: QueryInsightsQuery | null
}
interface ChartHookResult<T = any> {
chartConfig: ChartConfigResult<T> | null
metricBadges: { label: string; value: string; color: string }[]
renderConfig: {
yAxisDomain: [number, string | number]
yAxisFormatter?: (value: number) => string
yAxisWidth?: number
yAxisTickCount?: number
keyMappings: { [key: string]: string } // Maps config keys to visibleMetrics keys
}
}
/**
* Hook for Query Latency chart configuration
*/
export function useQueryLatencyChart({
data,
startTime,
endTime,
selectedQuery,
hoveredQuery,
metric,
}: ChartHookOptions & { metric: MetricType }): ChartHookResult {
const { ref } = useParams()
const isActive = metric === 'query_latency'
console.log(
'[useQueryLatencyChart] selectedQuery:',
selectedQuery?.query_id,
'hoveredQuery:',
hoveredQuery?.query_id
)
// Fetch single query latency data
const { data: queryLatencyData } = useSingleQueryLatency(
ref,
startTime,
endTime,
(selectedQuery?.query_id || hoveredQuery?.query_id)?.toString() || '',
{
// Only fetch data if this is the current metric and we have a query selected/hovered
enabled: isActive && !!(selectedQuery || hoveredQuery),
}
)
// Generate chart configuration
const chartConfig = useMemo(() => {
if (!isActive || !Array.isArray(data) || data.length === 0) {
return null
}
return getQueryLatencyConfig(data, queryLatencyData)
}, [data, queryLatencyData, isActive])
// Get metrics for the selected query badge
const metricBadges = useMemo(() => {
if (!isActive || !selectedQuery || !chartConfig) return []
const { chartData, config } = chartConfig
// Calculate average latency across all data points that have query_latency
const queryLatencyPoints = chartData.filter((point) => point.query_latency > 0)
const avgLatency =
queryLatencyPoints.length > 0
? queryLatencyPoints.reduce((sum, point) => sum + (point.query_latency ?? 0), 0) /
queryLatencyPoints.length
: 0
console.log('[useQueryLatencyChart] Calculating metric badge:', {
avgLatency,
pointsWithLatency: queryLatencyPoints.length,
totalPoints: chartData.length,
})
return [
{
label: 'Query latency',
value: formatLatency(avgLatency),
color: config.query_latency?.color || 'hsl(var(--chart-1))',
},
]
}, [selectedQuery, chartConfig, isActive])
// Provide rendering configuration
const renderConfig = {
yAxisDomain: [0, 'auto'] as [number, string | number],
yAxisWidth: 80,
yAxisTickCount: 5,
keyMappings: {
p50: 'latency_p50',
p95: 'latency_p95',
p99: 'latency_p99',
},
}
return { chartConfig, metricBadges, renderConfig }
}
/**
* Hook for Rows Read chart configuration
*/
export function useRowsReadChart({
data,
startTime,
endTime,
selectedQuery,
hoveredQuery,
metric,
}: ChartHookOptions & { metric: MetricType }): ChartHookResult {
const { ref } = useParams()
const isActive = metric === 'rows_read'
console.log(
'[useRowsReadChart] selectedQuery:',
selectedQuery?.query_id,
'hoveredQuery:',
hoveredQuery?.query_id
)
// Fetch single query rows data
const { data: queryRowsData } = useSingleQueryRows(
ref,
startTime,
endTime,
(selectedQuery?.query_id || hoveredQuery?.query_id)?.toString() || '',
{
// Only fetch data if this is the current metric and we have a query selected/hovered
enabled: isActive && !!(selectedQuery || hoveredQuery),
}
)
console.log('[useRowsReadChart] queryRowsData:', queryRowsData)
// Generate chart configuration
const chartConfig = useMemo(() => {
if (!isActive || !Array.isArray(data) || data.length === 0) {
return null
}
const config = getRowsReadConfig(data, queryRowsData)
console.log('[useRowsReadChart] chartConfig result:', {
chartDataLength: config.chartData.length,
hasQueryRows: config.chartData.some((point) => point.query_rows > 0),
firstFewPoints: config.chartData.slice(0, 3),
queryRowsPoints: config.chartData.filter((point) => point.query_rows > 0),
})
return config
}, [data, queryRowsData, isActive])
// Get metrics for the selected query badge
const metricBadges = useMemo(() => {
console.log('🔍 [useRowsReadChart] metricBadges useMemo triggered, isActive:', isActive, 'selectedQuery:', selectedQuery?.query_id, 'chartConfig:', !!chartConfig)
console.log('🔍 [useRowsReadChart] Conditions check:', {
isActive,
hasSelectedQuery: !!selectedQuery,
hasChartConfig: !!chartConfig,
willReturnEarly: !isActive || !selectedQuery || !chartConfig
})
if (!isActive || !selectedQuery || !chartConfig) {
console.log('🔍 [useRowsReadChart] Returning early due to conditions not met')
return []
}
const { chartData, config } = chartConfig
// Calculate total rows for display - sum across all data points
const totalQueryRows = chartData.reduce((sum, point) => sum + (point.query_rows ?? 0), 0)
console.log('🏷️ [useRowsReadChart] PILL metric badge calculation:', {
totalQueryRows,
chartDataPoints: chartData.length,
hasQueryRows: chartData.some((point) => point.query_rows > 0),
})
// Show the total rows rather than just the latest point
const formattedValue = formatMetricValue(totalQueryRows)
console.log('🏷️ [useRowsReadChart] PILL Final formatted value:', formattedValue, typeof formattedValue)
return [
{
label: 'Rows',
value: formattedValue,
color: config.query_rows?.color || 'hsl(var(--chart-2))',
},
]
}, [selectedQuery, chartConfig, isActive])
// Provide rendering configuration
const renderConfig = {
yAxisDomain: [0, 'auto'] as [number, string | number],
keyMappings: {},
}
return { chartConfig, metricBadges, renderConfig }
}
/**
* Hook for Calls chart configuration
*/
export function useCallsChart({
data,
startTime,
endTime,
selectedQuery,
hoveredQuery,
metric,
}: ChartHookOptions & { metric: MetricType }): ChartHookResult {
const { ref } = useParams()
const isActive = metric === 'calls'
console.log(
'[useCallsChart] selectedQuery:',
selectedQuery?.query_id,
'hoveredQuery:',
hoveredQuery?.query_id
)
// Fetch single query calls data
const { data: queryCallsData } = useSingleQueryCalls(
ref,
startTime,
endTime,
(selectedQuery?.query_id || hoveredQuery?.query_id)?.toString() || '',
{
// Only fetch data if this is the current metric and we have a query selected/hovered
enabled: isActive && !!(selectedQuery || hoveredQuery),
}
)
// Generate chart configuration
const chartConfig = useMemo(() => {
if (!isActive || !Array.isArray(data) || data.length === 0) {
return null
}
return getCallsConfig(data, queryCallsData)
}, [data, queryCallsData, isActive])
// Get metrics for the selected query badge
const metricBadges = useMemo(() => {
if (!isActive || !selectedQuery || !chartConfig) return []
const { chartData, config } = chartConfig
// Calculate total calls for display
const totalQueryCalls = chartData.reduce((sum, point) => sum + (point.query_calls ?? 0), 0)
return [
{
label: 'Calls',
value: formatMetricValue(totalQueryCalls),
color: config.query_calls?.color || 'hsl(var(--chart-3))',
},
]
}, [selectedQuery, chartConfig, isActive])
// Provide rendering configuration
const renderConfig = {
yAxisDomain: [0, 'auto'] as [number, string | number],
keyMappings: {},
}
return { chartConfig, metricBadges, renderConfig }
}
/**
* Hook for Cache Hits chart configuration
*/
export function useCacheHitsChart({
data,
metric,
}: ChartHookOptions & { metric: MetricType }): ChartHookResult {
const isActive = metric === 'cache_hits'
// Generate chart configuration
const chartConfig = useMemo(() => {
if (!isActive || !Array.isArray(data) || data.length === 0) {
return null
}
return getCacheHitsConfig(data)
}, [data, isActive])
// Get metrics for the selected query badge
const metricBadges = useMemo(() => {
if (!isActive || !chartConfig) return []
const { chartData } = chartConfig
// Calculate cache hit ratio
const totalHits = chartData.reduce((sum, point) => sum + point.shared_blks_hit, 0)
const totalReads = chartData.reduce((sum, point) => sum + point.shared_blks_read, 0)
const cacheHitRatio = totalReads > 0 ? (totalHits / totalReads) * 100 : 0
return [
{
label: 'Cache Hit Ratio',
value: `${cacheHitRatio.toFixed(1)}%`,
color: '#10b981',
},
]
}, [chartConfig, isActive])
return {
chartConfig,
metricBadges,
renderConfig: {
yAxisDomain: [0, 100],
yAxisFormatter: (value: number) => `${value.toFixed(0)}%`,
yAxisWidth: 60,
yAxisTickCount: 6,
keyMappings: {
cache_hit_ratio: 'cache_hit_ratio',
cache_miss_ratio: 'cache_miss_ratio',
shared_blks_hit: 'shared_blks_hit',
shared_blks_read: 'shared_blks_read',
shared_blks_dirtied: 'shared_blks_dirtied',
shared_blks_written: 'shared_blks_written',
},
},
}
}
/**
* Hook for Issues chart configuration
*/
export function useIssuesChart({
data,
metric,
}: ChartHookOptions & { metric: MetricType }): ChartHookResult {
const isActive = metric === 'issues'
// Generate chart configuration
const chartConfig = useMemo(() => {
if (!isActive || !Array.isArray(data) || data.length === 0) {
return null
}
return getIssuesConfig(data)
}, [data, isActive])
// Get metrics for the selected query badge
const metricBadges = useMemo(() => {
if (!isActive || !chartConfig) return []
const { chartData } = chartConfig
// Calculate total issues
const totalIssues = chartData.reduce((sum, point) => sum + point.issues, 0)
return [
{
label: 'Total Issues',
value: totalIssues.toString(),
color: '#ef4444',
},
]
}, [chartConfig, isActive])
return {
chartConfig,
metricBadges,
renderConfig: {
yAxisDomain: [0, 'auto'],
yAxisFormatter: (value: number) => value.toString(),
yAxisWidth: 60,
yAxisTickCount: 6,
keyMappings: {
issues: 'issues',
query_issues: 'query_issues',
},
},
}
}