diff --git a/apps/studio/components/interfaces/SQLEditor/UtilityPanel/ChartConfig.tsx b/apps/studio/components/interfaces/SQLEditor/UtilityPanel/ChartConfig.tsx index a54a698c6e2..870d33a0c18 100644 --- a/apps/studio/components/interfaces/SQLEditor/UtilityPanel/ChartConfig.tsx +++ b/apps/studio/components/interfaces/SQLEditor/UtilityPanel/ChartConfig.tsx @@ -25,6 +25,7 @@ import { Admonition } from 'ui-patterns' import { ButtonTooltip } from '@/components/ui/ButtonTooltip' import BarChart from '@/components/ui/Charts/BarChart' import NoDataPlaceholder from '@/components/ui/Charts/NoDataPlaceholder' +import { getCumulativeResults } from '@/components/ui/QueryBlock/QueryBlock.utils' import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage' type Results = { rows: readonly any[] } @@ -40,21 +41,6 @@ export type ChartConfig = { logScale?: boolean } -const getCumulativeResults = (results: Results, config: ChartConfig) => { - if (!results?.rows?.length) { - return [] - } - - const cumulativeResults = results.rows.reduce((acc, row) => { - const prev = acc[acc.length - 1] || {} - const next = { - ...row, - [config.yKey]: (prev[config.yKey] || 0) + row[config.yKey], - } - return [...acc, next] - }, []) - return cumulativeResults -} const VALID_RESULT_KEY_TYPES = ['number', 'string', 'date'] type ChartConfigProps = { diff --git a/apps/studio/components/ui/QueryBlock/QueryBlock.utils.ts b/apps/studio/components/ui/QueryBlock/QueryBlock.utils.ts index 7a7ffbb9708..342f5b17a60 100644 --- a/apps/studio/components/ui/QueryBlock/QueryBlock.utils.ts +++ b/apps/studio/components/ui/QueryBlock/QueryBlock.utils.ts @@ -1,4 +1,4 @@ -import { ChartConfig } from '@/components/interfaces/SQLEditor/UtilityPanel/ChartConfig' +import type { ChartConfig } from '@/components/interfaces/SQLEditor/UtilityPanel/ChartConfig' export const checkHasNonPositiveValues = (data: Record[], key: string): boolean => data.some((row) => (row[key] as number) <= 0) @@ -47,16 +47,19 @@ export const formatLogTick = (value: number): string => { return value.toLocaleString() } -export const getCumulativeResults = (results: { rows: any[] }, config: ChartConfig) => { +export const getCumulativeResults = (results: { rows: readonly any[] }, config: ChartConfig) => { if (!results?.rows?.length) { return [] } const cumulativeResults = results.rows.reduce((acc, row) => { const prev = acc[acc.length - 1] || {} + // Coerce to Number before adding: Postgres returns `bigint`, `numeric`, + // `money` and `count(*)` columns as strings, so a bare `+` would + // concatenate (e.g. "10" + "20" -> "1020") instead of summing. const next = { ...row, - [config.yKey]: (prev[config.yKey] || 0) + row[config.yKey], + [config.yKey]: (Number(prev[config.yKey]) || 0) + (Number(row[config.yKey]) || 0), } return [...acc, next] }, []) diff --git a/apps/studio/tests/components/ui/QueryBlock/QueryBlock.utils.test.ts b/apps/studio/tests/components/ui/QueryBlock/QueryBlock.utils.test.ts index 99c99109098..b48ad9aafaf 100644 --- a/apps/studio/tests/components/ui/QueryBlock/QueryBlock.utils.test.ts +++ b/apps/studio/tests/components/ui/QueryBlock/QueryBlock.utils.test.ts @@ -156,4 +156,52 @@ describe('getCumulativeResults', () => { const output = getCumulativeResults(results, config) expect(output).toEqual([{ x: 'a', y: 42 }]) }) + + // Postgres returns `bigint`, `numeric`, `money` and `count(*)` columns as + // strings, so the running total must sum numerically rather than concatenate. + it('sums numeric string yKey values instead of concatenating them', () => { + const results = { + rows: [ + { x: 'a', y: '10' }, + { x: 'b', y: '20' }, + { x: 'c', y: '30' }, + ], + } + const config = { + type: 'bar' as const, + xKey: 'x', + yKey: 'y', + cumulative: true, + showLabels: false, + showGrid: false, + } + const output = getCumulativeResults(results, config) + expect(output).toEqual([ + { x: 'a', y: 10 }, + { x: 'b', y: 30 }, + { x: 'c', y: 60 }, + ]) + }) + + it('treats null/undefined and non-numeric yKey values as 0', () => { + const results = { + rows: [ + { x: 'a', y: null }, + { x: 'b', y: '5' }, + { x: 'c', y: undefined }, + { x: 'd', y: 'not-a-number' }, + { x: 'e', y: 4 }, + ], + } + const config = { + type: 'bar' as const, + xKey: 'x', + yKey: 'y', + cumulative: true, + showLabels: false, + showGrid: false, + } + const output = getCumulativeResults(results, config) + expect(output.map((row: { y: number }) => row.y)).toEqual([0, 5, 5, 5, 9]) + }) })