Fix chart limits for two-dimensional group by (#16056)

The limit wasn't working properly for two dimensional stacked charts.
This PR fixes this.
This commit is contained in:
Raphaël Bosi
2025-11-25 15:31:25 +01:00
committed by GitHub
parent 8455ecc3e8
commit 42bba3de52
12 changed files with 246 additions and 6 deletions
@@ -0,0 +1,2 @@
// TODO: Remove this once backend returns total group count
export const EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS = 1;
@@ -0,0 +1 @@
export const BAR_CHART_MAXIMUM_NUMBER_OF_GROUPS_PER_BAR = 50;
@@ -1,7 +1,7 @@
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import { BAR_CHART_MAXIMUM_NUMBER_OF_BARS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMaximumNumberOfBars.constant';
import { type BarChartLayout } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartLayout';
import { type BarChartSeries } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartSeries';
import { getBarChartQueryLimit } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/getBarChartQueryLimit';
import { transformGroupByDataToBarChartData } from '@/page-layout/widgets/graph/graphWidgetBarChart/utils/transformGroupByDataToBarChartData';
import { useGraphWidgetGroupByQuery } from '@/page-layout/widgets/graph/hooks/useGraphWidgetGroupByQuery';
import { type RawDimensionValue } from '@/page-layout/widgets/graph/types/RawDimensionValue';
@@ -32,9 +32,6 @@ type UseGraphBarChartWidgetDataResult = {
>['objectMetadataItem'];
};
// TODO: Remove this once backend returns total group count
const EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS = 1;
export const useGraphBarChartWidgetData = ({
objectMetadataItemId,
configuration,
@@ -43,6 +40,8 @@ export const useGraphBarChartWidgetData = ({
objectId: objectMetadataItemId,
});
const limit = getBarChartQueryLimit(configuration);
const {
data: groupByData,
loading,
@@ -51,8 +50,7 @@ export const useGraphBarChartWidgetData = ({
} = useGraphWidgetGroupByQuery({
objectMetadataItemId,
configuration,
limit:
BAR_CHART_MAXIMUM_NUMBER_OF_BARS + EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS,
limit,
});
const transformedData = useMemo(
@@ -0,0 +1,48 @@
import { EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS } from '@/page-layout/widgets/graph/constants/ExtraItemToDetectTooManyGroups.constant';
import { BAR_CHART_MAXIMUM_NUMBER_OF_BARS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMaximumNumberOfBars.constant';
import { BAR_CHART_MAXIMUM_NUMBER_OF_GROUPS_PER_BAR } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMaximumNumberOfGroupsPerBar.constant';
import {
type BarChartConfiguration,
BarChartGroupMode,
} from '~/generated-metadata/graphql';
import { getBarChartQueryLimit } from '../getBarChartQueryLimit';
describe('getBarChartQueryLimit', () => {
it('should return one-dimensional limit for bar chart without secondary axis', () => {
const result = getBarChartQueryLimit({
__typename: 'BarChartConfiguration',
secondaryAxisGroupByFieldMetadataId: null,
groupMode: BarChartGroupMode.STACKED,
} as BarChartConfiguration);
expect(result).toBe(
BAR_CHART_MAXIMUM_NUMBER_OF_BARS + EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS,
);
});
it('should return two-dimensional stacked limit for bar chart with secondary axis and stacked mode', () => {
const result = getBarChartQueryLimit({
__typename: 'BarChartConfiguration',
secondaryAxisGroupByFieldMetadataId: 'some-field-id',
groupMode: BarChartGroupMode.STACKED,
} as BarChartConfiguration);
expect(result).toBe(
BAR_CHART_MAXIMUM_NUMBER_OF_BARS *
BAR_CHART_MAXIMUM_NUMBER_OF_GROUPS_PER_BAR +
EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS,
);
});
it('should return one-dimensional limit for bar chart with secondary axis and grouped mode', () => {
const result = getBarChartQueryLimit({
__typename: 'BarChartConfiguration',
secondaryAxisGroupByFieldMetadataId: 'some-field-id',
groupMode: BarChartGroupMode.GROUPED,
} as BarChartConfiguration);
expect(result).toBe(
BAR_CHART_MAXIMUM_NUMBER_OF_BARS + EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS,
);
});
});
@@ -0,0 +1,27 @@
import { EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS } from '@/page-layout/widgets/graph/constants/ExtraItemToDetectTooManyGroups.constant';
import { BAR_CHART_MAXIMUM_NUMBER_OF_BARS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMaximumNumberOfBars.constant';
import { BAR_CHART_MAXIMUM_NUMBER_OF_GROUPS_PER_BAR } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMaximumNumberOfGroupsPerBar.constant';
import { isChartConfigurationTwoDimensional } from '@/page-layout/widgets/graph/utils/isChartConfigurationTwoDimensional';
import {
BarChartGroupMode,
type BarChartConfiguration,
} from '~/generated/graphql';
export const getBarChartQueryLimit = (
configuration: BarChartConfiguration,
): number => {
if (
isChartConfigurationTwoDimensional(configuration) &&
configuration.groupMode === BarChartGroupMode.STACKED
) {
return (
BAR_CHART_MAXIMUM_NUMBER_OF_BARS *
BAR_CHART_MAXIMUM_NUMBER_OF_GROUPS_PER_BAR +
EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS
);
}
return (
BAR_CHART_MAXIMUM_NUMBER_OF_BARS + EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS
);
};
@@ -0,0 +1 @@
export const LINE_CHART_MAXIMUM_NUMBER_OF_NON_STACKED_SERIES = 50;
@@ -0,0 +1 @@
export const LINE_CHART_MAXIMUM_NUMBER_OF_STACKED_SERIES = 50;
@@ -1,5 +1,6 @@
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import { type LineChartSeries } from '@/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartSeries';
import { getLineChartQueryLimit } from '@/page-layout/widgets/graph/graphWidgetLineChart/utils/getLineChartQueryLimit';
import { useGraphWidgetGroupByQuery } from '@/page-layout/widgets/graph/hooks/useGraphWidgetGroupByQuery';
import { type RawDimensionValue } from '@/page-layout/widgets/graph/types/RawDimensionValue';
import { transformGroupByDataToLineChartData } from '@/page-layout/widgets/graph/utils/transformGroupByDataToLineChartData';
@@ -33,6 +34,8 @@ export const useGraphLineChartWidgetData = ({
objectId: objectMetadataItemId,
});
const limit = getLineChartQueryLimit(configuration);
const {
data: groupByData,
loading,
@@ -41,6 +44,7 @@ export const useGraphLineChartWidgetData = ({
} = useGraphWidgetGroupByQuery({
objectMetadataItemId,
configuration,
limit,
});
const transformedData = useMemo(
@@ -0,0 +1,49 @@
import { EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS } from '@/page-layout/widgets/graph/constants/ExtraItemToDetectTooManyGroups.constant';
import { LINE_CHART_MAXIMUM_NUMBER_OF_DATA_POINTS } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartMaximumNumberOfDataPoints.constant';
import { LINE_CHART_MAXIMUM_NUMBER_OF_NON_STACKED_SERIES } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartMaximumNumberOfNonStackedSeries.constant';
import { LINE_CHART_MAXIMUM_NUMBER_OF_STACKED_SERIES } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartMaximumNumberOfStackedSeries.constant';
import { type LineChartConfiguration } from '~/generated-metadata/graphql';
import { getLineChartQueryLimit } from '../getLineChartQueryLimit';
describe('getLineChartQueryLimit', () => {
it('should return one-dimensional limit for line chart without secondary axis', () => {
const result = getLineChartQueryLimit({
__typename: 'LineChartConfiguration',
secondaryAxisGroupByFieldMetadataId: null,
isStacked: false,
} as LineChartConfiguration);
expect(result).toBe(
LINE_CHART_MAXIMUM_NUMBER_OF_DATA_POINTS +
EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS,
);
});
it('should return two-dimensional stacked limit for line chart with secondary axis and stacked mode', () => {
const result = getLineChartQueryLimit({
__typename: 'LineChartConfiguration',
secondaryAxisGroupByFieldMetadataId: 'some-field-id',
isStacked: true,
} as LineChartConfiguration);
expect(result).toBe(
LINE_CHART_MAXIMUM_NUMBER_OF_DATA_POINTS *
LINE_CHART_MAXIMUM_NUMBER_OF_STACKED_SERIES +
EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS,
);
});
it('should return two-dimensional non-stacked limit for line chart with secondary axis and non-stacked mode', () => {
const result = getLineChartQueryLimit({
__typename: 'LineChartConfiguration',
secondaryAxisGroupByFieldMetadataId: 'some-field-id',
isStacked: false,
} as LineChartConfiguration);
expect(result).toBe(
LINE_CHART_MAXIMUM_NUMBER_OF_DATA_POINTS *
LINE_CHART_MAXIMUM_NUMBER_OF_NON_STACKED_SERIES +
EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS,
);
});
});
@@ -0,0 +1,33 @@
import { EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS } from '@/page-layout/widgets/graph/constants/ExtraItemToDetectTooManyGroups.constant';
import { LINE_CHART_MAXIMUM_NUMBER_OF_DATA_POINTS } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartMaximumNumberOfDataPoints.constant';
import { LINE_CHART_MAXIMUM_NUMBER_OF_NON_STACKED_SERIES } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartMaximumNumberOfNonStackedSeries.constant';
import { LINE_CHART_MAXIMUM_NUMBER_OF_STACKED_SERIES } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartMaximumNumberOfStackedSeries.constant';
import { isChartConfigurationTwoDimensional } from '@/page-layout/widgets/graph/utils/isChartConfigurationTwoDimensional';
import { type LineChartConfiguration } from '~/generated/graphql';
export const getLineChartQueryLimit = (
configuration: LineChartConfiguration,
): number => {
const isTwoDimensional = isChartConfigurationTwoDimensional(configuration);
if (!isTwoDimensional) {
return (
LINE_CHART_MAXIMUM_NUMBER_OF_DATA_POINTS +
EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS
);
}
if (configuration.isStacked === true) {
return (
LINE_CHART_MAXIMUM_NUMBER_OF_DATA_POINTS *
LINE_CHART_MAXIMUM_NUMBER_OF_STACKED_SERIES +
EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS
);
}
return (
LINE_CHART_MAXIMUM_NUMBER_OF_DATA_POINTS *
LINE_CHART_MAXIMUM_NUMBER_OF_NON_STACKED_SERIES +
EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS
);
};
@@ -0,0 +1,65 @@
import {
type BarChartConfiguration,
type LineChartConfiguration,
} from '~/generated-metadata/graphql';
import { isChartConfigurationTwoDimensional } from '../isChartConfigurationTwoDimensional';
describe('isChartConfigurationTwoDimensional', () => {
describe('BarChartConfiguration', () => {
it('should return true for bar chart with secondary axis', () => {
const result = isChartConfigurationTwoDimensional({
__typename: 'BarChartConfiguration',
secondaryAxisGroupByFieldMetadataId: 'some-field-id',
} as BarChartConfiguration);
expect(result).toBe(true);
});
it('should return false for bar chart without secondary axis', () => {
const result = isChartConfigurationTwoDimensional({
__typename: 'BarChartConfiguration',
secondaryAxisGroupByFieldMetadataId: null,
} as BarChartConfiguration);
expect(result).toBe(false);
});
it('should return false for bar chart with undefined secondary axis', () => {
const result = isChartConfigurationTwoDimensional({
__typename: 'BarChartConfiguration',
secondaryAxisGroupByFieldMetadataId: undefined,
} as BarChartConfiguration);
expect(result).toBe(false);
});
});
describe('LineChartConfiguration', () => {
it('should return true for line chart with secondary axis', () => {
const result = isChartConfigurationTwoDimensional({
__typename: 'LineChartConfiguration',
secondaryAxisGroupByFieldMetadataId: 'some-field-id',
} as LineChartConfiguration);
expect(result).toBe(true);
});
it('should return false for line chart without secondary axis', () => {
const result = isChartConfigurationTwoDimensional({
__typename: 'LineChartConfiguration',
secondaryAxisGroupByFieldMetadataId: null,
} as LineChartConfiguration);
expect(result).toBe(false);
});
it('should return false for line chart with undefined secondary axis', () => {
const result = isChartConfigurationTwoDimensional({
__typename: 'LineChartConfiguration',
secondaryAxisGroupByFieldMetadataId: undefined,
} as LineChartConfiguration);
expect(result).toBe(false);
});
});
});
@@ -0,0 +1,11 @@
import { isDefined } from 'twenty-shared/utils';
import {
type BarChartConfiguration,
type LineChartConfiguration,
} from '~/generated/graphql';
export const isChartConfigurationTwoDimensional = (
configuration: BarChartConfiguration | LineChartConfiguration,
): boolean => {
return isDefined(configuration.secondaryAxisGroupByFieldMetadataId);
};