diff --git a/apps/studio/components/interface/QueryInsights/QueryMetricChart.tsx b/apps/studio/components/interface/QueryInsights/QueryMetricChart.tsx new file mode 100644 index 00000000000..dc6122c787b --- /dev/null +++ b/apps/studio/components/interface/QueryInsights/QueryMetricChart.tsx @@ -0,0 +1,112 @@ +'use client' + +import { useMemo } from 'react' +import ComposedChart from 'ui/Charts/ComposedChart' +import { MultiAttribute } from 'ui/Charts/ComposedChart.utils' +import dayjs from 'dayjs' + +export interface QueryMetricChartProps { + data: Array<{ + period_start: string + timestamp: number + [key: string]: any + }> + attributes: Array<{ + attribute: string + label: string + format: string + color?: { + light: string + dark: string + } + strokeDasharray?: string + type?: 'line' + strokeWidth?: number + }> + selectedMetric: 'rows_read' | 'query_latency' | 'calls' | 'cache_hits' + className?: string + height?: number +} + +export const QueryMetricChart = ({ + data, + attributes, + selectedMetric, + className = '', + height = 264, +}: QueryMetricChartProps) => { + // Transform attributes into MultiAttribute format for ComposedChart + const chartAttributes = useMemo((): MultiAttribute[] => { + return attributes.map((attr) => ({ + attribute: attr.attribute, + provider: 'infra-monitoring' as const, + label: attr.label, + color: attr.color, + strokeDasharray: attr.strokeDasharray, + type: attr.type === 'line' ? 'line' : 'area-bar', + stackId: '1', // Stack all attributes together + enabled: true, + })) + }, [attributes]) + + // Transform data to match ComposedChart's expected format + const chartData = useMemo(() => { + return data.map((item) => { + const transformed: Record = { + timestamp: item.timestamp, + period_start: item.period_start, + } + + // Add each attribute value + attributes.forEach((attr) => { + transformed[attr.attribute] = item[attr.attribute] || 0 + }) + + return transformed + }) + }, [data, attributes]) + + // Determine format based on selected metric + const getFormat = () => { + switch (selectedMetric) { + case 'query_latency': + return 'ms' + case 'cache_hits': + return '%' + case 'rows_read': + case 'calls': + default: + return '' + } + } + + if (!data || data.length === 0) { + return ( +
+
No data available
+
+ ) + } + + return ( +
+ {}} // No-op for now + className="w-full" + /> +
+ ) +} diff --git a/apps/studio/components/interfaces/QueryInsights/QueryMetricExplorer.tsx b/apps/studio/components/interfaces/QueryInsights/QueryMetricExplorer.tsx index 87075058eb8..2dd89de7d26 100644 --- a/apps/studio/components/interfaces/QueryInsights/QueryMetricExplorer.tsx +++ b/apps/studio/components/interfaces/QueryInsights/QueryMetricExplorer.tsx @@ -1,4 +1,5 @@ import { useState, useMemo, useEffect, useCallback } from 'react' + import { Tabs_Shadcn_, TabsContent_Shadcn_, @@ -282,6 +283,20 @@ export const QueryMetricExplorer = ({ startTime, endTime }: QueryMetricExplorerP return attributes }, [selectedMetric, safeVisiblePercentiles, selectedQuery]) + const transformedChartAttributes = useMemo(() => { + return getChartAttributes + .filter((attr) => attr.label && attr.format) // Filter out undefined values + .map((attr) => ({ + attribute: attr.attribute, + label: attr.label!, + format: attr.format!, + color: attr.color, + strokeDasharray: attr.strokeDasharray, + type: attr.type === 'line' ? ('line' as const) : undefined, + strokeWidth: typeof attr.strokeWidth === 'number' ? attr.strokeWidth : undefined, + })) + }, [getChartAttributes]) + // Calculate average P95 for query latency const averageP95 = useMemo(() => { if (selectedMetric === 'query_latency' && metricsData && metricsData.length > 0) { @@ -578,7 +593,9 @@ export const QueryMetricExplorer = ({ startTime, endTime }: QueryMetricExplorerP customDateFormat="HH:mm" hideChartType={true} hideHighlightedValue={true} + hideHighlightedLabel={true} showTooltip={true} + showGrid={false} showLegend={ selectedMetric === 'query_latency' || selectedMetric === 'cache_hits' || @@ -587,6 +604,11 @@ export const QueryMetricExplorer = ({ startTime, endTime }: QueryMetricExplorerP showTotal={false} showMaxValue={false} updateDateRange={updateDateRange} + YAxisProps={{ + tick: true, + width: 60, + tickFormatter: (value) => value.toLocaleString(), + }} /> ) : ( diff --git a/apps/studio/components/ui/Charts/ChartHeader.tsx b/apps/studio/components/ui/Charts/ChartHeader.tsx index 92840ba3a0e..4238e4826c1 100644 --- a/apps/studio/components/ui/Charts/ChartHeader.tsx +++ b/apps/studio/components/ui/Charts/ChartHeader.tsx @@ -16,6 +16,7 @@ export interface ChartHeaderProps { highlightedLabel?: number | string | any | null highlightedValue?: number | string | any | null hideHighlightedValue?: boolean + hideHighlightedLabel?: boolean hideChartType?: boolean chartStyle?: string onChartStyleChange?: (style: string) => void @@ -38,6 +39,7 @@ const ChartHeader = ({ highlightedValue, highlightedLabel, hideHighlightedValue = false, + hideHighlightedLabel = false, title, minimalHeader = false, hideChartType = false, @@ -184,7 +186,7 @@ const ChartHeader = ({ {title && chartTitle}
{highlightedValue !== undefined && !hideHighlightedValue && highlighted} - {label} + {!hideHighlightedLabel && label}
) @@ -198,7 +200,7 @@ const ChartHeader = ({ {title && chartTitle}
{hasHighlightedValue && highlighted} - {label} + {!hideHighlightedLabel && label}
diff --git a/apps/studio/components/ui/Charts/ComposedChart.tsx b/apps/studio/components/ui/Charts/ComposedChart.tsx index 619e7ddf7c2..8a2fd199507 100644 --- a/apps/studio/components/ui/Charts/ComposedChart.tsx +++ b/apps/studio/components/ui/Charts/ComposedChart.tsx @@ -62,6 +62,7 @@ export interface ComposedChartProps extends CommonChartProps { titleTooltip?: string hideYAxis?: boolean hideHighlightedValue?: boolean + hideHighlightedLabel?: boolean syncId?: string docsUrl?: string } @@ -98,6 +99,7 @@ export default function ComposedChart({ updateDateRange, hideYAxis, hideHighlightedValue, + hideHighlightedLabel = false, syncId, docsUrl, }: ComposedChartProps) { @@ -307,6 +309,7 @@ export default function ComposedChart({ customDateFormat={customDateFormat} highlightedValue={formatHighlightedValue(resolvedHighlightedValue)} highlightedLabel={resolvedHighlightedLabel} + hideHighlightedLabel={hideHighlightedLabel} minimalHeader={minimalHeader} hideChartType={hideChartType} chartStyle={chartStyle}