From 809791a4a8d857dd29ae9cbcf38865ece28b7518 Mon Sep 17 00:00:00 2001 From: nitin <142569587+ehconitin@users.noreply.github.com> Date: Fri, 31 Oct 2025 18:24:13 +0530 Subject: [PATCH] fix: Orderby secondary dimension sum (#15493) before: https://github.com/user-attachments/assets/6919c45b-1435-408f-87e3-0452dd70fbdb after: https://github.com/user-attachments/assets/efa60d28-ac04-4219-831a-92aada13fa9c --- ...arChartDataBySecondaryDimensionSum.test.ts | 144 ++++++++++++++++++ ...sortBarChartDataBySecondaryDimensionSum.ts | 46 ++++++ ...formTwoDimensionalGroupByToBarChartData.ts | 12 +- 3 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/sortBarChartDataBySecondaryDimensionSum.test.ts create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/utils/sortBarChartDataBySecondaryDimensionSum.ts diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/sortBarChartDataBySecondaryDimensionSum.test.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/sortBarChartDataBySecondaryDimensionSum.test.ts new file mode 100644 index 00000000000..9c0e97bb672 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/sortBarChartDataBySecondaryDimensionSum.test.ts @@ -0,0 +1,144 @@ +import { GraphOrderBy } from '~/generated/graphql'; +import { sortBarChartDataBySecondaryDimensionSum } from '../sortBarChartDataBySecondaryDimensionSum'; + +describe('sortBarChartDataBySecondaryDimensionSum', () => { + const mockData = [ + { city: 'Paris', Open: 5, Closed: 10 }, + { city: 'London', Open: 20, Closed: 2 }, + { city: 'Berlin', Open: 8, Closed: 7 }, + ]; + + const keys = ['Open', 'Closed']; + + describe('VALUE_DESC sorting', () => { + it('should sort by totals in descending order', () => { + const result = sortBarChartDataBySecondaryDimensionSum({ + data: mockData, + keys, + orderBy: GraphOrderBy.VALUE_DESC, + }); + + expect(result).toEqual([ + { city: 'London', Open: 20, Closed: 2 }, + { city: 'Paris', Open: 5, Closed: 10 }, + { city: 'Berlin', Open: 8, Closed: 7 }, + ]); + }); + }); + + describe('VALUE_ASC sorting', () => { + it('should sort by totals in ascending order', () => { + const result = sortBarChartDataBySecondaryDimensionSum({ + data: mockData, + keys, + orderBy: GraphOrderBy.VALUE_ASC, + }); + + expect(result).toEqual([ + { city: 'Paris', Open: 5, Closed: 10 }, + { city: 'Berlin', Open: 8, Closed: 7 }, + { city: 'London', Open: 20, Closed: 2 }, + ]); + }); + }); + + describe('FIELD_ASC/FIELD_DESC (non-value sorting)', () => { + it('should not sort when orderBy is FIELD_ASC', () => { + const result = sortBarChartDataBySecondaryDimensionSum({ + data: mockData, + keys, + orderBy: GraphOrderBy.FIELD_ASC, + }); + + expect(result).toEqual(mockData); + }); + + it('should not sort when orderBy is FIELD_DESC', () => { + const result = sortBarChartDataBySecondaryDimensionSum({ + data: mockData, + keys, + orderBy: GraphOrderBy.FIELD_DESC, + }); + + expect(result).toEqual(mockData); + }); + }); + + describe('edge cases', () => { + it('should handle data with missing keys in some items', () => { + const dataWithMissing = [ + { city: 'Paris', Open: 5 } as any, + { city: 'London', Open: 20, Closed: 2 }, + ]; + + const result = sortBarChartDataBySecondaryDimensionSum({ + data: dataWithMissing, + keys, + orderBy: GraphOrderBy.VALUE_DESC, + }); + + expect(result).toEqual([ + { city: 'London', Open: 20, Closed: 2 }, + { city: 'Paris', Open: 5 }, + ]); + }); + + it('should handle data with zero values', () => { + const dataWithZeros = [ + { city: 'Paris', Open: 0, Closed: 10 }, + { city: 'London', Open: 20, Closed: 0 }, + ]; + + const result = sortBarChartDataBySecondaryDimensionSum({ + data: dataWithZeros, + keys, + orderBy: GraphOrderBy.VALUE_DESC, + }); + + expect(result).toEqual([ + { city: 'London', Open: 20, Closed: 0 }, + { city: 'Paris', Open: 0, Closed: 10 }, + ]); + }); + + it('should handle empty data array', () => { + const result = sortBarChartDataBySecondaryDimensionSum({ + data: [], + keys, + orderBy: GraphOrderBy.VALUE_DESC, + }); + + expect(result).toEqual([]); + }); + + it('should handle single item', () => { + const singleItem = [{ city: 'Paris', Open: 5, Closed: 10 }]; + + const result = sortBarChartDataBySecondaryDimensionSum({ + data: singleItem, + keys, + orderBy: GraphOrderBy.VALUE_DESC, + }); + + expect(result).toEqual(singleItem); + }); + }); + + describe('stability with equal totals', () => { + it('should maintain original order for items with equal totals', () => { + const dataWithEqualTotals = [ + { city: 'Paris', Open: 10, Closed: 5 }, + { city: 'Berlin', Open: 7, Closed: 8 }, + { city: 'Madrid', Open: 9, Closed: 6 }, + ]; + + const result = sortBarChartDataBySecondaryDimensionSum({ + data: dataWithEqualTotals, + keys, + orderBy: GraphOrderBy.VALUE_DESC, + }); + + expect(result).toEqual(dataWithEqualTotals); + }); + }); +}); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/sortBarChartDataBySecondaryDimensionSum.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/sortBarChartDataBySecondaryDimensionSum.ts new file mode 100644 index 00000000000..20aa0e9bd1b --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/sortBarChartDataBySecondaryDimensionSum.ts @@ -0,0 +1,46 @@ +import { type BarChartDataItem } from '@/page-layout/widgets/graph/graphWidgetBarChart/types/BarChartDataItem'; +import { isDefined } from 'twenty-shared/utils'; +import { GraphOrderBy } from '~/generated/graphql'; + +type SortBarChartDataBySecondaryDimensionSumParams = { + data: BarChartDataItem[]; + keys: string[]; + orderBy: GraphOrderBy; +}; + +export const sortBarChartDataBySecondaryDimensionSum = ({ + data, + keys, + orderBy, +}: SortBarChartDataBySecondaryDimensionSumParams): BarChartDataItem[] => { + if ( + orderBy !== GraphOrderBy.VALUE_ASC && + orderBy !== GraphOrderBy.VALUE_DESC + ) { + return data; + } + + const dataWithSecondaryDimensionSums = data.map((barChartDataItem) => { + const secondaryDimensionSum = keys.reduce((sumAccumulator, segmentKey) => { + const segmentValue = barChartDataItem[segmentKey]; + if (isDefined(segmentValue) && typeof segmentValue === 'number') { + return sumAccumulator + segmentValue; + } + return sumAccumulator; + }, 0); + + return { barChartDataItem, secondaryDimensionSum }; + }); + + dataWithSecondaryDimensionSums.sort((a, b) => { + if (orderBy === GraphOrderBy.VALUE_ASC) { + return a.secondaryDimensionSum - b.secondaryDimensionSum; + } else { + return b.secondaryDimensionSum - a.secondaryDimensionSum; + } + }); + + return dataWithSecondaryDimensionSums.map( + ({ barChartDataItem }) => barChartDataItem, + ); +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/transformTwoDimensionalGroupByToBarChartData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/transformTwoDimensionalGroupByToBarChartData.ts index 886d7ee4af6..7a371cc5c9a 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/transformTwoDimensionalGroupByToBarChartData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/transformTwoDimensionalGroupByToBarChartData.ts @@ -10,6 +10,7 @@ import { computeAggregateValueFromGroupByResult } from '@/page-layout/widgets/gr import { formatDimensionValue } from '@/page-layout/widgets/graph/utils/formatDimensionValue'; import { getFieldKey } from '@/page-layout/widgets/graph/utils/getFieldKey'; import { getSortedKeys } from '@/page-layout/widgets/graph/utils/getSortedKeys'; +import { sortBarChartDataBySecondaryDimensionSum } from '@/page-layout/widgets/graph/utils/sortBarChartDataBySecondaryDimensionSum'; import { isDefined } from 'twenty-shared/utils'; import { BarChartGroupMode, @@ -132,8 +133,17 @@ export const transformTwoDimensionalGroupByToBarChartData = ({ color: configuration.color as GraphColor, })); + const unsortedData = Array.from(dataMap.values()); + const data = isDefined(configuration.primaryAxisOrderBy) + ? sortBarChartDataBySecondaryDimensionSum({ + data: unsortedData, + keys, + orderBy: configuration.primaryAxisOrderBy, + }) + : unsortedData; + return { - data: Array.from(dataMap.values()), + data, indexBy: indexByKey, keys, series,