From accd55d7cbfb68212a014383fb081ca402e2097e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Thu, 27 Nov 2025 16:13:06 +0100 Subject: [PATCH] [DASHBOARDS] Add default order by and date granularity when choosing field (#16143) ## QA https://github.com/user-attachments/assets/b512eea0-26d1-4e1c-b8b9-f993a5c0d0fb https://github.com/user-attachments/assets/0222600b-8a9a-44dc-a992-2a234712c913 --- ...oupByFieldSelectionDropdownContentBase.tsx | 63 ++++------ ...buildChartGroupByFieldConfigUpdate.test.ts | 77 ++++++++++++ .../buildChartGroupByFieldConfigUpdate.ts | 111 ++++++++++++++++++ 3 files changed, 209 insertions(+), 42 deletions(-) create mode 100644 packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/__tests__/buildChartGroupByFieldConfigUpdate.test.ts create mode 100644 packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/buildChartGroupByFieldConfigUpdate.ts diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartGroupByFieldSelectionDropdownContentBase.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartGroupByFieldSelectionDropdownContentBase.tsx index 95ecff8964..f6aa974cc6 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartGroupByFieldSelectionDropdownContentBase.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartGroupByFieldSelectionDropdownContentBase.tsx @@ -3,6 +3,7 @@ import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pa import { useUpdateCurrentWidgetConfig } from '@/command-menu/pages/page-layout/hooks/useUpdateCurrentWidgetConfig'; import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode'; import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/ChartConfiguration'; +import { buildChartGroupByFieldConfigUpdate } from '@/command-menu/pages/page-layout/utils/buildChartGroupByFieldConfigUpdate'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType'; @@ -22,7 +23,6 @@ import { useMemo, useState } from 'react'; import { isDefined } from 'twenty-shared/utils'; import { useIcons } from 'twenty-ui/display'; import { MenuItemSelect } from 'twenty-ui/navigation'; -import { BarChartGroupMode } from '~/generated/graphql'; import { filterBySearchQuery } from '~/utils/filterBySearchQuery'; type ChartGroupByFieldSelectionDropdownContentBaseProps< @@ -93,48 +93,18 @@ export const ChartGroupByFieldSelectionDropdownContentBase = < return null; } - const buildConfigUpdate = ( - fieldId: string | null, - subFieldName: string | null, - ) => { - const isSecondaryAxis = - fieldMetadataIdKey === 'secondaryAxisGroupByFieldMetadataId'; - const baseConfig = { - [fieldMetadataIdKey]: fieldId, - [subFieldNameKey]: subFieldName, - }; - - if (!isSecondaryAxis) { - return baseConfig; - } - - if (configuration.__typename === 'BarChartConfiguration') { - return { - ...baseConfig, - groupMode: isDefined(fieldId) - ? (configuration.groupMode ?? BarChartGroupMode.STACKED) - : null, - }; - } - - if (configuration.__typename === 'LineChartConfiguration') { - return { - ...baseConfig, - isStacked: isDefined(fieldId) - ? (configuration.isStacked ?? true) - : null, - }; - } - - return baseConfig; - }; - const handleSelectField = (fieldMetadataItem: FieldMetadataItem) => { if (isCompositeFieldType(fieldMetadataItem.type)) { setSelectedCompositeField(fieldMetadataItem); } else { updateCurrentWidgetConfig({ - configToUpdate: buildConfigUpdate(fieldMetadataItem.id, null), + configToUpdate: buildChartGroupByFieldConfigUpdate({ + configuration, + fieldMetadataIdKey, + subFieldNameKey, + fieldId: fieldMetadataItem.id, + subFieldName: null, + }), }); closeDropdown(); } @@ -142,7 +112,13 @@ export const ChartGroupByFieldSelectionDropdownContentBase = < const handleSelectNone = () => { updateCurrentWidgetConfig({ - configToUpdate: buildConfigUpdate(null, null), + configToUpdate: buildChartGroupByFieldConfigUpdate({ + configuration, + fieldMetadataIdKey, + subFieldNameKey, + fieldId: null, + subFieldName: null, + }), }); closeDropdown(); }; @@ -157,10 +133,13 @@ export const ChartGroupByFieldSelectionDropdownContentBase = < } updateCurrentWidgetConfig({ - configToUpdate: buildConfigUpdate( - selectedCompositeField.id, + configToUpdate: buildChartGroupByFieldConfigUpdate({ + configuration, + fieldMetadataIdKey, + subFieldNameKey, + fieldId: selectedCompositeField.id, subFieldName, - ), + }), }); closeDropdown(); }; diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/__tests__/buildChartGroupByFieldConfigUpdate.test.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/__tests__/buildChartGroupByFieldConfigUpdate.test.ts new file mode 100644 index 0000000000..4ee5811ae3 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/__tests__/buildChartGroupByFieldConfigUpdate.test.ts @@ -0,0 +1,77 @@ +import { ObjectRecordGroupByDateGranularity } from 'twenty-shared/types'; +import { + type BarChartConfiguration, + BarChartGroupMode, + GraphOrderBy, + type PieChartConfiguration, +} from '~/generated/graphql'; +import { buildChartGroupByFieldConfigUpdate } from '../buildChartGroupByFieldConfigUpdate'; + +describe('buildChartGroupByFieldConfigUpdate', () => { + it('sets default orderBy and dateGranularity for primary axis', () => { + const result = buildChartGroupByFieldConfigUpdate({ + configuration: { + __typename: 'BarChartConfiguration', + } as BarChartConfiguration, + fieldMetadataIdKey: 'primaryAxisGroupByFieldMetadataId', + subFieldNameKey: 'primaryAxisGroupBySubFieldName', + fieldId: 'field', + subFieldName: null, + }); + + expect(result).toMatchObject({ + primaryAxisOrderBy: GraphOrderBy.FIELD_ASC, + primaryAxisDateGranularity: ObjectRecordGroupByDateGranularity.DAY, + }); + }); + + it('clears orderBy and dateGranularity when primary axis fieldId is null', () => { + const result = buildChartGroupByFieldConfigUpdate({ + configuration: { + __typename: 'BarChartConfiguration', + } as BarChartConfiguration, + fieldMetadataIdKey: 'primaryAxisGroupByFieldMetadataId', + subFieldNameKey: 'primaryAxisGroupBySubFieldName', + fieldId: null, + subFieldName: null, + }); + + expect(result).toMatchObject({ + primaryAxisOrderBy: null, + primaryAxisDateGranularity: null, + }); + }); + + it('sets groupMode for bar chart secondary axis', () => { + const result = buildChartGroupByFieldConfigUpdate({ + configuration: { + __typename: 'BarChartConfiguration', + } as BarChartConfiguration, + fieldMetadataIdKey: 'secondaryAxisGroupByFieldMetadataId', + subFieldNameKey: 'secondaryAxisGroupBySubFieldName', + fieldId: 'field', + subFieldName: null, + }); + + expect(result).toMatchObject({ + groupMode: BarChartGroupMode.STACKED, + }); + }); + + it('sets orderBy and dateGranularity for pie chart groupBy', () => { + const result = buildChartGroupByFieldConfigUpdate({ + configuration: { + __typename: 'PieChartConfiguration', + } as PieChartConfiguration, + fieldMetadataIdKey: 'groupByFieldMetadataId', + subFieldNameKey: 'groupBySubFieldName', + fieldId: 'field', + subFieldName: null, + }); + + expect(result).toMatchObject({ + orderBy: GraphOrderBy.FIELD_ASC, + dateGranularity: ObjectRecordGroupByDateGranularity.DAY, + }); + }); +}); diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/buildChartGroupByFieldConfigUpdate.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/buildChartGroupByFieldConfigUpdate.ts new file mode 100644 index 0000000000..2898eb9060 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/buildChartGroupByFieldConfigUpdate.ts @@ -0,0 +1,111 @@ +import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/ChartConfiguration'; +import { ObjectRecordGroupByDateGranularity } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; +import { BarChartGroupMode, GraphOrderBy } from '~/generated/graphql'; + +type BuildChartGroupByFieldConfigUpdateArgs = { + configuration: T; + fieldMetadataIdKey: keyof T; + subFieldNameKey: keyof T; + fieldId: string | null; + subFieldName: string | null; +}; + +export const buildChartGroupByFieldConfigUpdate = < + T extends ChartConfiguration, +>({ + configuration, + fieldMetadataIdKey, + subFieldNameKey, + fieldId, + subFieldName, +}: BuildChartGroupByFieldConfigUpdateArgs) => { + const isPrimaryAxis = + fieldMetadataIdKey === 'primaryAxisGroupByFieldMetadataId'; + const isSecondaryAxis = + fieldMetadataIdKey === 'secondaryAxisGroupByFieldMetadataId'; + const isPieChartGroupBy = fieldMetadataIdKey === 'groupByFieldMetadataId'; + + const baseConfig = { + [fieldMetadataIdKey]: fieldId, + [subFieldNameKey]: subFieldName, + }; + + const isBarChart = configuration.__typename === 'BarChartConfiguration'; + const isLineChart = configuration.__typename === 'LineChartConfiguration'; + const isPieChart = configuration.__typename === 'PieChartConfiguration'; + + if (isPrimaryAxis) { + const existingOrderBy = + isBarChart || isLineChart ? configuration.primaryAxisOrderBy : null; + + const existingDateGranularity = + isBarChart || isLineChart + ? configuration.primaryAxisDateGranularity + : null; + + return { + ...baseConfig, + primaryAxisOrderBy: isDefined(fieldId) + ? (existingOrderBy ?? GraphOrderBy.FIELD_ASC) + : null, + primaryAxisDateGranularity: isDefined(fieldId) + ? (existingDateGranularity ?? ObjectRecordGroupByDateGranularity.DAY) + : null, + }; + } + + if (isPieChartGroupBy) { + const existingOrderBy = isPieChart ? configuration.orderBy : null; + + const existingDateGranularity = isPieChart + ? configuration.dateGranularity + : null; + + return { + ...baseConfig, + orderBy: isDefined(fieldId) + ? (existingOrderBy ?? GraphOrderBy.FIELD_ASC) + : null, + dateGranularity: isDefined(fieldId) + ? (existingDateGranularity ?? ObjectRecordGroupByDateGranularity.DAY) + : null, + }; + } + + if (!isSecondaryAxis) { + return baseConfig; + } + + if (isBarChart) { + return { + ...baseConfig, + secondaryAxisOrderBy: isDefined(fieldId) + ? (configuration.secondaryAxisOrderBy ?? GraphOrderBy.FIELD_ASC) + : null, + secondaryAxisGroupByDateGranularity: isDefined(fieldId) + ? (configuration.secondaryAxisGroupByDateGranularity ?? + ObjectRecordGroupByDateGranularity.DAY) + : null, + groupMode: isDefined(fieldId) + ? (configuration.groupMode ?? BarChartGroupMode.STACKED) + : null, + }; + } + + if (isLineChart) { + return { + ...baseConfig, + secondaryAxisOrderBy: isDefined(fieldId) + ? (configuration.secondaryAxisOrderBy ?? GraphOrderBy.FIELD_ASC) + : null, + secondaryAxisGroupByDateGranularity: isDefined(fieldId) + ? (configuration.secondaryAxisGroupByDateGranularity ?? + ObjectRecordGroupByDateGranularity.DAY) + : null, + isStacked: isDefined(fieldId) ? (configuration.isStacked ?? true) : null, + }; + } + + return baseConfig; +};