diff --git a/apps/studio/components/ui/Charts/ComposedChart.tsx b/apps/studio/components/ui/Charts/ComposedChart.tsx index 49a47745b1e..3e7ebf4ad3a 100644 --- a/apps/studio/components/ui/Charts/ComposedChart.tsx +++ b/apps/studio/components/ui/Charts/ComposedChart.tsx @@ -36,7 +36,6 @@ import { CustomLabel, CustomTooltip, MultiAttribute, - StackingMode, } from './ComposedChart.utils' import NoDataPlaceholder from './NoDataPlaceholder' import { ChartHighlight } from './useChartHighlight' @@ -73,9 +72,6 @@ export interface ComposedChartProps extends CommonChartProps { sql?: string highlightActions?: ChartHighlightAction[] showNewBadge?: boolean - // Controls how stacked values are displayed: 'normal' shows raw values, 'percentage' normalizes to 100% - // This is necessary for multi-core CPU metrics where values can exceed 100%, making them comparable across different core counts - stackingMode?: StackingMode } interface CustomizedDotProps { @@ -135,7 +131,6 @@ export function ComposedChart({ highlightActions, titleTooltip, showNewBadge, - stackingMode = 'normal', }: ComposedChartProps) { const { resolvedTheme } = useTheme() const { hoveredIndex, syncTooltip, setHover, clearHover } = useChartHoverState( @@ -272,8 +267,6 @@ export function ComposedChart({ chartHighlight?.coordinates.right && chartHighlight?.coordinates.left !== chartHighlight?.coordinates.right - const isPercentageStacking = stackingMode === 'percentage' - const initialChartData = data && !!data[0] ? Object.entries(data[0]) @@ -491,9 +484,7 @@ export function ComposedChart({ hide={hideYAxis} axisLine={{ stroke: CHART_COLORS.AXIS }} tickLine={{ stroke: CHART_COLORS.AXIS }} - domain={ - isPercentageStacking || (isPercentage && !showMaxValue) ? yDomain : ['auto', 'auto'] - } + domain={isPercentage && !showMaxValue ? yDomain : ['auto', 'auto']} key={yAxisKey} /> { it('should convert data point to payload format', () => { @@ -141,154 +135,3 @@ describe('convertDataPointToPayload', () => { ]) }) }) - -describe('normalizeToPercentageStacking', () => { - it('keeps values unchanged when total is exactly 100%', () => { - const data = [ - { timestamp: 1, cpu_system: 20, cpu_user: 30, cpu_other: 50 }, - { timestamp: 2, cpu_system: 10, cpu_user: 40, cpu_other: 50 }, - ] - const attributeKeys = ['cpu_system', 'cpu_user', 'cpu_other'] - - const result: typeof data = normalizeToPercentageStacking(data, attributeKeys) - - expect(result[0].cpu_system).toBe(20) - expect(result[0].cpu_user).toBe(30) - expect(result[0].cpu_other).toBe(50) - - expect(result[1].cpu_system).toBe(10) - expect(result[1].cpu_user).toBe(40) - expect(result[1].cpu_other).toBe(50) - }) - - it('keeps values unchanged when total is below 100%', () => { - const data = [{ timestamp: 1, cpu_system: 5, cpu_user: 10, cpu_other: 35 }] - const attributeKeys = ['cpu_system', 'cpu_user', 'cpu_other'] - - const result: typeof data = normalizeToPercentageStacking(data, attributeKeys) - - expect(result[0].cpu_system).toBe(5) - expect(result[0].cpu_user).toBe(10) - expect(result[0].cpu_other).toBe(35) - }) - - it('normalizes values exceeding 100% (multi-core CPU)', () => { - const data = [{ timestamp: 1, cpu_system: 40, cpu_user: 60, cpu_other: 100 }] - const attributeKeys = ['cpu_system', 'cpu_user', 'cpu_other'] - - const result: typeof data = normalizeToPercentageStacking(data, attributeKeys) - - expect(result[0].cpu_system).toBe(20) - expect(result[0].cpu_user).toBe(30) - expect(result[0].cpu_other).toBe(50) - expect(result[0].cpu_system + result[0].cpu_user + result[0].cpu_other).toBe(100) - }) - - it('normalizes 2-core CPU at 150% to 100%', () => { - const data = [{ timestamp: 1, cpu_system: 30, cpu_user: 60, cpu_iowait: 60 }] - const attributeKeys = ['cpu_system', 'cpu_user', 'cpu_iowait'] - - const result: typeof data = normalizeToPercentageStacking(data, attributeKeys) - - expect(result[0].cpu_system).toBe(20) - expect(result[0].cpu_user).toBe(40) - expect(result[0].cpu_iowait).toBe(40) - expect(result[0].cpu_system + result[0].cpu_user + result[0].cpu_iowait).toBe(100) - }) - - it('preserves non-attribute fields unchanged', () => { - const data = [ - { timestamp: 1704067200000, period_start: '2024-01-01', cpu_system: 80, cpu_user: 120 }, - ] - const attributeKeys = ['cpu_system', 'cpu_user'] - - const result: typeof data = normalizeToPercentageStacking(data, attributeKeys) - - expect(result[0].timestamp).toBe(1704067200000) - expect(result[0].period_start).toBe('2024-01-01') - }) - - it('handles data points with zero total gracefully', () => { - const data = [{ timestamp: 1, cpu_system: 0, cpu_user: 0, cpu_other: 0 }] - const attributeKeys = ['cpu_system', 'cpu_user', 'cpu_other'] - - const result = normalizeToPercentageStacking(data, attributeKeys) - - expect(result[0].cpu_system).toBe(0) - expect(result[0].cpu_user).toBe(0) - expect(result[0].cpu_other).toBe(0) - }) - - it('handles empty data array', () => { - const data: Array> = [] - const attributeKeys = ['cpu_system', 'cpu_user'] - - const result: typeof data = normalizeToPercentageStacking(data, attributeKeys) - - expect(result).toEqual([]) - }) - - it('keeps values unchanged when single attribute is below 100%', () => { - const data = [{ timestamp: 1, cpu_system: 60 }] - const attributeKeys = ['cpu_system', 'cpu_user'] - - const result = normalizeToPercentageStacking(data, attributeKeys) - - expect(result[0].cpu_system).toBe(60) - expect((result[0] as Record).cpu_user).toBeUndefined() - }) - - it('maintains proportions when normalizing values over 100%', () => { - const data = [{ timestamp: 1, a: 100, b: 200, c: 300 }] - const attributeKeys = ['a', 'b', 'c'] - - const result: typeof data = normalizeToPercentageStacking(data, attributeKeys) - - const ratioAB = result[0].a / result[0].b - const ratioBC = result[0].b / result[0].c - - expect(ratioAB).toBeCloseTo(0.5) - expect(ratioBC).toBeCloseTo(2 / 3) - expect(result[0].a + result[0].b + result[0].c).toBe(100) - }) -}) - -describe('selectStackedAttributeKeys', () => { - it('filters out reference lines, max values, disabled and special attributes', () => { - const attributes: MultiAttribute[] = [ - { attribute: 'a', enabled: true }, - { attribute: 'b', enabled: false }, - { attribute: 'c', provider: 'reference-line' }, - { attribute: 'd', isMaxValue: true }, - { attribute: 'rest' }, - { attribute: 'e' }, - ] - - const keys = selectStackedAttributeKeys(attributes) - expect(keys).toEqual(['a', 'e']) - }) -}) - -describe('transformDataForStacking', () => { - it('returns input when stackingMode is normal', () => { - const data = [{ a: 1, b: 2 }] - const attributes: MultiAttribute[] = [{ attribute: 'a' }, { attribute: 'b' }] - - const result = transformDataForStacking(data, attributes, 'normal') - expect(result).toEqual(data) - }) - - it('normalizes data when stackingMode is percentage', () => { - const data = [{ timestamp: 1, a: 100, b: 200 }] - const attributes: MultiAttribute[] = [{ attribute: 'a' }, { attribute: 'b' }] - - const result = transformDataForStacking(data, attributes, 'percentage')! - expect(result[0].a + result[0].b).toBe(100) - }) - - it('handles undefined or empty data gracefully', () => { - const attributes: MultiAttribute[] = [{ attribute: 'a' }] - expect(transformDataForStacking(undefined, attributes, 'percentage')).toBeUndefined() - expect(transformDataForStacking([], attributes, 'percentage')).toEqual([]) - }) -}) diff --git a/apps/studio/components/ui/Charts/ComposedChart.utils.tsx b/apps/studio/components/ui/Charts/ComposedChart.utils.tsx index 578a6ececf5..7f15cfa6278 100644 --- a/apps/studio/components/ui/Charts/ComposedChart.utils.tsx +++ b/apps/studio/components/ui/Charts/ComposedChart.utils.tsx @@ -7,8 +7,6 @@ import { cn, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from 'ui import { CHART_COLORS, DateTimeFormats } from './Charts.constants' import { formatPercentage, numberFormatter } from './Charts.utils' -export type StackingMode = 'normal' | 'percentage' - export interface ReportAttributes { id?: string titleTooltip?: string @@ -33,7 +31,6 @@ export interface ReportAttributes { tickFormatter?: (value: any) => string } hideHighlightedValue?: boolean - stackingMode?: StackingMode } export type Provider = 'infra-monitoring' | 'daily-stats' | 'mock' | 'reference-line' | 'logs' @@ -146,76 +143,6 @@ export const convertDataPointToPayload = ( ) } -/** - * Normalize data points to 100% stacking mode. - * Only normalizes when total exceeds 100% (e.g., multi-core CPU metrics). - * Values below or equal to 100% are kept as-is to show actual utilization. - */ -export const normalizeToPercentageStacking = >( - data: T[], - attributeKeys: string[] -): T[] => { - return data.map((point) => { - const total = attributeKeys.reduce((sum, attr) => { - const value = point[attr] - return sum + (typeof value === 'number' ? value : 0) - }, 0) - - if (total <= 100) { - return point - } - - const normalized = { ...point } - const lastAttribute = attributeKeys[attributeKeys.length - 1] - let accumulated = 0 - - attributeKeys.forEach((attr, index) => { - const value = point[attr] - if (typeof value !== 'number') return - - if (attr === lastAttribute && index === attributeKeys.length - 1) { - const remainder = 100 - accumulated - ;(normalized as Record)[attr] = remainder - return - } - const normalizedValue = (value / total) * 100 - accumulated += normalizedValue - ;(normalized as Record)[attr] = normalizedValue - }) - return normalized - }) -} - -/** - * Select attribute keys to be included in stacking normalization. - * Filters out reference lines, max value lines, disabled and special attributes. - */ -export const selectStackedAttributeKeys = (attributes: MultiAttribute[]): string[] => { - return attributes - .filter((attr) => attr && attr.enabled !== false) - .filter((attr) => attr.provider !== 'reference-line') - .filter((attr) => !attr.isMaxValue) - .filter((attr) => attr.attribute !== 'rest') - .map((attr) => attr.attribute) -} - -/** - * Transform chart data according to stacking mode, applying percentage normalization when needed. - */ -export const transformDataForStacking = >( - data: T[] | undefined, - attributes: MultiAttribute[], - stackingMode?: StackingMode -): T[] | undefined => { - if (!data || data.length === 0) return data - if (stackingMode !== 'percentage') return data - - const keys = selectStackedAttributeKeys(attributes) - if (keys.length === 0) return data - - return normalizeToPercentageStacking(data, keys) -} - /** * Calculate the total aggregate of the chart values * by summing the values of the attributes diff --git a/apps/studio/components/ui/Charts/ComposedChartHandler.tsx b/apps/studio/components/ui/Charts/ComposedChartHandler.tsx index 0534e632268..dd7913be794 100644 --- a/apps/studio/components/ui/Charts/ComposedChartHandler.tsx +++ b/apps/studio/components/ui/Charts/ComposedChartHandler.tsx @@ -18,7 +18,7 @@ import { useChartHighlight } from './useChartHighlight' import dayjs from 'dayjs' import type { UpdateDateRange } from 'pages/project/[ref]/observability/database' import type { ChartData } from './Charts.types' -import { MultiAttribute, StackingMode, transformDataForStacking } from './ComposedChart.utils' +import { MultiAttribute } from './ComposedChart.utils' export interface ComposedChartHandlerProps { id?: string @@ -45,7 +45,6 @@ export interface ComposedChartHandlerProps { docsUrl?: string hide?: boolean syncId?: string - stackingMode?: StackingMode hideHighlightedValue?: boolean } @@ -207,18 +206,11 @@ const ComposedChartHandler = ({ return combined as DataPoint[] }, [data, attributeQueries, attributes]) - // Apply pre-render transformations (e.g., percentage normalization) const processedData = useMemo(() => { - const dataPoints = Array.isArray(combinedData) + return Array.isArray(combinedData) ? combinedData.map((point) => ({ ...point })) : combinedData?.data?.map((point) => ({ ...point })) - - return transformDataForStacking>( - dataPoints, - attributes, - otherProps.stackingMode - ) - }, [combinedData, attributes, otherProps.stackingMode]) + }, [combinedData]) const loading = isLoading || attributeQueries.some((query: any) => query.isLoading) diff --git a/apps/studio/data/reports/database-charts.ts b/apps/studio/data/reports/database-charts.ts index c1fa630c5f5..444202ff001 100644 --- a/apps/studio/data/reports/database-charts.ts +++ b/apps/studio/data/reports/database-charts.ts @@ -74,7 +74,6 @@ export const getReportAttributesV2: ( showLegend: true, showMaxValue: true, showGrid: true, - stackingMode: 'percentage', YAxisProps: { width: 45, tickFormatter: (value: any) => {