diff --git a/packages/twenty-front/src/modules/page-layout/hooks/__tests__/usePageLayoutWithRelationWidgets.test.tsx b/packages/twenty-front/src/modules/page-layout/hooks/__tests__/usePageLayoutWithRelationWidgets.test.tsx index 6792ed9e2f..f76ad20f76 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/__tests__/usePageLayoutWithRelationWidgets.test.tsx +++ b/packages/twenty-front/src/modules/page-layout/hooks/__tests__/usePageLayoutWithRelationWidgets.test.tsx @@ -5,6 +5,9 @@ import { type PageLayout } from '@/page-layout/types/PageLayout'; import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext'; import { renderHook } from '@testing-library/react'; import { + AggregateOperations, + BarChartLayout, + GraphOrderBy, PageLayoutType, WidgetConfigurationType, WidgetType, @@ -100,11 +103,11 @@ describe('usePageLayoutWithRelationWidgets', () => { configuration: { __typename: 'BarChartConfiguration', configurationType: WidgetConfigurationType.BAR_CHART, - layout: 'VERTICAL', - aggregateOperation: 'COUNT', + layout: BarChartLayout.VERTICAL, + aggregateOperation: AggregateOperations.COUNT, aggregateFieldMetadataId: 'id', primaryAxisGroupByFieldMetadataId: 'createdAt', - primaryAxisOrderBy: 'FIELD_ASC', + primaryAxisOrderBy: GraphOrderBy.FIELD_ASC, displayDataLabel: false, }, createdAt: new Date().toISOString(), @@ -222,11 +225,11 @@ describe('usePageLayoutWithRelationWidgets', () => { configuration: { __typename: 'BarChartConfiguration', configurationType: WidgetConfigurationType.BAR_CHART, - layout: 'VERTICAL', - aggregateOperation: 'COUNT', + layout: BarChartLayout.VERTICAL, + aggregateOperation: AggregateOperations.COUNT, aggregateFieldMetadataId: 'id', primaryAxisGroupByFieldMetadataId: 'createdAt', - primaryAxisOrderBy: 'FIELD_ASC', + primaryAxisOrderBy: GraphOrderBy.FIELD_ASC, displayDataLabel: false, }, createdAt: new Date().toISOString(), diff --git a/packages/twenty-server/src/modules/dashboard/chart-data/services/__tests__/line-chart-data.service.spec.ts b/packages/twenty-server/src/modules/dashboard/chart-data/services/__tests__/line-chart-data.service.spec.ts index e0d29cf70d..840a67b64a 100644 --- a/packages/twenty-server/src/modules/dashboard/chart-data/services/__tests__/line-chart-data.service.spec.ts +++ b/packages/twenty-server/src/modules/dashboard/chart-data/services/__tests__/line-chart-data.service.spec.ts @@ -316,14 +316,14 @@ describe('LineChartDataService', () => { authContext: mockAuthContext, }); - const stageA = result.series.find((s) => s.id === 'Stage A'); + const stageA = result.series.find((s) => s.label === 'Stage A'); expect(stageA?.data).toHaveLength(3); expect(stageA?.data[0].y).toBe(100); expect(stageA?.data[1].y).toBe(200); expect(stageA?.data[2].y).toBe(0); - const stageB = result.series.find((s) => s.id === 'Stage B'); + const stageB = result.series.find((s) => s.label === 'Stage B'); expect(stageB?.data).toHaveLength(3); expect(stageB?.data[0].y).toBe(150); @@ -349,8 +349,8 @@ describe('LineChartDataService', () => { authContext: mockAuthContext, }); - const seriesA = result.series.find((s) => s.id === 'A'); - const seriesB = result.series.find((s) => s.id === 'B'); + const seriesA = result.series.find((s) => s.label === 'A'); + const seriesB = result.series.find((s) => s.label === 'B'); expect(seriesA?.data[0].y).toBe(10); expect(seriesA?.data[1].y).toBe(30); @@ -380,8 +380,8 @@ describe('LineChartDataService', () => { authContext: mockAuthContext, }); - const seriesA = result.series.find((s) => s.id === 'A'); - const seriesB = result.series.find((s) => s.id === 'B'); + const seriesA = result.series.find((s) => s.label === 'A'); + const seriesB = result.series.find((s) => s.label === 'B'); expect(seriesA).toBeDefined(); expect(seriesB).toBeDefined(); diff --git a/packages/twenty-server/src/modules/dashboard/chart-data/services/line-chart-data.service.ts b/packages/twenty-server/src/modules/dashboard/chart-data/services/line-chart-data.service.ts index e66e33f0ba..f89c39d800 100644 --- a/packages/twenty-server/src/modules/dashboard/chart-data/services/line-chart-data.service.ts +++ b/packages/twenty-server/src/modules/dashboard/chart-data/services/line-chart-data.service.ts @@ -36,6 +36,7 @@ import { processOneDimensionalResults } from 'src/modules/dashboard/chart-data/u import { processTwoDimensionalResults } from 'src/modules/dashboard/chart-data/utils/process-two-dimensional-results.util'; import { sortChartDataIfNeeded } from 'src/modules/dashboard/chart-data/utils/sort-chart-data-if-needed.util'; import { sortSecondaryAxisData } from 'src/modules/dashboard/chart-data/utils/sort-secondary-axis-data.util'; +import { buildLineChartSeriesIdPrefix } from 'src/modules/dashboard/chart-data/utils/build-line-chart-series-id-prefix.util'; type GetLineChartDataParams = { workspaceId: string; @@ -164,6 +165,11 @@ export class LineChartDataService { secondaryAxisOrderBy: configuration.secondaryAxisOrderBy, }); + const seriesIdPrefix = buildLineChartSeriesIdPrefix( + objectMetadataId, + configuration, + ); + if (isTwoDimensional && isDefined(secondaryAxisGroupByField)) { return this.transformToTwoDimensionalLineChartData({ rawResults, @@ -173,6 +179,7 @@ export class LineChartDataService { configuration, userTimezone, firstDayOfTheWeek, + seriesIdPrefix, }); } @@ -183,6 +190,7 @@ export class LineChartDataService { configuration, userTimezone, firstDayOfTheWeek, + seriesIdPrefix, }); } catch (error) { if (error instanceof ChartDataException) { @@ -206,6 +214,7 @@ export class LineChartDataService { configuration, userTimezone, firstDayOfTheWeek, + seriesIdPrefix, }: { rawResults: GroupByRawResult[]; primaryAxisGroupByField: FlatFieldMetadata; @@ -213,6 +222,7 @@ export class LineChartDataService { configuration: LineChartConfigurationDTO; userTimezone: string; firstDayOfTheWeek: CalendarStartDay; + seriesIdPrefix: string; }): LineChartDataOutputDTO { const filteredResults = configuration.omitNullValues ? rawResults.filter( @@ -299,7 +309,7 @@ export class LineChartDataService { const series = [ { - id: aggregateField.name, + id: `${seriesIdPrefix}${aggregateField.name}`, label: aggregateField.label, data: dataPoints, }, @@ -329,6 +339,7 @@ export class LineChartDataService { configuration, userTimezone, firstDayOfTheWeek, + seriesIdPrefix, }: { rawResults: GroupByRawResult[]; primaryAxisGroupByField: FlatFieldMetadata; @@ -337,6 +348,7 @@ export class LineChartDataService { configuration: LineChartConfigurationDTO; userTimezone: string; firstDayOfTheWeek: CalendarStartDay; + seriesIdPrefix: string; }): LineChartDataOutputDTO { const filteredResults = configuration.omitNullValues ? rawResults.filter( @@ -491,6 +503,7 @@ export class LineChartDataService { const series = limitedSeriesIds.map((seriesId) => { const xToYMap = seriesMap.get(seriesId) ?? new Map(); + const prefixedSeriesId = `${seriesIdPrefix}${seriesId}`; let dataPoints = filteredXValues.map((xValue) => ({ x: xValue, @@ -502,7 +515,7 @@ export class LineChartDataService { } return { - id: seriesId, + id: prefixedSeriesId, label: seriesId, data: dataPoints, }; @@ -522,6 +535,14 @@ export class LineChartDataService { ...secondaryFormattedToRawLookup, ]); + for (const seriesId of limitedSeriesIds) { + const rawValue = secondaryFormattedToRawLookup.get(seriesId); + + if (isDefined(rawValue)) { + mergedLookup.set(`${seriesIdPrefix}${seriesId}`, rawValue); + } + } + return { series, xAxisLabel, diff --git a/packages/twenty-server/src/modules/dashboard/chart-data/utils/build-line-chart-series-id-prefix.util.ts b/packages/twenty-server/src/modules/dashboard/chart-data/utils/build-line-chart-series-id-prefix.util.ts new file mode 100644 index 0000000000..b632736690 --- /dev/null +++ b/packages/twenty-server/src/modules/dashboard/chart-data/utils/build-line-chart-series-id-prefix.util.ts @@ -0,0 +1,17 @@ +import { createHash } from 'crypto'; + +import type { LineChartConfigurationDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/line-chart-configuration.dto'; + +export const buildLineChartSeriesIdPrefix = ( + objectMetadataId: string, + configuration: LineChartConfigurationDTO, +): string => { + const hash = createHash('sha256') + .update(objectMetadataId) + .update(':') + .update(JSON.stringify(configuration)) + .digest('hex') + .slice(0, 16); + + return `lc_${hash}:`; +};