diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/ChartSettings.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/ChartSettings.tsx index cff403c647..24bbef59d7 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/ChartSettings.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/ChartSettings.tsx @@ -11,7 +11,7 @@ import { usePageLayoutIdFromContextStoreTargetedRecord } from '@/command-menu/pa import { useUpdateChartSettingInput } from '@/command-menu/pages/page-layout/hooks/useUpdateChartSettingInput'; import { useUpdateChartSettingToggle } from '@/command-menu/pages/page-layout/hooks/useUpdateChartSettingToggle'; import { useUpdateCurrentWidgetConfig } from '@/command-menu/pages/page-layout/hooks/useUpdateCurrentWidgetConfig'; -import { useUpdateGraphTypeConfig } from '@/command-menu/pages/page-layout/hooks/useUpdateGraphTypeConfig'; +import { useGetConfigToUpdateAfterGraphTypeChange } from '@/command-menu/pages/page-layout/hooks/useUpdateGraphTypeConfig'; import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/ChartConfiguration'; import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds'; import { shouldHideChartSetting } from '@/command-menu/pages/page-layout/utils/shouldHideChartSetting'; @@ -19,6 +19,7 @@ import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { BAR_CHART_MAXIMUM_NUMBER_OF_BARS } from '@/page-layout/widgets/graph/graphWidgetBarChart/constants/BarChartMaximumNumberOfBars.constant'; import { LINE_CHART_MAXIMUM_NUMBER_OF_DATA_POINTS } from '@/page-layout/widgets/graph/graphWidgetLineChart/constants/LineChartMaximumNumberOfDataPoints.constant'; +import { PIE_CHART_MAXIMUM_NUMBER_OF_SLICES } from '@/page-layout/widgets/graph/graphWidgetPieChart/constants/PieChartMaximumNumberOfSlices.constant'; import { hasWidgetTooManyGroupsComponentState } from '@/page-layout/widgets/graph/states/hasWidgetTooManyGroupsComponentState'; import { useOpenDropdown } from '@/ui/layout/dropdown/hooks/useOpenDropdown'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; @@ -76,11 +77,11 @@ export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => { const { updateChartSettingInput } = useUpdateChartSettingInput(pageLayoutId); - const { updateGraphTypeConfig } = useUpdateGraphTypeConfig({ - pageLayoutId, - widget, - configuration, - }); + const { getConfigToUpdateAfterGraphTypeChange } = + useGetConfigToUpdateAfterGraphTypeChange({ + pageLayoutId, + widget, + }); const isGroupByEnabled = getChartSettingsValues( CHART_CONFIGURATION_SETTING_IDS.GROUP_BY, @@ -89,7 +90,7 @@ export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => { useRecoilComponentState(hasWidgetTooManyGroupsComponentState); const handleGraphTypeChange = (graphType: GraphType) => { - const configToUpdate = updateGraphTypeConfig(graphType); + const configToUpdate = getConfigToUpdateAfterGraphTypeChange(graphType); updateCurrentWidgetConfig({ configToUpdate, @@ -133,7 +134,9 @@ export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => { configuration.__typename === 'BarChartConfiguration' || configuration.__typename === 'LineChartConfiguration' ? configuration.primaryAxisGroupByFieldMetadataId - : null; + : configuration.__typename === 'PieChartConfiguration' + ? configuration.groupByFieldMetadataId + : null; const primaryAxisField = objectMetadataItem?.fields?.find( (field) => field.id === primaryAxisFieldMetadataId, @@ -155,7 +158,10 @@ export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => { message={ currentGraphType === GraphType.LINE ? t`Undisplayed data: max ${LINE_CHART_MAXIMUM_NUMBER_OF_DATA_POINTS} data points per chart.` - : t`Undisplayed data: max ${BAR_CHART_MAXIMUM_NUMBER_OF_BARS} bars per chart.` + : currentGraphType === GraphType.VERTICAL_BAR || + currentGraphType === GraphType.HORIZONTAL_BAR + ? t`Undisplayed data: max ${BAR_CHART_MAXIMUM_NUMBER_OF_BARS} bars per chart.` + : t`Undisplayed data: max ${PIE_CHART_MAXIMUM_NUMBER_OF_SLICES} slices per chart.` } tooltipMessage={ isPrimaryAxisDate diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartAggregateOperationSelectionDropdownContent.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartAggregateOperationSelectionDropdownContent.tsx index 9f2cbd96b4..f40da8d97c 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartAggregateOperationSelectionDropdownContent.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartAggregateOperationSelectionDropdownContent.tsx @@ -42,7 +42,8 @@ export const ChartAggregateOperationSelectionDropdownContent = ({ widgetInEditMode?.configuration?.__typename !== 'BarChartConfiguration' && widgetInEditMode?.configuration?.__typename !== 'LineChartConfiguration' && widgetInEditMode?.configuration?.__typename !== - 'AggregateChartConfiguration' + 'AggregateChartConfiguration' && + widgetInEditMode?.configuration?.__typename !== 'PieChartConfiguration' ) { throw new Error('Invalid configuration type'); } diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartDateGranularitySelectionDropdownContent.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartDateGranularitySelectionDropdownContent.tsx index e3f1150976..66fa05d4bd 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartDateGranularitySelectionDropdownContent.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartDateGranularitySelectionDropdownContent.tsx @@ -24,10 +24,14 @@ const getCurrentDateGranularity = ({ axis, }: { configuration: ChartConfiguration; - axis: 'primary' | 'secondary'; + axis?: 'primary' | 'secondary'; }) => { const defaultGranularity = ObjectRecordGroupByDateGranularity.DAY; + if (configuration?.__typename === 'PieChartConfiguration') { + return configuration.dateGranularity || defaultGranularity; + } + const isBarOrLineChart = configuration?.__typename === 'BarChartConfiguration' || configuration?.__typename === 'LineChartConfiguration'; diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartFieldSelectionForAggregateOperationDropdownContent.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartFieldSelectionForAggregateOperationDropdownContent.tsx index 320cc7230a..b66d613f73 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartFieldSelectionForAggregateOperationDropdownContent.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartFieldSelectionForAggregateOperationDropdownContent.tsx @@ -31,7 +31,8 @@ export const ChartFieldSelectionForAggregateOperationDropdownContent = () => { if ( configuration?.__typename !== 'BarChartConfiguration' && configuration?.__typename !== 'LineChartConfiguration' && - configuration?.__typename !== 'AggregateChartConfiguration' + configuration?.__typename !== 'AggregateChartConfiguration' && + configuration?.__typename !== 'PieChartConfiguration' ) { throw new Error('Invalid configuration type'); } diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartXAxisSortBySelectionDropdownContent.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartSortBySelectionDropdownContent.tsx similarity index 70% rename from packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartXAxisSortBySelectionDropdownContent.tsx rename to packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartSortBySelectionDropdownContent.tsx index 7cf168650e..2e04ff647a 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartXAxisSortBySelectionDropdownContent.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartSortBySelectionDropdownContent.tsx @@ -14,16 +14,21 @@ import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/ho import { type CompositeFieldSubFieldName } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { MenuItemSelect } from 'twenty-ui/navigation'; -import { type GraphOrderBy } from '~/generated/graphql'; +import { + type BarChartConfiguration, + type GraphOrderBy, + type LineChartConfiguration, +} from '~/generated/graphql'; -export const ChartXAxisSortBySelectionDropdownContent = () => { +export const ChartSortBySelectionDropdownContent = () => { const { pageLayoutId } = usePageLayoutIdFromContextStoreTargetedRecord(); const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId); const configuration = widgetInEditMode?.configuration; if ( configuration?.__typename !== 'BarChartConfiguration' && - configuration?.__typename !== 'LineChartConfiguration' + configuration?.__typename !== 'LineChartConfiguration' && + configuration?.__typename !== 'PieChartConfiguration' ) { throw new Error('Invalid configuration type'); } @@ -49,14 +54,22 @@ export const ChartXAxisSortBySelectionDropdownContent = () => { objectMetadataId: widgetInEditMode.objectMetadataId, }); + const isPieChart = configuration.__typename === 'PieChartConfiguration'; + const isLineChart = configuration.__typename === 'LineChartConfiguration'; + const handleSelect = (orderBy: GraphOrderBy) => { - updateCurrentWidgetConfig({ - configToUpdate: { primaryAxisOrderBy: orderBy }, - }); + if (isPieChart) { + updateCurrentWidgetConfig({ + configToUpdate: { orderBy }, + }); + } else { + updateCurrentWidgetConfig({ + configToUpdate: { primaryAxisOrderBy: orderBy }, + }); + } closeDropdown(); }; - const isLineChart = configuration.__typename === 'LineChartConfiguration'; const availableOptions = X_SORT_BY_OPTIONS.filter((option) => { if (isLineChart) { return option.value !== 'VALUE_ASC' && option.value !== 'VALUE_DESC'; @@ -64,6 +77,26 @@ export const ChartXAxisSortBySelectionDropdownContent = () => { return true; }); + let currentOrderBy: GraphOrderBy | undefined; + let groupByFieldMetadataId: string | undefined; + let groupBySubFieldName: string | null | undefined; + + if (configuration.__typename === 'PieChartConfiguration') { + currentOrderBy = configuration.orderBy ?? undefined; + groupByFieldMetadataId = configuration.groupByFieldMetadataId; + groupBySubFieldName = configuration.groupBySubFieldName; + } else { + const barOrLineChartConfiguration = configuration as + | BarChartConfiguration + | LineChartConfiguration; + currentOrderBy = + barOrLineChartConfiguration.primaryAxisOrderBy ?? undefined; + groupByFieldMetadataId = + barOrLineChartConfiguration.primaryAxisGroupByFieldMetadataId; + groupBySubFieldName = + barOrLineChartConfiguration.primaryAxisGroupBySubFieldName; + } + return ( { { diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/LineChartSettings.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/LineChartSettings.ts index e21e9adfeb..e839d40e24 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/LineChartSettings.ts +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/LineChartSettings.ts @@ -9,10 +9,10 @@ import { DATE_GRANULARITY_Y_SETTING } from '@/command-menu/pages/page-layout/con import { FILTER_SETTING } from '@/command-menu/pages/page-layout/constants/settings/FilterSetting'; import { GROUP_BY_SETTING } from '@/command-menu/pages/page-layout/constants/settings/GroupBySetting'; import { OMIT_NULL_VALUES_SETTING } from '@/command-menu/pages/page-layout/constants/settings/OmitNullValuesSetting'; +import { PRIMARY_SORT_BY_SETTING } from '@/command-menu/pages/page-layout/constants/settings/PrimarySortBySetting'; import { RANGE_MAX_SETTING } from '@/command-menu/pages/page-layout/constants/settings/RangeMaxSetting'; import { RANGE_MIN_SETTING } from '@/command-menu/pages/page-layout/constants/settings/RangeMinSetting'; import { SORT_BY_GROUP_BY_FIELD_SETTING } from '@/command-menu/pages/page-layout/constants/settings/SortByGroupByFieldSetting'; -import { SORT_BY_X_SETTING } from '@/command-menu/pages/page-layout/constants/settings/SortByXSetting'; import { STACKED_LINES_SETTING } from '@/command-menu/pages/page-layout/constants/settings/StackedLineSettings'; import { type ChartSettingsGroup } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup'; @@ -26,7 +26,7 @@ export const LINE_CHART_SETTINGS: ChartSettingsGroup[] = [ items: [ DATA_DISPLAY_X_SETTING, DATE_GRANULARITY_X_SETTING, - SORT_BY_X_SETTING, + PRIMARY_SORT_BY_SETTING, OMIT_NULL_VALUES_SETTING, ], }, diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/PieChartSettings.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/PieChartSettings.ts index 7b3c4409aa..a5c20db25f 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/PieChartSettings.ts +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/PieChartSettings.ts @@ -5,7 +5,7 @@ import { DATA_LABELS_SETTING } from '@/command-menu/pages/page-layout/constants/ import { DATE_GRANULARITY_SETTING } from '@/command-menu/pages/page-layout/constants/settings/DateGranularitySetting'; import { EACH_SLICE_REPRESENTS_SETTING } from '@/command-menu/pages/page-layout/constants/settings/EachSliceRepresentsSetting'; import { FILTER_SETTING } from '@/command-menu/pages/page-layout/constants/settings/FilterSetting'; -import { SORT_BY_X_SETTING } from '@/command-menu/pages/page-layout/constants/settings/SortByXSetting'; +import { PRIMARY_SORT_BY_SETTING } from '@/command-menu/pages/page-layout/constants/settings/PrimarySortBySetting'; import { type ChartSettingsGroup } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup'; export const PIE_CHART_SETTINGS: ChartSettingsGroup[] = [ @@ -17,7 +17,7 @@ export const PIE_CHART_SETTINGS: ChartSettingsGroup[] = [ DATA_DISPLAY_PIE_CHART_SETTING, DATE_GRANULARITY_SETTING, EACH_SLICE_REPRESENTS_SETTING, - SORT_BY_X_SETTING, + PRIMARY_SORT_BY_SETTING, ], }, { diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels.ts index 6aded911b5..4fcff748c5 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels.ts +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels.ts @@ -6,7 +6,7 @@ export const CHART_CONFIGURATION_SETTING_LABELS = { COLORS: msg`Colors`, FILTER: msg`Filter`, GROUP_BY: msg`Group by`, - SORT_BY_X: msg`Sort by`, + PRIMARY_SORT_BY: msg`Sort by`, SORT_BY_GROUP_BY_FIELD: msg`Sort by`, DATA_ON_DISPLAY_X: msg`Data on display`, DATA_ON_DISPLAY_Y: msg`Data on display`, diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/DataDisplayPieChartSetting.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/DataDisplayPieChartSetting.ts index 695033a90e..07218b893d 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/DataDisplayPieChartSetting.ts +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/DataDisplayPieChartSetting.ts @@ -1,3 +1,4 @@ +import { ChartPieFieldSelectionDropdownContent } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartPieFieldSelectionDropdownContent'; import { CHART_CONFIGURATION_SETTING_LABELS } from '@/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels'; import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds'; import { type ChartSettingsItem } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup'; @@ -9,4 +10,5 @@ export const DATA_DISPLAY_PIE_CHART_SETTING: ChartSettingsItem = { label: CHART_CONFIGURATION_SETTING_LABELS.DATA_ON_DISPLAY_PIE_CHART, id: CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_PIE_CHART, dependsOn: [CHART_CONFIGURATION_SETTING_IDS.SOURCE], + DropdownContent: ChartPieFieldSelectionDropdownContent, }; diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/EachSliceRepresentsSetting.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/EachSliceRepresentsSetting.ts index a98ef99d9a..a8064f9dc3 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/EachSliceRepresentsSetting.ts +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/EachSliceRepresentsSetting.ts @@ -1,4 +1,4 @@ -import { ChartPieFieldSelectionDropdownContent } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartPieFieldSelectionDropdownContent'; +import { ChartFieldSelectionForAggregateOperationDropdownContent } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartFieldSelectionForAggregateOperationDropdownContent'; import { CHART_CONFIGURATION_SETTING_LABELS } from '@/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels'; import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds'; import { type ChartSettingsItem } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup'; @@ -9,5 +9,5 @@ export const EACH_SLICE_REPRESENTS_SETTING: ChartSettingsItem = { Icon: IconChartPie, label: CHART_CONFIGURATION_SETTING_LABELS.EACH_SLICE_REPRESENTS, id: CHART_CONFIGURATION_SETTING_IDS.EACH_SLICE_REPRESENTS, - DropdownContent: ChartPieFieldSelectionDropdownContent, + DropdownContent: ChartFieldSelectionForAggregateOperationDropdownContent, }; diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/SortByXSetting.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/PrimarySortBySetting.ts similarity index 54% rename from packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/SortByXSetting.ts rename to packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/PrimarySortBySetting.ts index 13dd5fd283..89c05c36b7 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/SortByXSetting.ts +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/constants/settings/PrimarySortBySetting.ts @@ -1,13 +1,13 @@ -import { ChartXAxisSortBySelectionDropdownContent } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartXAxisSortBySelectionDropdownContent'; +import { ChartSortBySelectionDropdownContent } from '@/command-menu/pages/page-layout/components/dropdown-content/ChartSortBySelectionDropdownContent'; import { CHART_CONFIGURATION_SETTING_LABELS } from '@/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels'; import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds'; import { type ChartSettingsItem } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup'; import { IconArrowsSort } from 'twenty-ui/display'; -export const SORT_BY_X_SETTING: ChartSettingsItem = { +export const PRIMARY_SORT_BY_SETTING: ChartSettingsItem = { isBoolean: false, Icon: IconArrowsSort, - label: CHART_CONFIGURATION_SETTING_LABELS.SORT_BY_X, - id: CHART_CONFIGURATION_SETTING_IDS.SORT_BY_X, - DropdownContent: ChartXAxisSortBySelectionDropdownContent, + label: CHART_CONFIGURATION_SETTING_LABELS.PRIMARY_SORT_BY, + id: CHART_CONFIGURATION_SETTING_IDS.PRIMARY_SORT_BY, + DropdownContent: ChartSortBySelectionDropdownContent, }; diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/__tests__/useChartSettingsValues.test.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/__tests__/useChartSettingsValues.test.tsx index 107eb2f125..c12b0bf644 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/__tests__/useChartSettingsValues.test.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/__tests__/useChartSettingsValues.test.tsx @@ -141,7 +141,7 @@ describe('useChartSettingsValues', () => { const { result } = renderUseChartSettingsValues(verticalBarConfig); const value = result.current.getChartSettingsValues( - CHART_CONFIGURATION_SETTING_IDS.SORT_BY_X, + CHART_CONFIGURATION_SETTING_IDS.PRIMARY_SORT_BY, ); expect(value).toBeDefined(); @@ -203,7 +203,7 @@ describe('useChartSettingsValues', () => { const { result } = renderUseChartSettingsValues(horizontalBarConfig); const value = result.current.getChartSettingsValues( - CHART_CONFIGURATION_SETTING_IDS.SORT_BY_X, + CHART_CONFIGURATION_SETTING_IDS.PRIMARY_SORT_BY, ); expect(value).toBeDefined(); @@ -451,7 +451,7 @@ describe('useChartSettingsValues', () => { const { result } = renderUseChartSettingsValues(config); const value = result.current.getChartSettingsValues( - CHART_CONFIGURATION_SETTING_IDS.SORT_BY_X, + CHART_CONFIGURATION_SETTING_IDS.PRIMARY_SORT_BY, ); expect(value).toBeUndefined(); @@ -573,7 +573,7 @@ describe('useChartSettingsValues', () => { CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_X, CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_Y, CHART_CONFIGURATION_SETTING_IDS.GROUP_BY, - CHART_CONFIGURATION_SETTING_IDS.SORT_BY_X, + CHART_CONFIGURATION_SETTING_IDS.PRIMARY_SORT_BY, CHART_CONFIGURATION_SETTING_IDS.SORT_BY_GROUP_BY_FIELD, CHART_CONFIGURATION_SETTING_IDS.AXIS_NAME, CHART_CONFIGURATION_SETTING_IDS.DATA_LABELS, diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts index 0b02ce9462..5c641b2304 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts @@ -102,12 +102,28 @@ export const useChartSettingsValues = ({ }) : undefined; + let pieChartSortByLabel: string | undefined; + if (configuration.__typename === 'PieChartConfiguration') { groupByOrderBy = configuration.orderBy; groupByFieldYId = configuration.groupByFieldMetadataId; groupBySubFieldNameY = configuration.groupBySubFieldName as | CompositeFieldSubFieldName | undefined; + + pieChartSortByLabel = + isDefined(configuration.orderBy) && + isDefined(configuration.groupByFieldMetadataId) + ? getXSortOptionLabel({ + graphOrderBy: configuration.orderBy, + groupByFieldMetadataIdX: configuration.groupByFieldMetadataId, + groupBySubFieldNameX: configuration.groupBySubFieldName as + | CompositeFieldSubFieldName + | undefined, + aggregateFieldMetadataId: configuration.aggregateFieldMetadataId, + aggregateOperation: configuration.aggregateOperation ?? undefined, + }) + : undefined; } const finalGroupByFieldYId = groupByFieldYId; @@ -135,7 +151,8 @@ export const useChartSettingsValues = ({ ? capitalize(configuration.color) : undefined; case CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_Y: - case CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_AGGREGATE: { + case CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_AGGREGATE: + case CHART_CONFIGURATION_SETTING_IDS.EACH_SLICE_REPRESENTS: { const hasAggregateLabel = isDefined(aggregateField?.label); const hasAggregateOperation = isDefined(aggregateOperation); @@ -145,6 +162,24 @@ export const useChartSettingsValues = ({ : '' }`; } + case CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_PIE_CHART: { + const pieChartGroupByField = isDefined(finalGroupByFieldYId) + ? objectMetadataItem?.fields.find( + (field) => field.id === finalGroupByFieldYId, + ) + : undefined; + const pieChartGroupBySubFieldNameLabel = + isDefined(finalGroupBySubFieldNameY) && + isDefined(pieChartGroupByField) + ? getFieldLabelWithSubField({ + field: pieChartGroupByField, + subFieldName: finalGroupBySubFieldNameY, + }) + : undefined; + + return pieChartGroupBySubFieldNameLabel ?? pieChartGroupByField?.label; + } + case CHART_CONFIGURATION_SETTING_IDS.GROUP_BY: return groupByFieldY?.label; case CHART_CONFIGURATION_SETTING_IDS.AXIS_NAME: @@ -152,8 +187,8 @@ export const useChartSettingsValues = ({ isDefined(configuration.axisNameDisplay) ? getChartAxisNameDisplayOptions(configuration.axisNameDisplay) : undefined; - case CHART_CONFIGURATION_SETTING_IDS.SORT_BY_X: - return xAxisOrderByLabel; + case CHART_CONFIGURATION_SETTING_IDS.PRIMARY_SORT_BY: + return pieChartSortByLabel ?? xAxisOrderByLabel; case CHART_CONFIGURATION_SETTING_IDS.SORT_BY_GROUP_BY_FIELD: return groupByOrderByLabel; case CHART_CONFIGURATION_SETTING_IDS.DATA_LABELS: diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useUpdateGraphTypeConfig.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useUpdateGraphTypeConfig.ts index 35f751ec5f..ae9e0399ee 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useUpdateGraphTypeConfig.ts +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/hooks/useUpdateGraphTypeConfig.ts @@ -1,25 +1,26 @@ import { GRAPH_TYPE_TO_CONFIG_TYPENAME } from '@/command-menu/pages/page-layout/constants/GraphTypeToConfigTypename'; import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/ChartConfiguration'; +import { convertAggregateOperationForDateField } from '@/command-menu/pages/page-layout/utils/convertAggregateOperationForDateField'; +import { convertBarOrLineChartConfigToPieChart } from '@/command-menu/pages/page-layout/utils/convertBarOrLineChartConfigToPieChart'; +import { convertPieChartConfigToBarOrLineChart } from '@/command-menu/pages/page-layout/utils/convertPieChartConfigToBarOrLineChart'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; -import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations'; import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState'; +import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState'; import { getTabListInstanceIdFromPageLayoutId } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutId'; import { updateWidgetMinimumSizeForGraphType } from '@/page-layout/utils/updateWidgetMinimumSizeForGraphType'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState'; import { useRecoilCallback } from 'recoil'; -import { isDefined, isFieldMetadataDateKind } from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; import { GraphType, type PageLayoutWidget } from '~/generated/graphql'; -export const useUpdateGraphTypeConfig = ({ +export const useGetConfigToUpdateAfterGraphTypeChange = ({ pageLayoutId, widget, - configuration, }: { pageLayoutId: string; widget: PageLayoutWidget; - configuration: ChartConfiguration; }) => { const { objectMetadataItems } = useObjectMetadataItems(); @@ -40,10 +41,41 @@ export const useUpdateGraphTypeConfig = ({ pageLayoutId, ); - const updateGraphTypeConfig = useRecoilCallback( + const pageLayoutDraftState = useRecoilComponentCallbackState( + pageLayoutDraftComponentState, + pageLayoutId, + ); + + const getConfigToUpdateAfterGraphTypeChange = useRecoilCallback( ({ set, snapshot }) => (graphType: GraphType) => { - const configToUpdate: Record = { + const currentlyEditingWidgetId = snapshot + .getLoadable(currentlyEditingWidgetIdState) + .getValue(); + + if (!isDefined(currentlyEditingWidgetId)) { + throw new Error('No widget is currently being edited'); + } + + const draftPageLayout = snapshot + .getLoadable(pageLayoutDraftState) + .getValue(); + + const widgetInDraft = draftPageLayout.tabs + .flatMap((tab) => tab.widgets) + .find((w) => w.id === currentlyEditingWidgetId); + + if ( + !isDefined(widgetInDraft) || + !isDefined(widgetInDraft.configuration) + ) { + throw new Error('Widget configuration not found in draft state'); + } + + const currentConfiguration = + widgetInDraft.configuration as ChartConfiguration; + + let configToUpdate: Record = { __typename: GRAPH_TYPE_TO_CONFIG_TYPENAME[graphType], graphType, }; @@ -52,33 +84,50 @@ export const useUpdateGraphTypeConfig = ({ graphType !== GraphType.AGGREGATE && graphType !== GraphType.GAUGE ) { - const currentAggregateFieldMetadataId = - configuration.aggregateFieldMetadataId; - const objectMetadataItem = objectMetadataItems.find( (item) => item.id === widget.objectMetadataId, ); - if (isDefined(objectMetadataItem)) { - const aggregateField = objectMetadataItem.fields.find( - (field) => field.id === currentAggregateFieldMetadataId, + const convertedAggregateOperation = + convertAggregateOperationForDateField( + currentConfiguration, + objectMetadataItem, ); - if ( - isDefined(aggregateField) && - isFieldMetadataDateKind(aggregateField.type) && - (configuration.aggregateOperation === AggregateOperations.MIN || - configuration.aggregateOperation === AggregateOperations.MAX) - ) { - configToUpdate.aggregateOperation = AggregateOperations.COUNT; - } + if (isDefined(convertedAggregateOperation)) { + configToUpdate = { + ...configToUpdate, + aggregateOperation: convertedAggregateOperation, + }; } } + const isPieChart = graphType === GraphType.PIE; + const isBarOrLineChart = + graphType === GraphType.VERTICAL_BAR || + graphType === GraphType.HORIZONTAL_BAR || + graphType === GraphType.LINE; + const wasBarOrLineChart = + currentConfiguration.__typename === 'BarChartConfiguration' || + currentConfiguration.__typename === 'LineChartConfiguration'; + const wasPieChart = + currentConfiguration.__typename === 'PieChartConfiguration'; + + if (isPieChart && wasBarOrLineChart) { + configToUpdate = { + ...configToUpdate, + ...convertBarOrLineChartConfigToPieChart(currentConfiguration), + }; + } + + if (isBarOrLineChart && wasPieChart) { + configToUpdate = { + ...configToUpdate, + ...convertPieChartConfigToBarOrLineChart(currentConfiguration), + }; + } + const activeTabId = snapshot.getLoadable(activeTabIdState).getValue(); - const currentlyEditingWidgetId = snapshot - .getLoadable(currentlyEditingWidgetIdState) - .getValue(); if (isDefined(activeTabId) && isDefined(currentlyEditingWidgetId)) { const currentLayouts = snapshot @@ -99,13 +148,13 @@ export const useUpdateGraphTypeConfig = ({ }, [ activeTabIdState, - configuration, currentlyEditingWidgetIdState, objectMetadataItems, pageLayoutCurrentLayoutsState, + pageLayoutDraftState, widget.objectMetadataId, ], ); - return { updateGraphTypeConfig }; + return { getConfigToUpdateAfterGraphTypeChange }; }; diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/ChartConfigurationSettingIds.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/ChartConfigurationSettingIds.ts index 8d07d2f0c1..152fb0f071 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/ChartConfigurationSettingIds.ts +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/ChartConfigurationSettingIds.ts @@ -4,7 +4,7 @@ export enum CHART_CONFIGURATION_SETTING_IDS { COLORS = 'COLORS', FILTER = 'FILTER', GROUP_BY = 'GROUP_BY', - SORT_BY_X = 'SORT_BY_X', + PRIMARY_SORT_BY = 'PRIMARY_SORT_BY', SORT_BY_GROUP_BY_FIELD = 'SORT_BY_GROUP_BY_FIELD', SORT_BY = 'SORT_BY', DATA_ON_DISPLAY_X = 'DATA_ON_DISPLAY_X', diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/__tests__/getBarChartSettings.test.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/__tests__/getBarChartSettings.test.ts index f31d74d806..2b18de0437 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/__tests__/getBarChartSettings.test.ts +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/__tests__/getBarChartSettings.test.ts @@ -9,10 +9,10 @@ import { DATE_GRANULARITY_Y_SETTING } from '@/command-menu/pages/page-layout/con import { FILTER_SETTING } from '@/command-menu/pages/page-layout/constants/settings/FilterSetting'; import { GROUP_BY_SETTING } from '@/command-menu/pages/page-layout/constants/settings/GroupBySetting'; import { OMIT_NULL_VALUES_SETTING } from '@/command-menu/pages/page-layout/constants/settings/OmitNullValuesSetting'; +import { PRIMARY_SORT_BY_SETTING } from '@/command-menu/pages/page-layout/constants/settings/PrimarySortBySetting'; import { RANGE_MAX_SETTING } from '@/command-menu/pages/page-layout/constants/settings/RangeMaxSetting'; import { RANGE_MIN_SETTING } from '@/command-menu/pages/page-layout/constants/settings/RangeMinSetting'; import { SORT_BY_GROUP_BY_FIELD_SETTING } from '@/command-menu/pages/page-layout/constants/settings/SortByGroupByFieldSetting'; -import { SORT_BY_X_SETTING } from '@/command-menu/pages/page-layout/constants/settings/SortByXSetting'; import { STACKED_BARS_SETTING } from '@/command-menu/pages/page-layout/constants/settings/StackedBarsSetting'; import { IconAxisX, IconAxisY } from 'twenty-ui/display'; import { GraphType } from '~/generated-metadata/graphql'; @@ -31,7 +31,7 @@ describe('getBarChartSettings', () => { expect(xAxisGroup?.items[0].label).toBe(DATA_DISPLAY_X_SETTING.label); expect(xAxisGroup?.items[0].Icon).toBe(IconAxisX); expect(xAxisGroup?.items[1]).toEqual(DATE_GRANULARITY_X_SETTING); - expect(xAxisGroup?.items[2]).toEqual(SORT_BY_X_SETTING); + expect(xAxisGroup?.items[2]).toEqual(PRIMARY_SORT_BY_SETTING); expect(xAxisGroup?.items[3]).toEqual(OMIT_NULL_VALUES_SETTING); }); @@ -92,7 +92,7 @@ describe('getBarChartSettings', () => { expect(yAxisGroup?.items[0].label).toBe(DATA_DISPLAY_X_SETTING.label); expect(yAxisGroup?.items[0].Icon).toBe(IconAxisY); expect(yAxisGroup?.items[1]).toEqual(DATE_GRANULARITY_X_SETTING); - expect(yAxisGroup?.items[2]).toEqual(SORT_BY_X_SETTING); + expect(yAxisGroup?.items[2]).toEqual(PRIMARY_SORT_BY_SETTING); expect(yAxisGroup?.items[3]).toEqual(OMIT_NULL_VALUES_SETTING); }); diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/convertAggregateOperationForDateField.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/convertAggregateOperationForDateField.ts new file mode 100644 index 0000000000..2b98c7a02e --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/convertAggregateOperationForDateField.ts @@ -0,0 +1,31 @@ +import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/ChartConfiguration'; +import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations'; +import { isDefined, isFieldMetadataDateKind } from 'twenty-shared/utils'; + +export const convertAggregateOperationForDateField = ( + configuration: ChartConfiguration, + objectMetadataItem: ObjectMetadataItem | undefined, +): AggregateOperations | undefined => { + if (!isDefined(objectMetadataItem)) { + return undefined; + } + + const currentAggregateFieldMetadataId = + configuration.aggregateFieldMetadataId; + + const aggregateField = objectMetadataItem.fields.find( + (field) => field.id === currentAggregateFieldMetadataId, + ); + + if ( + isDefined(aggregateField) && + isFieldMetadataDateKind(aggregateField.type) && + (configuration.aggregateOperation === AggregateOperations.MIN || + configuration.aggregateOperation === AggregateOperations.MAX) + ) { + return AggregateOperations.COUNT; + } + + return undefined; +}; diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/convertBarOrLineChartConfigToPieChart.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/convertBarOrLineChartConfigToPieChart.ts new file mode 100644 index 0000000000..5a7bcf2b51 --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/convertBarOrLineChartConfigToPieChart.ts @@ -0,0 +1,30 @@ +import { + type BarChartConfiguration, + type LineChartConfiguration, +} from '~/generated/graphql'; + +export const convertBarOrLineChartConfigToPieChart = ( + configuration: BarChartConfiguration | LineChartConfiguration, +): Record => { + const configToUpdate: Record = {}; + + if ('primaryAxisGroupByFieldMetadataId' in configuration) { + configToUpdate.groupByFieldMetadataId = + configuration.primaryAxisGroupByFieldMetadataId; + } + + if ('primaryAxisGroupBySubFieldName' in configuration) { + configToUpdate.groupBySubFieldName = + configuration.primaryAxisGroupBySubFieldName; + } + + if ('primaryAxisDateGranularity' in configuration) { + configToUpdate.dateGranularity = configuration.primaryAxisDateGranularity; + } + + if ('primaryAxisOrderBy' in configuration) { + configToUpdate.orderBy = configuration.primaryAxisOrderBy; + } + + return configToUpdate; +}; diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/convertPieChartConfigToBarOrLineChart.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/convertPieChartConfigToBarOrLineChart.ts new file mode 100644 index 0000000000..afe858435d --- /dev/null +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/convertPieChartConfigToBarOrLineChart.ts @@ -0,0 +1,27 @@ +import { type PieChartConfiguration } from '~/generated/graphql'; + +export const convertPieChartConfigToBarOrLineChart = ( + configuration: PieChartConfiguration, +): Record => { + const configToUpdate: Record = {}; + + if ('groupByFieldMetadataId' in configuration) { + configToUpdate.primaryAxisGroupByFieldMetadataId = + configuration.groupByFieldMetadataId; + } + + if ('groupBySubFieldName' in configuration) { + configToUpdate.primaryAxisGroupBySubFieldName = + configuration.groupBySubFieldName; + } + + if ('dateGranularity' in configuration) { + configToUpdate.primaryAxisDateGranularity = configuration.dateGranularity; + } + + if ('orderBy' in configuration) { + configToUpdate.primaryAxisOrderBy = configuration.orderBy; + } + + return configToUpdate; +}; diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/getBarChartSettings.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/getBarChartSettings.ts index 2ff87c3f69..1dd182e20c 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/getBarChartSettings.ts +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/getBarChartSettings.ts @@ -9,10 +9,10 @@ import { DATE_GRANULARITY_Y_SETTING } from '@/command-menu/pages/page-layout/con import { FILTER_SETTING } from '@/command-menu/pages/page-layout/constants/settings/FilterSetting'; import { GROUP_BY_SETTING } from '@/command-menu/pages/page-layout/constants/settings/GroupBySetting'; import { OMIT_NULL_VALUES_SETTING } from '@/command-menu/pages/page-layout/constants/settings/OmitNullValuesSetting'; +import { PRIMARY_SORT_BY_SETTING } from '@/command-menu/pages/page-layout/constants/settings/PrimarySortBySetting'; import { RANGE_MAX_SETTING } from '@/command-menu/pages/page-layout/constants/settings/RangeMaxSetting'; import { RANGE_MIN_SETTING } from '@/command-menu/pages/page-layout/constants/settings/RangeMinSetting'; import { SORT_BY_GROUP_BY_FIELD_SETTING } from '@/command-menu/pages/page-layout/constants/settings/SortByGroupByFieldSetting'; -import { SORT_BY_X_SETTING } from '@/command-menu/pages/page-layout/constants/settings/SortByXSetting'; import { STACKED_BARS_SETTING } from '@/command-menu/pages/page-layout/constants/settings/StackedBarsSetting'; import { type ChartSettingsGroup } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup'; import { IconAxisX, IconAxisY } from 'twenty-ui/display'; @@ -29,7 +29,7 @@ export const getBarChartSettings = ( const primaryAxisItems = [ { ...DATA_DISPLAY_X_SETTING, Icon: dataDisplayXIcon }, DATE_GRANULARITY_X_SETTING, - SORT_BY_X_SETTING, + PRIMARY_SORT_BY_SETTING, OMIT_NULL_VALUES_SETTING, ]; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidget.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidget.tsx index 5bb7a92b9c..cd87c59112 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidget.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidget.tsx @@ -5,18 +5,11 @@ import { ChartSkeletonLoader } from '@/page-layout/widgets/graph/components/Char import { GraphWidgetAggregateChartRenderer } from '@/page-layout/widgets/graph/graphWidgetAggregateChart/components/GraphWidgetAggregateChartRenderer'; import { GraphWidgetBarChartRenderer } from '@/page-layout/widgets/graph/graphWidgetBarChart/components/GraphWidgetBarChartRenderer'; import { GraphWidgetLineChartRenderer } from '@/page-layout/widgets/graph/graphWidgetLineChart/components/GraphWidgetLineChartRenderer'; +import { GraphWidgetPieChartRenderer } from '@/page-layout/widgets/graph/graphWidgetPieChart/components/GraphWidgetPieChartRenderer'; import { areChartConfigurationFieldsValidForQuery } from '@/page-layout/widgets/graph/utils/areChartConfigurationFieldsValidForQuery'; import { lazy, Suspense } from 'react'; import { GraphType, type PageLayoutWidget } from '~/generated/graphql'; -const GraphWidgetPieChart = lazy(() => - import( - '@/page-layout/widgets/graph/graphWidgetPieChart/components/GraphWidgetPieChart' - ).then((module) => ({ - default: module.GraphWidgetPieChart, - })), -); - const GraphWidgetGaugeChart = lazy(() => import( '@/page-layout/widgets/graph/graphWidgetGaugeChart/components/GraphWidgetGaugeChart' @@ -49,25 +42,23 @@ export const GraphWidget = ({ return ; } - const data: any = getDefaultWidgetData(graphType); - - if (!data) { - return null; - } - switch (graphType) { case GraphType.AGGREGATE: return ; - case GraphType.GAUGE: + case GraphType.GAUGE: { + const gaugeData: any = getDefaultWidgetData(graphType); + if (!gaugeData) { + return null; + } return ( }> ); + } case GraphType.PIE: - return ( - }> - - - ); + return ; case GraphType.VERTICAL_BAR: case GraphType.HORIZONTAL_BAR: diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetLegend.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetLegend.tsx index 6181455a27..daab99eabf 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetLegend.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetLegend.tsx @@ -17,6 +17,7 @@ const StyledLegendContainer = styled.div` flex-wrap: wrap; gap: ${({ theme }) => theme.spacing(3)}; justify-content: center; + padding-top: ${({ theme }) => theme.spacing(3)}; `; const StyledLegendItem = styled.div` diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/GraphWidgetBarChart.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/GraphWidgetBarChart.tsx index f99f0763aa..e19c2c1206 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/GraphWidgetBarChart.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetBarChart/components/GraphWidgetBarChart.tsx @@ -92,7 +92,6 @@ export const GraphWidgetBarChart = ({ const theme = useTheme(); const colorRegistry = createGraphColorRegistry(theme); - // Chart dimensions const [chartWidth, setChartWidth] = useState(0); const [chartHeight, setChartHeight] = useState(0); const containerRef = useRef(null); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/GraphWidgetPieChart.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/GraphWidgetPieChart.tsx index 889fed2e7e..80f5cfb750 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/GraphWidgetPieChart.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/GraphWidgetPieChart.tsx @@ -1,7 +1,7 @@ import { GraphWidgetChartContainer } from '@/page-layout/widgets/graph/components/GraphWidgetChartContainer'; import { GraphWidgetLegend } from '@/page-layout/widgets/graph/components/GraphWidgetLegend'; import { GraphWidgetTooltip } from '@/page-layout/widgets/graph/components/GraphWidgetTooltip'; -import { PieChartEndLines } from '@/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartEndLines'; +import { PIE_CHART_HOVER_BRIGHTNESS } from '@/page-layout/widgets/graph/graphWidgetPieChart/constants/PieChartHoverBrightness'; import { usePieChartData } from '@/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartData'; import { usePieChartHandlers } from '@/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartHandlers'; import { usePieChartTooltip } from '@/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartTooltip'; @@ -10,17 +10,14 @@ import { createGraphColorRegistry } from '@/page-layout/widgets/graph/utils/crea import { type GraphValueFormatOptions } from '@/page-layout/widgets/graph/utils/graphFormatters'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; -import { - ResponsivePie, - type PieCustomLayerProps, - type PieTooltipProps, -} from '@nivo/pie'; +import { ResponsivePie, type PieTooltipProps } from '@nivo/pie'; import { isDefined } from 'twenty-shared/utils'; type GraphWidgetPieChartProps = { data: PieChartDataItem[]; showLegend?: boolean; id: string; + onSliceClick?: (datum: PieChartDataItem) => void; } & GraphValueFormatOptions; const StyledContainer = styled.div` @@ -32,6 +29,19 @@ const StyledContainer = styled.div` width: 100%; `; +const StyledPieChartWrapper = styled.div` + width: 100%; + height: 100%; + + svg g path { + transition: filter 0.15s ease-in-out; + + &:hover { + filter: brightness(${PIE_CHART_HOVER_BRIGHTNESS}); + } + } +`; + export const GraphWidgetPieChart = ({ data, showLegend = true, @@ -41,6 +51,7 @@ export const GraphWidgetPieChart = ({ prefix, suffix, customFormatter, + onSliceClick, }: GraphWidgetPieChartProps) => { const theme = useTheme(); const colorRegistry = createGraphColorRegistry(theme); @@ -58,12 +69,11 @@ export const GraphWidgetPieChart = ({ setHoveredSliceId, handleSliceClick, hasClickableItems, - } = usePieChartHandlers({ data }); + } = usePieChartHandlers({ data, onSliceClick }); - const { enrichedData, enrichedDataMap, defs, fill } = usePieChartData({ + const { enrichedData } = usePieChartData({ data, colorRegistry, - id, hoveredSliceId, }); @@ -73,19 +83,6 @@ export const GraphWidgetPieChart = ({ displayType, }); - const renderSliceEndLines = ( - layerProps: PieCustomLayerProps, - ) => ( - - ); - const renderTooltip = ({ datum }: PieTooltipProps) => { const tooltipData = createTooltipData(datum); if (!isDefined(tooltipData)) return null; @@ -97,23 +94,23 @@ export const GraphWidgetPieChart = ({ - `url(#${item.gradientId})`)} - borderWidth={0} - enableArcLinkLabels={false} - enableArcLabels={false} - tooltip={renderTooltip} - onClick={handleSliceClick} - onMouseEnter={(datum) => setHoveredSliceId(datum.id)} - onMouseLeave={() => setHoveredSliceId(null)} - defs={defs} - fill={fill} - layers={['arcs', renderSliceEndLines]} - /> + + item.colorScheme.solid)} + borderWidth={0} + enableArcLinkLabels={false} + enableArcLabels={false} + tooltip={renderTooltip} + onClick={handleSliceClick} + onMouseEnter={(datum) => setHoveredSliceId(datum.id)} + onMouseLeave={() => setHoveredSliceId(null)} + layers={['arcs']} + /> + + import( + '@/page-layout/widgets/graph/graphWidgetPieChart/components/GraphWidgetPieChart' + ).then((module) => ({ + default: module.GraphWidgetPieChart, + })), +); + +export const GraphWidgetPieChartRenderer = ({ + widget, +}: { + widget: PageLayoutWidget; +}) => { + const { data, loading, hasTooManyGroups, objectMetadataItem } = + useGraphPieChartWidgetData({ + objectMetadataItemId: widget.objectMetadataId, + configuration: widget.configuration as PieChartConfiguration, + }); + + const navigate = useNavigate(); + + const indexViewId = useRecoilValue( + coreIndexViewIdFromObjectMetadataItemFamilySelector({ + objectMetadataItemId: objectMetadataItem.id, + }), + ); + + const handleSliceClick = (_datum: PieChartDataItem) => { + return navigate( + getAppPath( + AppPath.RecordIndexPage, + { + objectNamePlural: objectMetadataItem.namePlural, + }, + isDefined(indexViewId) ? { viewId: indexViewId } : undefined, + ), + ); + }; + + if (loading) { + return ; + } + + return ( + }> + + + + ); +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/constants/PieChartHoverBrightness.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/constants/PieChartHoverBrightness.ts new file mode 100644 index 0000000000..d0e921ede4 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/constants/PieChartHoverBrightness.ts @@ -0,0 +1 @@ +export const PIE_CHART_HOVER_BRIGHTNESS = 0.85; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/constants/PieChartMaximumNumberOfSlices.constant.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/constants/PieChartMaximumNumberOfSlices.constant.ts new file mode 100644 index 0000000000..b2fbd1c9b2 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/constants/PieChartMaximumNumberOfSlices.constant.ts @@ -0,0 +1 @@ +export const PIE_CHART_MAXIMUM_NUMBER_OF_SLICES = 50; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/__tests__/usePieChartData.test.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/__tests__/usePieChartData.test.ts index 7a476d37a2..533d940c27 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/__tests__/usePieChartData.test.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/__tests__/usePieChartData.test.ts @@ -67,7 +67,6 @@ describe('usePieChartData', () => { usePieChartData({ data: mockData, colorRegistry: mockColorRegistry, - id: 'test-chart', hoveredSliceId: null, }), ); @@ -80,7 +79,6 @@ describe('usePieChartData', () => { percentage: 30, colorScheme: mockColorRegistry.red, isHovered: false, - gradientId: 'redGradient-test-chart-0', }); expect(result.current.enrichedData[1].percentage).toBe(50); expect(result.current.enrichedData[2].percentage).toBe(20); @@ -91,7 +89,6 @@ describe('usePieChartData', () => { usePieChartData({ data: mockData, colorRegistry: mockColorRegistry, - id: 'test-chart', hoveredSliceId: null, }), ); @@ -107,7 +104,6 @@ describe('usePieChartData', () => { usePieChartData({ data: mockData, colorRegistry: mockColorRegistry, - id: 'test-chart', hoveredSliceId, }), { initialProps: { hoveredSliceId: null as DatumId | null } }, @@ -121,57 +117,16 @@ describe('usePieChartData', () => { expect(result.current.enrichedData[2].isHovered).toBe(false); }); - it('should generate gradient definitions', () => { - const { result } = renderHook(() => - usePieChartData({ - data: mockData, - colorRegistry: mockColorRegistry, - id: 'test-chart', - hoveredSliceId: 'item1', - }), - ); - - expect(result.current.defs).toHaveLength(3); - expect(result.current.defs[0]).toMatchObject({ - id: 'redGradient-test-chart-0', - type: 'linearGradient', - colors: [ - { offset: 0, color: 'red3' }, - { offset: 100, color: 'red4' }, - ], - }); - }); - - it('should generate fill configuration', () => { - const { result } = renderHook(() => - usePieChartData({ - data: mockData, - colorRegistry: mockColorRegistry, - id: 'test-chart', - hoveredSliceId: null, - }), - ); - - expect(result.current.fill).toEqual([ - { match: { id: 'item1' }, id: 'redGradient-test-chart-0' }, - { match: { id: 'item2' }, id: 'blueGradient-test-chart-1' }, - { match: { id: 'item3' }, id: 'redGradient-test-chart-2' }, - ]); - }); - it('should handle empty data', () => { const { result } = renderHook(() => usePieChartData({ data: [], colorRegistry: mockColorRegistry, - id: 'test-chart', hoveredSliceId: null, }), ); expect(result.current.enrichedData).toEqual([]); - expect(result.current.defs).toEqual([]); - expect(result.current.fill).toEqual([]); }); it('should handle single data item', () => { @@ -183,7 +138,6 @@ describe('usePieChartData', () => { usePieChartData({ data: singleData, colorRegistry: mockColorRegistry, - id: 'test-chart', hoveredSliceId: null, }), ); @@ -202,7 +156,6 @@ describe('usePieChartData', () => { usePieChartData({ data: dataWithColors, colorRegistry: mockColorRegistry, - id: 'test-chart', hoveredSliceId: null, }), ); @@ -213,24 +166,19 @@ describe('usePieChartData', () => { it('should memoize calculations', () => { const { result, rerender } = renderHook( - ({ id }) => + ({ hoveredSliceId }) => usePieChartData({ data: mockData, colorRegistry: mockColorRegistry, - id, - hoveredSliceId: null, + hoveredSliceId, }), - { initialProps: { id: 'test-chart' } }, + { initialProps: { hoveredSliceId: null as DatumId | null } }, ); const firstEnrichedData = result.current.enrichedData; - const firstDefs = result.current.defs; - const firstFill = result.current.fill; - rerender({ id: 'test-chart' }); + rerender({ hoveredSliceId: null as DatumId | null }); expect(result.current.enrichedData).toBe(firstEnrichedData); - expect(result.current.defs).toBe(firstDefs); - expect(result.current.fill).toBe(firstFill); }); }); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/useGraphPieChartWidgetData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/useGraphPieChartWidgetData.ts new file mode 100644 index 0000000000..45e9337f7f --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/useGraphPieChartWidgetData.ts @@ -0,0 +1,63 @@ +import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById'; +import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { PIE_CHART_MAXIMUM_NUMBER_OF_SLICES } from '@/page-layout/widgets/graph/graphWidgetPieChart/constants/PieChartMaximumNumberOfSlices.constant'; +import { type PieChartDataItem } from '@/page-layout/widgets/graph/graphWidgetPieChart/types/PieChartDataItem'; +import { transformGroupByDataToPieChartData } from '@/page-layout/widgets/graph/graphWidgetPieChart/utils/transformGroupByDataToPieChartData'; +import { useGraphWidgetGroupByQuery } from '@/page-layout/widgets/graph/hooks/useGraphWidgetGroupByQuery'; +import { useMemo } from 'react'; +import { type PieChartConfiguration } from '~/generated/graphql'; + +type UseGraphPieChartWidgetDataProps = { + objectMetadataItemId: string; + configuration: PieChartConfiguration; +}; + +type UseGraphPieChartWidgetDataResult = { + data: PieChartDataItem[]; + loading: boolean; + error?: Error; + hasTooManyGroups: boolean; + objectMetadataItem: ObjectMetadataItem; +}; + +// TODO: Remove this once backend returns total group count +const EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS = 1; + +export const useGraphPieChartWidgetData = ({ + objectMetadataItemId, + configuration, +}: UseGraphPieChartWidgetDataProps): UseGraphPieChartWidgetDataResult => { + const { objectMetadataItem } = useObjectMetadataItemById({ + objectId: objectMetadataItemId, + }); + + const { + data: groupByData, + loading, + error, + aggregateOperation, + } = useGraphWidgetGroupByQuery({ + objectMetadataItemId, + configuration, + limit: + PIE_CHART_MAXIMUM_NUMBER_OF_SLICES + EXTRA_ITEM_TO_DETECT_TOO_MANY_GROUPS, + }); + + const transformedData = useMemo( + () => + transformGroupByDataToPieChartData({ + groupByData, + objectMetadataItem, + configuration, + aggregateOperation, + }), + [groupByData, objectMetadataItem, configuration, aggregateOperation], + ); + + return { + ...transformedData, + objectMetadataItem, + loading, + error, + }; +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartData.ts index d1f1638eb4..18268f4ecd 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartData.ts @@ -3,7 +3,6 @@ import { type PieChartEnrichedData } from '@/page-layout/widgets/graph/graphWidg import { calculatePieChartAngles } from '@/page-layout/widgets/graph/graphWidgetPieChart/utils/calculatePieChartAngles'; import { calculatePieChartPercentage } from '@/page-layout/widgets/graph/graphWidgetPieChart/utils/calculatePieChartPercentage'; import { type GraphColorRegistry } from '@/page-layout/widgets/graph/types/GraphColorRegistry'; -import { createGradientDef } from '@/page-layout/widgets/graph/utils/createGradientDef'; import { getColorScheme } from '@/page-layout/widgets/graph/utils/getColorScheme'; import { type DatumId } from '@nivo/pie'; import { useMemo } from 'react'; @@ -11,14 +10,12 @@ import { useMemo } from 'react'; type UsePieChartDataProps = { data: PieChartDataItem[]; colorRegistry: GraphColorRegistry; - id: string; hoveredSliceId: DatumId | null; }; export const usePieChartData = ({ data, colorRegistry, - id, hoveredSliceId, }: UsePieChartDataProps) => { const enrichedData = useMemo((): PieChartEnrichedData[] => { @@ -34,7 +31,6 @@ export const usePieChartData = ({ }); const isHovered = hoveredSliceId === item.id; - const gradientId = `${colorScheme.name}Gradient-${id}-${index}`; const percentage = calculatePieChartPercentage(item.value, totalValue); const angles = calculatePieChartAngles(percentage, cumulativeAngle); @@ -42,32 +38,13 @@ export const usePieChartData = ({ return { ...item, - gradientId, colorScheme, isHovered, percentage, middleAngle: angles.middleAngle, }; }); - }, [data, colorRegistry, id, hoveredSliceId]); - - const defs = useMemo(() => { - return enrichedData.map((item) => - createGradientDef( - item.colorScheme, - item.gradientId, - item.isHovered, - item.middleAngle, - ), - ); - }, [enrichedData]); - - const fill = useMemo(() => { - return enrichedData.map((item) => ({ - match: { id: item.id }, - id: item.gradientId, - })); - }, [enrichedData]); + }, [data, colorRegistry, hoveredSliceId]); const enrichedDataMap = useMemo( () => new Map(enrichedData.map((item) => [item.id, item])), @@ -77,7 +54,5 @@ export const usePieChartData = ({ return { enrichedData, enrichedDataMap, - defs, - fill, }; }; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartHandlers.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartHandlers.ts index 8cbb062c81..6605add5ec 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartHandlers.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/hooks/usePieChartHandlers.ts @@ -5,21 +5,30 @@ import { isDefined } from 'twenty-shared/utils'; type UsePieChartHandlersProps = { data: PieChartDataItem[]; + onSliceClick?: (datum: PieChartDataItem) => void; }; -export const usePieChartHandlers = ({ data }: UsePieChartHandlersProps) => { +export const usePieChartHandlers = ({ + data, + onSliceClick, +}: UsePieChartHandlersProps) => { const [hoveredSliceId, setHoveredSliceId] = useState(null); const handleSliceClick = ( datum: ComputedDatum<{ id: string; value: number; label?: string }>, ) => { const clickedItem = data.find((d) => d.id === datum.id); - if (isDefined(clickedItem?.to)) { - window.location.href = clickedItem.to; + if (isDefined(clickedItem)) { + if (isDefined(onSliceClick)) { + onSliceClick(clickedItem); + } else if (isDefined(clickedItem.to)) { + window.location.href = clickedItem.to; + } } }; - const hasClickableItems = data.some((item) => isDefined(item.to)); + const hasClickableItems = + isDefined(onSliceClick) || data.some((item) => isDefined(item.to)); return { hoveredSliceId, diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/types/PieChartEnrichedData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/types/PieChartEnrichedData.ts index 89535dc365..f7c6499670 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/types/PieChartEnrichedData.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/types/PieChartEnrichedData.ts @@ -2,7 +2,6 @@ import { type PieChartDataItem } from '@/page-layout/widgets/graph/graphWidgetPi import { type GraphColorScheme } from '@/page-layout/widgets/graph/types/GraphColorScheme'; export type PieChartEnrichedData = PieChartDataItem & { - gradientId: string; colorScheme: GraphColorScheme; isHovered: boolean; percentage: number; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/transformGroupByDataToPieChartData.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/transformGroupByDataToPieChartData.ts new file mode 100644 index 0000000000..328e8b64a4 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/graphWidgetPieChart/utils/transformGroupByDataToPieChartData.ts @@ -0,0 +1,101 @@ +import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; +import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { type ExtendedAggregateOperations } from '@/object-record/record-table/types/ExtendedAggregateOperations'; +import { getGroupByQueryResultGqlFieldName } from '@/page-layout/utils/getGroupByQueryResultGqlFieldName'; +import { PIE_CHART_MAXIMUM_NUMBER_OF_SLICES } from '@/page-layout/widgets/graph/graphWidgetPieChart/constants/PieChartMaximumNumberOfSlices.constant'; +import { type PieChartDataItem } from '@/page-layout/widgets/graph/graphWidgetPieChart/types/PieChartDataItem'; +import { type GraphColor } from '@/page-layout/widgets/graph/types/GraphColor'; +import { type GroupByRawResult } from '@/page-layout/widgets/graph/types/GroupByRawResult'; +import { computeAggregateValueFromGroupByResult } from '@/page-layout/widgets/graph/utils/computeAggregateValueFromGroupByResult'; +import { formatDimensionValue } from '@/page-layout/widgets/graph/utils/formatDimensionValue'; +import { isDefined } from 'twenty-shared/utils'; +import { type PieChartConfiguration } from '~/generated/graphql'; + +type TransformGroupByDataToPieChartDataParams = { + groupByData: Record | null | undefined; + objectMetadataItem: ObjectMetadataItem; + configuration: PieChartConfiguration; + aggregateOperation: string; +}; + +type TransformGroupByDataToPieChartDataResult = { + data: PieChartDataItem[]; + hasTooManyGroups: boolean; +}; + +const EMPTY_PIE_CHART_RESULT: TransformGroupByDataToPieChartDataResult = { + data: [], + hasTooManyGroups: false, +}; + +export const transformGroupByDataToPieChartData = ({ + groupByData, + objectMetadataItem, + configuration, + aggregateOperation, +}: TransformGroupByDataToPieChartDataParams): TransformGroupByDataToPieChartDataResult => { + if (!isDefined(groupByData)) { + return EMPTY_PIE_CHART_RESULT; + } + + const groupByField = objectMetadataItem.fields.find( + (field: FieldMetadataItem) => + field.id === configuration.groupByFieldMetadataId, + ); + + const aggregateField = objectMetadataItem.fields.find( + (field: FieldMetadataItem) => + field.id === configuration.aggregateFieldMetadataId, + ); + + if (!isDefined(groupByField) || !isDefined(aggregateField)) { + return EMPTY_PIE_CHART_RESULT; + } + + const queryName = getGroupByQueryResultGqlFieldName(objectMetadataItem); + const rawResults = groupByData[queryName]; + + if (!isDefined(rawResults) || !Array.isArray(rawResults)) { + return EMPTY_PIE_CHART_RESULT; + } + + // TODO: Add a limit to the query instead of slicing here (issue: twentyhq/core-team-issues#1600) + const limitedResults = rawResults.slice( + 0, + PIE_CHART_MAXIMUM_NUMBER_OF_SLICES, + ); + + const data: PieChartDataItem[] = limitedResults.map((result) => { + const dimensionValues = result.groupByDimensionValues; + + const label = isDefined(dimensionValues?.[0]) + ? formatDimensionValue({ + value: dimensionValues[0], + fieldMetadata: groupByField, + dateGranularity: configuration.dateGranularity ?? undefined, + subFieldName: configuration.groupBySubFieldName ?? undefined, + }) + : ''; + + const value = computeAggregateValueFromGroupByResult({ + rawResult: result, + aggregateField, + aggregateOperation: + configuration.aggregateOperation as unknown as ExtendedAggregateOperations, + aggregateOperationFromRawResult: aggregateOperation, + objectMetadataItem, + }); + + return { + id: label, + value: value, + label: label, + color: (configuration.color as GraphColor | undefined) ?? undefined, + }; + }); + + return { + data, + hasTooManyGroups: rawResults.length > PIE_CHART_MAXIMUM_NUMBER_OF_SLICES, + }; +}; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/hooks/useGraphWidgetGroupByQuery.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/hooks/useGraphWidgetGroupByQuery.ts index 3541c07c7a..61f056ce36 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/hooks/useGraphWidgetGroupByQuery.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/hooks/useGraphWidgetGroupByQuery.ts @@ -3,11 +3,17 @@ import { generateGroupByAggregateQuery } from '@/object-record/record-aggregate/ import { getAvailableAggregationsFromObjectFields } from '@/object-record/utils/getAvailableAggregationsFromObjectFields'; import { useGraphWidgetQueryCommon } from '@/page-layout/widgets/graph/hooks/useGraphWidgetQueryCommon'; import { type GroupByChartConfiguration } from '@/page-layout/widgets/graph/types/GroupByChartConfiguration'; -import { generateGroupByQueryVariablesFromChartConfiguration } from '@/page-layout/widgets/graph/utils/generateGroupByQueryVariablesFromChartConfiguration'; +import { generateGroupByQueryVariablesFromBarOrLineChartConfiguration } from '@/page-layout/widgets/graph/utils/generateGroupByQueryVariablesFromBarOrLineChartConfiguration'; +import { generateGroupByQueryVariablesFromPieChartConfiguration } from '@/page-layout/widgets/graph/utils/generateGroupByQueryVariablesFromPieChartConfiguration'; import { useQuery } from '@apollo/client'; import { useMemo } from 'react'; import { DEFAULT_NUMBER_OF_GROUPS_LIMIT } from 'twenty-shared/constants'; import { isDefined } from 'twenty-shared/utils'; +import { + type BarChartConfiguration, + type LineChartConfiguration, + type PieChartConfiguration, +} from '~/generated/graphql'; export const useGraphWidgetGroupByQuery = ({ objectMetadataItemId, @@ -45,13 +51,27 @@ export const useGraphWidgetGroupByQuery = ({ throw new Error('Aggregate operation not found'); } - const groupByQueryVariables = - generateGroupByQueryVariablesFromChartConfiguration({ - objectMetadataItem, - chartConfiguration: configuration, - aggregateOperation: aggregateOperation, - limit, - }); + const isPieChart = ( + config: GroupByChartConfiguration, + ): config is PieChartConfiguration => { + return config.__typename === 'PieChartConfiguration'; + }; + + const groupByQueryVariables = isPieChart(configuration) + ? generateGroupByQueryVariablesFromPieChartConfiguration({ + objectMetadataItem, + chartConfiguration: configuration, + aggregateOperation: aggregateOperation, + limit, + }) + : generateGroupByQueryVariablesFromBarOrLineChartConfiguration({ + objectMetadataItem, + chartConfiguration: configuration as + | BarChartConfiguration + | LineChartConfiguration, + aggregateOperation: aggregateOperation, + limit, + }); const variables = { ...groupByQueryVariables, diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/hooks/useGraphWidgetQueryCommon.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/hooks/useGraphWidgetQueryCommon.ts index 5dfcfb3c12..0383505467 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/hooks/useGraphWidgetQueryCommon.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/hooks/useGraphWidgetQueryCommon.ts @@ -7,6 +7,7 @@ import { type AggregateChartConfiguration, type BarChartConfiguration, type LineChartConfiguration, + type PieChartConfiguration, } from '~/generated/graphql'; export const useGraphWidgetQueryCommon = ({ @@ -17,7 +18,8 @@ export const useGraphWidgetQueryCommon = ({ configuration: | BarChartConfiguration | AggregateChartConfiguration - | LineChartConfiguration; + | LineChartConfiguration + | PieChartConfiguration; }) => { const { objectMetadataItem } = useObjectMetadataItemById({ objectId: objectMetadataItemId, diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/types/GroupByChartConfiguration.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/types/GroupByChartConfiguration.ts index 6da4bf5a71..9256026029 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/types/GroupByChartConfiguration.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/types/GroupByChartConfiguration.ts @@ -1,8 +1,10 @@ import { type BarChartConfiguration, type LineChartConfiguration, + type PieChartConfiguration, } from '~/generated-metadata/graphql'; export type GroupByChartConfiguration = | BarChartConfiguration - | LineChartConfiguration; + | LineChartConfiguration + | PieChartConfiguration; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/__snapshots__/generateGroupByQueryVariablesFromBarOrLineChartConfiguration.test.ts.snap b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/__snapshots__/generateGroupByQueryVariablesFromBarOrLineChartConfiguration.test.ts.snap new file mode 100644 index 0000000000..c4f477d29b --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/__snapshots__/generateGroupByQueryVariablesFromBarOrLineChartConfiguration.test.ts.snap @@ -0,0 +1,131 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generateGroupByQueryVariablesFromBarOrLineChartConfiguration Bar Chart Configuration Horizontal bar configuration should generate variables with secondary axis 1`] = ` +{ + "groupBy": [ + { + "stage": true, + }, + { + "ownerId": true, + }, + ], +} +`; + +exports[`generateGroupByQueryVariablesFromBarOrLineChartConfiguration Bar Chart Configuration Horizontal bar configuration should generate variables with single groupBy field 1`] = ` +{ + "groupBy": [ + { + "stage": true, + }, + ], +} +`; + +exports[`generateGroupByQueryVariablesFromBarOrLineChartConfiguration Bar Chart Configuration Vertical bar configuration should generate variables with composite field 1`] = ` +{ + "groupBy": [ + { + "name": { + "firstName": true, + }, + }, + ], +} +`; + +exports[`generateGroupByQueryVariablesFromBarOrLineChartConfiguration Bar Chart Configuration Vertical bar configuration should generate variables with date field and granularity 1`] = ` +{ + "groupBy": [ + { + "createdAt": { + "granularity": "MONTH", + }, + }, + ], +} +`; + +exports[`generateGroupByQueryVariablesFromBarOrLineChartConfiguration Bar Chart Configuration Vertical bar configuration should generate variables with single groupBy field 1`] = ` +{ + "groupBy": [ + { + "stage": true, + }, + ], +} +`; + +exports[`generateGroupByQueryVariablesFromBarOrLineChartConfiguration Bar Chart Configuration Vertical bar configuration should generate variables with two groupBy fields 1`] = ` +{ + "groupBy": [ + { + "stage": true, + }, + { + "ownerId": true, + }, + ], +} +`; + +exports[`generateGroupByQueryVariablesFromBarOrLineChartConfiguration Line Chart Configuration should generate variables with composite field 1`] = ` +{ + "groupBy": [ + { + "name": { + "firstName": true, + }, + }, + ], +} +`; + +exports[`generateGroupByQueryVariablesFromBarOrLineChartConfiguration Line Chart Configuration should generate variables with date field and granularity 1`] = ` +{ + "groupBy": [ + { + "createdAt": { + "granularity": "MONTH", + }, + }, + ], +} +`; + +exports[`generateGroupByQueryVariablesFromBarOrLineChartConfiguration Line Chart Configuration should generate variables with secondary axis 1`] = ` +{ + "groupBy": [ + { + "stage": true, + }, + { + "ownerId": true, + }, + ], +} +`; + +exports[`generateGroupByQueryVariablesFromBarOrLineChartConfiguration Line Chart Configuration should generate variables with single groupBy field 1`] = ` +{ + "groupBy": [ + { + "stage": true, + }, + ], +} +`; + +exports[`generateGroupByQueryVariablesFromBarOrLineChartConfiguration Line Chart Configuration should generate variables with two groupBy fields (multi-series) 1`] = ` +{ + "groupBy": [ + { + "stage": true, + }, + { + "ownerId": true, + }, + ], +} +`; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/__snapshots__/generateGroupByQueryVariablesFromChartConfiguration.test.ts.snap b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/__snapshots__/generateGroupByQueryVariablesFromChartConfiguration.test.ts.snap deleted file mode 100644 index 62a5bac6ae..0000000000 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/__snapshots__/generateGroupByQueryVariablesFromChartConfiguration.test.ts.snap +++ /dev/null @@ -1,131 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`generateGroupByQueryVariablesFromChartConfiguration Bar Chart Configuration Horizontal bar configuration should generate variables with secondary axis 1`] = ` -{ - "groupBy": [ - { - "stage": true, - }, - { - "ownerId": true, - }, - ], -} -`; - -exports[`generateGroupByQueryVariablesFromChartConfiguration Bar Chart Configuration Horizontal bar configuration should generate variables with single groupBy field 1`] = ` -{ - "groupBy": [ - { - "stage": true, - }, - ], -} -`; - -exports[`generateGroupByQueryVariablesFromChartConfiguration Bar Chart Configuration Vertical bar configuration should generate variables with composite field 1`] = ` -{ - "groupBy": [ - { - "name": { - "firstName": true, - }, - }, - ], -} -`; - -exports[`generateGroupByQueryVariablesFromChartConfiguration Bar Chart Configuration Vertical bar configuration should generate variables with date field and granularity 1`] = ` -{ - "groupBy": [ - { - "createdAt": { - "granularity": "MONTH", - }, - }, - ], -} -`; - -exports[`generateGroupByQueryVariablesFromChartConfiguration Bar Chart Configuration Vertical bar configuration should generate variables with single groupBy field 1`] = ` -{ - "groupBy": [ - { - "stage": true, - }, - ], -} -`; - -exports[`generateGroupByQueryVariablesFromChartConfiguration Bar Chart Configuration Vertical bar configuration should generate variables with two groupBy fields 1`] = ` -{ - "groupBy": [ - { - "stage": true, - }, - { - "ownerId": true, - }, - ], -} -`; - -exports[`generateGroupByQueryVariablesFromChartConfiguration Line Chart Configuration should generate variables with composite field 1`] = ` -{ - "groupBy": [ - { - "name": { - "firstName": true, - }, - }, - ], -} -`; - -exports[`generateGroupByQueryVariablesFromChartConfiguration Line Chart Configuration should generate variables with date field and granularity 1`] = ` -{ - "groupBy": [ - { - "createdAt": { - "granularity": "MONTH", - }, - }, - ], -} -`; - -exports[`generateGroupByQueryVariablesFromChartConfiguration Line Chart Configuration should generate variables with secondary axis 1`] = ` -{ - "groupBy": [ - { - "stage": true, - }, - { - "ownerId": true, - }, - ], -} -`; - -exports[`generateGroupByQueryVariablesFromChartConfiguration Line Chart Configuration should generate variables with single groupBy field 1`] = ` -{ - "groupBy": [ - { - "stage": true, - }, - ], -} -`; - -exports[`generateGroupByQueryVariablesFromChartConfiguration Line Chart Configuration should generate variables with two groupBy fields (multi-series) 1`] = ` -{ - "groupBy": [ - { - "stage": true, - }, - { - "ownerId": true, - }, - ], -} -`; diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/__snapshots__/generateGroupByQueryVariablesFromPieChartConfiguration.test.ts.snap b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/__snapshots__/generateGroupByQueryVariablesFromPieChartConfiguration.test.ts.snap new file mode 100644 index 0000000000..2ca0f5b3be --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/__snapshots__/generateGroupByQueryVariablesFromPieChartConfiguration.test.ts.snap @@ -0,0 +1,64 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generateGroupByQueryVariablesFromPieChartConfiguration Basic Configuration should generate variables with single groupBy field 1`] = ` +{ + "groupBy": [ + { + "stage": true, + }, + ], +} +`; + +exports[`generateGroupByQueryVariablesFromPieChartConfiguration Basic Configuration should generate variables with composite field 1`] = ` +{ + "groupBy": [ + { + "name": { + "firstName": true, + }, + }, + ], +} +`; + +exports[`generateGroupByQueryVariablesFromPieChartConfiguration Basic Configuration should generate variables with date field and granularity 1`] = ` +{ + "groupBy": [ + { + "createdAt": { + "granularity": "MONTH", + }, + }, + ], +} +`; + +exports[`generateGroupByQueryVariablesFromPieChartConfiguration Basic Configuration should generate variables with limit 1`] = ` +{ + "groupBy": [ + { + "stage": true, + }, + ], + "limit": 10, +} +`; + +exports[`generateGroupByQueryVariablesFromPieChartConfiguration Basic Configuration should generate variables with orderBy 1`] = ` +{ + "groupBy": [ + { + "stage": true, + }, + ], + "orderBy": [ + { + "aggregate": { + "count": "AscNullsLast", + }, + }, + ], +} +`; + diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/generateGroupByQueryVariablesFromChartConfiguration.test.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/generateGroupByQueryVariablesFromBarOrLineChartConfiguration.test.ts similarity index 56% rename from packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/generateGroupByQueryVariablesFromChartConfiguration.test.ts rename to packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/generateGroupByQueryVariablesFromBarOrLineChartConfiguration.test.ts index 16b8fc0222..80b9e9bc97 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/generateGroupByQueryVariablesFromChartConfiguration.test.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/generateGroupByQueryVariablesFromBarOrLineChartConfiguration.test.ts @@ -1,14 +1,14 @@ import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { generateGroupByQueryVariablesFromBarOrLineChartConfiguration } from '@/page-layout/widgets/graph/utils/generateGroupByQueryVariablesFromBarOrLineChartConfiguration'; +import { FieldMetadataType } from 'twenty-shared/types'; import { AggregateOperations, type BarChartConfiguration, - FieldMetadataType, GraphType, type LineChartConfiguration, -} from '~/generated-metadata/graphql'; -import { generateGroupByQueryVariablesFromChartConfiguration } from '../generateGroupByQueryVariablesFromChartConfiguration'; +} from '~/generated/graphql'; -describe('generateGroupByQueryVariablesFromChartConfiguration', () => { +describe('generateGroupByQueryVariablesFromBarOrLineChartConfiguration', () => { const mockObjectMetadataItem: ObjectMetadataItem = { id: 'obj-1', nameSingular: 'opportunity', @@ -64,56 +64,60 @@ describe('generateGroupByQueryVariablesFromChartConfiguration', () => { describe('Bar Chart Configuration', () => { describe('Vertical bar configuration', () => { it('should generate variables with single groupBy field', () => { - const result = generateGroupByQueryVariablesFromChartConfiguration({ - objectMetadataItem: mockObjectMetadataItem, - chartConfiguration: buildBarChartConfiguration({ - graphType: GraphType.VERTICAL_BAR, - primaryAxisGroupByFieldMetadataId: 'field-1', - primaryAxisGroupBySubFieldName: null, - }), - }); + const result = + generateGroupByQueryVariablesFromBarOrLineChartConfiguration({ + objectMetadataItem: mockObjectMetadataItem, + chartConfiguration: buildBarChartConfiguration({ + graphType: GraphType.VERTICAL_BAR, + primaryAxisGroupByFieldMetadataId: 'field-1', + primaryAxisGroupBySubFieldName: null, + }), + }); expect(result).toMatchSnapshot(); }); it('should generate variables with two groupBy fields', () => { - const result = generateGroupByQueryVariablesFromChartConfiguration({ - objectMetadataItem: mockObjectMetadataItem, - chartConfiguration: buildBarChartConfiguration({ - graphType: GraphType.VERTICAL_BAR, - primaryAxisGroupByFieldMetadataId: 'field-1', - primaryAxisGroupBySubFieldName: null, - secondaryAxisGroupByFieldMetadataId: 'field-2', - secondaryAxisGroupBySubFieldName: null, - }), - }); + const result = + generateGroupByQueryVariablesFromBarOrLineChartConfiguration({ + objectMetadataItem: mockObjectMetadataItem, + chartConfiguration: buildBarChartConfiguration({ + graphType: GraphType.VERTICAL_BAR, + primaryAxisGroupByFieldMetadataId: 'field-1', + primaryAxisGroupBySubFieldName: null, + secondaryAxisGroupByFieldMetadataId: 'field-2', + secondaryAxisGroupBySubFieldName: null, + }), + }); expect(result).toMatchSnapshot(); }); it('should generate variables with composite field', () => { - const result = generateGroupByQueryVariablesFromChartConfiguration({ - objectMetadataItem: mockObjectMetadataItem, - chartConfiguration: buildBarChartConfiguration({ - graphType: GraphType.VERTICAL_BAR, - primaryAxisGroupByFieldMetadataId: 'field-4', - primaryAxisGroupBySubFieldName: 'firstName', - }), - }); + const result = + generateGroupByQueryVariablesFromBarOrLineChartConfiguration({ + objectMetadataItem: mockObjectMetadataItem, + chartConfiguration: buildBarChartConfiguration({ + graphType: GraphType.VERTICAL_BAR, + primaryAxisGroupByFieldMetadataId: 'field-4', + primaryAxisGroupBySubFieldName: 'firstName', + }), + }); expect(result).toMatchSnapshot(); }); it('should generate variables with date field and granularity', () => { - const result = generateGroupByQueryVariablesFromChartConfiguration({ - objectMetadataItem: mockObjectMetadataItem, - chartConfiguration: buildBarChartConfiguration({ - graphType: GraphType.VERTICAL_BAR, - primaryAxisGroupByFieldMetadataId: 'field-3', - primaryAxisGroupBySubFieldName: null, - primaryAxisDateGranularity: 'MONTH' as any, - }), - }); + const result = + generateGroupByQueryVariablesFromBarOrLineChartConfiguration({ + objectMetadataItem: mockObjectMetadataItem, + chartConfiguration: buildBarChartConfiguration({ + graphType: GraphType.VERTICAL_BAR, + primaryAxisGroupByFieldMetadataId: 'field-3', + primaryAxisGroupBySubFieldName: null, + primaryAxisDateGranularity: 'MONTH' as any, + }), + }); expect(result).toMatchSnapshot(); }); @@ -121,27 +125,29 @@ describe('generateGroupByQueryVariablesFromChartConfiguration', () => { describe('Horizontal bar configuration', () => { it('should generate variables with single groupBy field', () => { - const result = generateGroupByQueryVariablesFromChartConfiguration({ - objectMetadataItem: mockObjectMetadataItem, - chartConfiguration: buildBarChartConfiguration({ - graphType: GraphType.HORIZONTAL_BAR, - primaryAxisGroupByFieldMetadataId: 'field-1', - primaryAxisGroupBySubFieldName: null, - }), - }); + const result = + generateGroupByQueryVariablesFromBarOrLineChartConfiguration({ + objectMetadataItem: mockObjectMetadataItem, + chartConfiguration: buildBarChartConfiguration({ + graphType: GraphType.HORIZONTAL_BAR, + primaryAxisGroupByFieldMetadataId: 'field-1', + primaryAxisGroupBySubFieldName: null, + }), + }); expect(result).toMatchSnapshot(); }); it('should generate variables with secondary axis', () => { - const result = generateGroupByQueryVariablesFromChartConfiguration({ - objectMetadataItem: mockObjectMetadataItem, - chartConfiguration: buildBarChartConfiguration({ - graphType: GraphType.HORIZONTAL_BAR, - primaryAxisGroupByFieldMetadataId: 'field-1', - secondaryAxisGroupByFieldMetadataId: 'field-2', - }), - }); + const result = + generateGroupByQueryVariablesFromBarOrLineChartConfiguration({ + objectMetadataItem: mockObjectMetadataItem, + chartConfiguration: buildBarChartConfiguration({ + graphType: GraphType.HORIZONTAL_BAR, + primaryAxisGroupByFieldMetadataId: 'field-1', + secondaryAxisGroupByFieldMetadataId: 'field-2', + }), + }); expect(result).toMatchSnapshot(); }); @@ -150,69 +156,74 @@ describe('generateGroupByQueryVariablesFromChartConfiguration', () => { describe('Line Chart Configuration', () => { it('should generate variables with single groupBy field', () => { - const result = generateGroupByQueryVariablesFromChartConfiguration({ - objectMetadataItem: mockObjectMetadataItem, - chartConfiguration: buildLineChartConfiguration({ - graphType: GraphType.LINE, - primaryAxisGroupByFieldMetadataId: 'field-1', - primaryAxisGroupBySubFieldName: null, - }), - }); + const result = + generateGroupByQueryVariablesFromBarOrLineChartConfiguration({ + objectMetadataItem: mockObjectMetadataItem, + chartConfiguration: buildLineChartConfiguration({ + graphType: GraphType.LINE, + primaryAxisGroupByFieldMetadataId: 'field-1', + primaryAxisGroupBySubFieldName: null, + }), + }); expect(result).toMatchSnapshot(); }); it('should generate variables with two groupBy fields (multi-series)', () => { - const result = generateGroupByQueryVariablesFromChartConfiguration({ - objectMetadataItem: mockObjectMetadataItem, - chartConfiguration: buildLineChartConfiguration({ - graphType: GraphType.LINE, - primaryAxisGroupByFieldMetadataId: 'field-1', - primaryAxisGroupBySubFieldName: null, - secondaryAxisGroupByFieldMetadataId: 'field-2', - secondaryAxisGroupBySubFieldName: null, - }), - }); + const result = + generateGroupByQueryVariablesFromBarOrLineChartConfiguration({ + objectMetadataItem: mockObjectMetadataItem, + chartConfiguration: buildLineChartConfiguration({ + graphType: GraphType.LINE, + primaryAxisGroupByFieldMetadataId: 'field-1', + primaryAxisGroupBySubFieldName: null, + secondaryAxisGroupByFieldMetadataId: 'field-2', + secondaryAxisGroupBySubFieldName: null, + }), + }); expect(result).toMatchSnapshot(); }); it('should generate variables with date field and granularity', () => { - const result = generateGroupByQueryVariablesFromChartConfiguration({ - objectMetadataItem: mockObjectMetadataItem, - chartConfiguration: buildLineChartConfiguration({ - graphType: GraphType.LINE, - primaryAxisGroupByFieldMetadataId: 'field-3', - primaryAxisGroupBySubFieldName: null, - primaryAxisDateGranularity: 'MONTH' as any, - }), - }); + const result = + generateGroupByQueryVariablesFromBarOrLineChartConfiguration({ + objectMetadataItem: mockObjectMetadataItem, + chartConfiguration: buildLineChartConfiguration({ + graphType: GraphType.LINE, + primaryAxisGroupByFieldMetadataId: 'field-3', + primaryAxisGroupBySubFieldName: null, + primaryAxisDateGranularity: 'MONTH' as any, + }), + }); expect(result).toMatchSnapshot(); }); it('should generate variables with composite field', () => { - const result = generateGroupByQueryVariablesFromChartConfiguration({ - objectMetadataItem: mockObjectMetadataItem, - chartConfiguration: buildLineChartConfiguration({ - graphType: GraphType.LINE, - primaryAxisGroupByFieldMetadataId: 'field-4', - primaryAxisGroupBySubFieldName: 'firstName', - }), - }); + const result = + generateGroupByQueryVariablesFromBarOrLineChartConfiguration({ + objectMetadataItem: mockObjectMetadataItem, + chartConfiguration: buildLineChartConfiguration({ + graphType: GraphType.LINE, + primaryAxisGroupByFieldMetadataId: 'field-4', + primaryAxisGroupBySubFieldName: 'firstName', + }), + }); expect(result).toMatchSnapshot(); }); it('should generate variables with secondary axis', () => { - const result = generateGroupByQueryVariablesFromChartConfiguration({ - objectMetadataItem: mockObjectMetadataItem, - chartConfiguration: buildLineChartConfiguration({ - graphType: GraphType.LINE, - primaryAxisGroupByFieldMetadataId: 'field-1', - secondaryAxisGroupByFieldMetadataId: 'field-2', - }), - }); + const result = + generateGroupByQueryVariablesFromBarOrLineChartConfiguration({ + objectMetadataItem: mockObjectMetadataItem, + chartConfiguration: buildLineChartConfiguration({ + graphType: GraphType.LINE, + primaryAxisGroupByFieldMetadataId: 'field-1', + secondaryAxisGroupByFieldMetadataId: 'field-2', + }), + }); expect(result).toMatchSnapshot(); }); @@ -221,7 +232,7 @@ describe('generateGroupByQueryVariablesFromChartConfiguration', () => { describe('Error handling', () => { it('should throw error when primary axis field not found (bar chart)', () => { expect(() => - generateGroupByQueryVariablesFromChartConfiguration({ + generateGroupByQueryVariablesFromBarOrLineChartConfiguration({ objectMetadataItem: mockObjectMetadataItem, chartConfiguration: buildBarChartConfiguration({ primaryAxisGroupByFieldMetadataId: 'invalid-field', @@ -232,7 +243,7 @@ describe('generateGroupByQueryVariablesFromChartConfiguration', () => { it('should throw error when primary axis field not found (line chart)', () => { expect(() => - generateGroupByQueryVariablesFromChartConfiguration({ + generateGroupByQueryVariablesFromBarOrLineChartConfiguration({ objectMetadataItem: mockObjectMetadataItem, chartConfiguration: buildLineChartConfiguration({ primaryAxisGroupByFieldMetadataId: 'invalid-field', diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/generateGroupByQueryVariablesFromPieChartConfiguration.test.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/generateGroupByQueryVariablesFromPieChartConfiguration.test.ts new file mode 100644 index 0000000000..1fa9061e6f --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/generateGroupByQueryVariablesFromPieChartConfiguration.test.ts @@ -0,0 +1,130 @@ +import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { + AggregateOperations, + FieldMetadataType, + GraphOrderBy, + GraphType, + type PieChartConfiguration, +} from '~/generated-metadata/graphql'; +import { generateGroupByQueryVariablesFromPieChartConfiguration } from '../generateGroupByQueryVariablesFromPieChartConfiguration'; + +describe('generateGroupByQueryVariablesFromPieChartConfiguration', () => { + const mockObjectMetadataItem: ObjectMetadataItem = { + id: 'obj-1', + nameSingular: 'opportunity', + namePlural: 'opportunities', + fields: [ + { + id: 'field-1', + name: 'stage', + type: FieldMetadataType.TEXT, + }, + { + id: 'field-2', + name: 'owner', + type: FieldMetadataType.RELATION, + }, + { + id: 'field-3', + name: 'createdAt', + type: FieldMetadataType.DATE_TIME, + }, + { + id: 'field-4', + name: 'name', + type: FieldMetadataType.FULL_NAME, + }, + ], + } as ObjectMetadataItem; + + const buildPieChartConfiguration = ( + overrides: Partial, + ): PieChartConfiguration => + ({ + __typename: 'PieChartConfiguration', + aggregateFieldMetadataId: 'aggregate-field', + aggregateOperation: AggregateOperations.COUNT, + graphType: GraphType.PIE, + groupByFieldMetadataId: 'field-1', + ...overrides, + }) as PieChartConfiguration; + + describe('Basic Configuration', () => { + it('should generate variables with single groupBy field', () => { + const result = generateGroupByQueryVariablesFromPieChartConfiguration({ + objectMetadataItem: mockObjectMetadataItem, + chartConfiguration: buildPieChartConfiguration({ + groupByFieldMetadataId: 'field-1', + groupBySubFieldName: null, + }), + }); + + expect(result).toMatchSnapshot(); + }); + + it('should generate variables with composite field', () => { + const result = generateGroupByQueryVariablesFromPieChartConfiguration({ + objectMetadataItem: mockObjectMetadataItem, + chartConfiguration: buildPieChartConfiguration({ + groupByFieldMetadataId: 'field-4', + groupBySubFieldName: 'firstName', + }), + }); + + expect(result).toMatchSnapshot(); + }); + + it('should generate variables with date field and granularity', () => { + const result = generateGroupByQueryVariablesFromPieChartConfiguration({ + objectMetadataItem: mockObjectMetadataItem, + chartConfiguration: buildPieChartConfiguration({ + groupByFieldMetadataId: 'field-3', + groupBySubFieldName: null, + dateGranularity: 'MONTH' as any, + }), + }); + + expect(result).toMatchSnapshot(); + }); + + it('should generate variables with limit', () => { + const result = generateGroupByQueryVariablesFromPieChartConfiguration({ + objectMetadataItem: mockObjectMetadataItem, + chartConfiguration: buildPieChartConfiguration({ + groupByFieldMetadataId: 'field-1', + }), + limit: 10, + }); + + expect(result).toMatchSnapshot(); + expect(result.limit).toBe(10); + }); + + it('should generate variables with orderBy', () => { + const result = generateGroupByQueryVariablesFromPieChartConfiguration({ + objectMetadataItem: mockObjectMetadataItem, + chartConfiguration: buildPieChartConfiguration({ + groupByFieldMetadataId: 'field-1', + orderBy: GraphOrderBy.VALUE_ASC, + }), + aggregateOperation: 'count', + }); + + expect(result).toMatchSnapshot(); + expect(result.orderBy).toBeDefined(); + }); + }); + + describe('Error Handling', () => { + it('should throw error when groupBy field not found', () => { + expect(() => + generateGroupByQueryVariablesFromPieChartConfiguration({ + objectMetadataItem: mockObjectMetadataItem, + chartConfiguration: buildPieChartConfiguration({ + groupByFieldMetadataId: 'invalid-field', + }), + }), + ).toThrow('Field with id invalid-field not found in object metadata'); + }); + }); +}); diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/generateGroupByQueryVariablesFromChartConfiguration.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/generateGroupByQueryVariablesFromBarOrLineChartConfiguration.ts similarity index 93% rename from packages/twenty-front/src/modules/page-layout/widgets/graph/utils/generateGroupByQueryVariablesFromChartConfiguration.ts rename to packages/twenty-front/src/modules/page-layout/widgets/graph/utils/generateGroupByQueryVariablesFromBarOrLineChartConfiguration.ts index 95613d1fbe..dd4dec393c 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/generateGroupByQueryVariablesFromChartConfiguration.ts +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/generateGroupByQueryVariablesFromBarOrLineChartConfiguration.ts @@ -1,5 +1,4 @@ import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { type GroupByChartConfiguration } from '@/page-layout/widgets/graph/types/GroupByChartConfiguration'; import { getGroupByOrderBy } from '@/page-layout/widgets/graph/utils/getGroupByOrderBy'; import { type AggregateOrderByWithGroupByField, @@ -8,16 +7,20 @@ import { type ObjectRecordOrderByWithGroupByDateField, } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; +import { + type BarChartConfiguration, + type LineChartConfiguration, +} from '~/generated/graphql'; import { buildGroupByFieldObject } from './buildGroupByFieldObject'; -export const generateGroupByQueryVariablesFromChartConfiguration = ({ +export const generateGroupByQueryVariablesFromBarOrLineChartConfiguration = ({ objectMetadataItem, chartConfiguration, aggregateOperation, limit, }: { objectMetadataItem: ObjectMetadataItem; - chartConfiguration: GroupByChartConfiguration; + chartConfiguration: BarChartConfiguration | LineChartConfiguration; aggregateOperation?: string; limit?: number; }) => { diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/generateGroupByQueryVariablesFromPieChartConfiguration.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/generateGroupByQueryVariablesFromPieChartConfiguration.ts new file mode 100644 index 0000000000..235d1673d1 --- /dev/null +++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/generateGroupByQueryVariablesFromPieChartConfiguration.ts @@ -0,0 +1,73 @@ +import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { getGroupByOrderBy } from '@/page-layout/widgets/graph/utils/getGroupByOrderBy'; +import { + type AggregateOrderByWithGroupByField, + type ObjectRecordOrderByForCompositeField, + type ObjectRecordOrderByForScalarField, + type ObjectRecordOrderByWithGroupByDateField, +} from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; +import { type PieChartConfiguration } from '~/generated/graphql'; +import { buildGroupByFieldObject } from './buildGroupByFieldObject'; + +export const generateGroupByQueryVariablesFromPieChartConfiguration = ({ + objectMetadataItem, + chartConfiguration, + aggregateOperation, + limit, +}: { + objectMetadataItem: ObjectMetadataItem; + chartConfiguration: PieChartConfiguration; + aggregateOperation?: string; + limit?: number; +}) => { + const groupByFieldId = chartConfiguration.groupByFieldMetadataId; + const groupBySubFieldName = + chartConfiguration.groupBySubFieldName ?? undefined; + const dateGranularity = chartConfiguration.dateGranularity ?? undefined; + + const groupByField = objectMetadataItem.fields.find( + (field) => field.id === groupByFieldId, + ); + + if (!isDefined(groupByField) || !isDefined(groupByFieldId)) { + throw new Error( + `Field with id ${groupByFieldId} not found in object metadata`, + ); + } + + const groupBy: Array< + Record> + > = [ + buildGroupByFieldObject({ + field: groupByField, + subFieldName: groupBySubFieldName, + dateGranularity, + }), + ]; + + const orderBy: Array< + | AggregateOrderByWithGroupByField + | ObjectRecordOrderByForScalarField + | ObjectRecordOrderByWithGroupByDateField + | ObjectRecordOrderByForCompositeField + > = []; + + if (isDefined(chartConfiguration.orderBy)) { + orderBy.push( + getGroupByOrderBy({ + graphOrderBy: chartConfiguration.orderBy, + groupByField, + groupBySubFieldName, + aggregateOperation, + dateGranularity, + }), + ); + } + + return { + groupBy, + ...(orderBy.length > 0 && { orderBy }), + ...(isDefined(limit) && { limit }), + }; +};