Files
supabase/apps/studio/components/ui/Charts/ComposedChart.utils.test.ts
Jordi Enric c166bfc044 fix(studio): stop stacking overlaid area series in report charts (#48034)
Line-style report charts (auth processing time, percentiles, edge
functions, realtime, etc.) hardcoded `stackId="1"` on every `<Area>`, so
recharts summed the series additively instead of overlaying them.

When multiple series share a value (e.g. Max/Min/Avg all `153.98`), they
rendered as three stacked bands at 1x/2x/3x the value, even though the
tooltip showed the true identical values.

## Fix

Default each area to its own `stackId` (its attribute name) so series
overlay, while still honoring an explicit per-attribute `stackId` from
config — matching the existing bar-chart path directly above it.
`normalizeVisibleStackToPercent`, the only flag that would make stacked
areas intentional, is never enabled anywhere.

## Before / after

Before: three equal values drawn at `0→153.98`, `153.98→307.96`,
`307.96→461.94`.
After: all three overlay at `153.98`.

Affects every line-style multi-series report, not just auth.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Bug Fixes**
- Improved stacked chart behavior by consistently applying configured
series stacking settings.
- Ensured area charts can overlay correctly when no explicit stacking
configuration is provided.
  - Added safeguards for missing or invalid chart attribute data.

- **Tests**
- Added coverage for configured stack IDs, fallback behavior, empty
values, and invalid inputs.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-21 11:34:35 +02:00

61 lines
2.3 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { getStackId } from './ComposedChart.utils'
import type { MultiAttribute } from './ComposedChart.utils'
const attr = (attribute: string, stackId?: string): MultiAttribute => ({ attribute, stackId })
describe('getStackId', () => {
it('returns the explicit stackId when the attribute configures one', () => {
const attributes = [attr('ingress', 'traffic'), attr('egress', 'traffic')]
expect(getStackId(attributes, 'ingress', '1')).toBe('traffic')
expect(getStackId(attributes, 'egress', '1')).toBe('traffic')
})
it('falls back when the attribute has no stackId (overlay case)', () => {
const attributes = [attr('avg'), attr('min'), attr('max')]
expect(getStackId(attributes, 'avg', 'avg')).toBe('avg')
expect(getStackId(attributes, 'min', 'min')).toBe('min')
expect(getStackId(attributes, 'max', 'max')).toBe('max')
})
it('falls back to the shared bar id when no stackId is configured', () => {
const attributes = [attr('reads'), attr('writes')]
expect(getStackId(attributes, 'reads', '1')).toBe('1')
expect(getStackId(attributes, 'writes', '1')).toBe('1')
})
it('falls back when the attribute is not found', () => {
expect(getStackId([attr('reads', 'io')], 'writes', 'fallback')).toBe('fallback')
})
it('does not crash on undefined or null attributes', () => {
expect(getStackId(undefined, 'reads', '1')).toBe('1')
expect(getStackId(null, 'reads', '1')).toBe('1')
})
it('does not crash on undefined or null name', () => {
expect(getStackId([attr('reads', 'io')], undefined, '1')).toBe('1')
expect(getStackId([attr('reads', 'io')], null, '1')).toBe('1')
})
it('does not crash on falsy entries in the attributes array', () => {
const attributes: (MultiAttribute | false | null | undefined)[] = [
false,
null,
undefined,
attr('reads', 'io'),
]
expect(getStackId(attributes, 'reads', '1')).toBe('io')
expect(getStackId(attributes, 'writes', '1')).toBe('1')
})
it('does not crash on a non-array value', () => {
expect(getStackId('nope' as unknown as MultiAttribute[], 'reads', '1')).toBe('1')
})
it('treats an empty-string stackId as configured', () => {
expect(getStackId([attr('reads', '')], 'reads', '1')).toBe('')
})
})