Convert number chart to aggregate chart allowing date aggregates (#15294)

- Convert number chart to aggregate chart 
- Allow date aggregates on all charts
- Only allow `EARLIEST` and `LATEST` on Aggregate chart and Gauge chart
- Various design fixes


https://github.com/user-attachments/assets/b5a2239d-7b11-48b5-93e6-98ceffae5cab
This commit is contained in:
Raphaël Bosi
2025-10-23 18:48:42 +02:00
committed by GitHub
parent 6f45c47435
commit f301f07e43
45 changed files with 205 additions and 151 deletions
@@ -16,6 +16,7 @@ import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/
import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layout/types/ChartConfigurationSettingIds';
import { isChartSettingDisabled } from '@/command-menu/pages/page-layout/utils/isChartSettingDisabled';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { GRAPH_MAXIMUM_NUMBER_OF_GROUPS } from '@/page-layout/widgets/graph/constants/GraphMaximumNumberOfGroups.constant';
import { hasWidgetTooManyGroupsComponentState } from '@/page-layout/widgets/graph/states/hasWidgetTooManyGroupsComponentState';
import { useOpenDropdown } from '@/ui/layout/dropdown/hooks/useOpenDropdown';
@@ -23,9 +24,14 @@ import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectab
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { isDefined, isFieldMetadataDateKind } from 'twenty-shared/utils';
import { SidePanelInformationBanner } from 'twenty-ui/display';
import { GraphType, type PageLayoutWidget } from '~/generated/graphql';
import {
AggregateOperations,
GraphType,
type PageLayoutWidget,
} from '~/generated/graphql';
const StyledSidePanelInformationBanner = styled(SidePanelInformationBanner)`
margin-top: ${({ theme }) => theme.spacing(2)};
@@ -41,6 +47,7 @@ export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => {
const { setSelectedItemId } = useSelectableList(
COMMAND_MENU_LIST_SELECTABLE_LIST_ID,
);
const { objectMetadataItems } = useObjectMetadataItems();
if (widget.configuration?.__typename === 'IframeConfiguration') {
throw new Error(t`IframeConfiguration is not supported`);
@@ -69,11 +76,37 @@ export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => {
useRecoilComponentState(hasWidgetTooManyGroupsComponentState);
const handleGraphTypeChange = (graphType: GraphType) => {
const configToUpdate: Record<string, any> = {
__typename: GRAPH_TYPE_TO_CONFIG_TYPENAME[graphType],
graphType,
};
if (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,
);
if (
isDefined(aggregateField) &&
isFieldMetadataDateKind(aggregateField.type) &&
(configuration.aggregateOperation === AggregateOperations.MIN ||
configuration.aggregateOperation === AggregateOperations.MAX)
) {
configToUpdate.aggregateOperation = AggregateOperations.COUNT;
}
}
}
updateCurrentWidgetConfig({
configToUpdate: {
__typename: GRAPH_TYPE_TO_CONFIG_TYPENAME[graphType],
graphType,
},
configToUpdate,
});
updateCommandMenuPageInfo({
@@ -8,7 +8,7 @@ import { MenuPicker } from 'twenty-ui/navigation';
const graphTypeOptions = [
GraphType.VERTICAL_BAR,
GraphType.HORIZONTAL_BAR,
GraphType.NUMBER,
GraphType.AGGREGATE,
GraphType.PIE,
GraphType.LINE,
GraphType.GAUGE,
@@ -3,6 +3,7 @@ import { useUpdateCurrentWidgetConfig } from '@/command-menu/pages/page-layout/h
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel';
import { DateAggregateOperations } from '@/object-record/record-table/constants/DateAggregateOperations';
import { getAvailableAggregateOperationsForFieldMetadataType } from '@/object-record/record-table/record-table-footer/utils/getAvailableAggregateOperationsForFieldMetadataType';
import { convertExtendedAggregateOperationToAggregateOperation } from '@/object-record/utils/convertExtendedAggregateOperationToAggregateOperation';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
@@ -22,7 +23,7 @@ import { useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { IconChevronLeft } from 'twenty-ui/display';
import { MenuItemSelect } from 'twenty-ui/navigation';
import { type AggregateOperations } from '~/generated/graphql';
import { GraphType, type AggregateOperations } from '~/generated/graphql';
import { filterBySearchQuery } from '~/utils/filterBySearchQuery';
export const ChartAggregateOperationSelectionDropdownContent = ({
@@ -40,7 +41,8 @@ export const ChartAggregateOperationSelectionDropdownContent = ({
if (
widgetInEditMode?.configuration?.__typename !== 'BarChartConfiguration' &&
widgetInEditMode?.configuration?.__typename !== 'LineChartConfiguration' &&
widgetInEditMode?.configuration?.__typename !== 'NumberChartConfiguration'
widgetInEditMode?.configuration?.__typename !==
'AggregateChartConfiguration'
) {
throw new Error('Invalid configuration type');
}
@@ -71,7 +73,21 @@ export const ChartAggregateOperationSelectionDropdownContent = ({
})
: [];
const aggregateOperationsWithLabels = availableAggregateOperations.map(
const isAggregateOrGaugeChart =
widgetInEditMode.configuration.graphType === GraphType.AGGREGATE ||
widgetInEditMode.configuration.graphType === GraphType.GAUGE;
const filteredAggregateOperations = availableAggregateOperations.filter(
(operation) => {
return (
isAggregateOrGaugeChart ||
(operation !== DateAggregateOperations.EARLIEST &&
operation !== DateAggregateOperations.LATEST)
);
},
);
const aggregateOperationsWithLabels = filteredAggregateOperations.map(
(operation) => ({
operation,
label: getAggregateOperationLabel(operation),
@@ -14,7 +14,6 @@ import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/com
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { t } from '@lingui/core/macro';
import { useState } from 'react';
import { isFieldMetadataDateKind } from 'twenty-shared/utils';
import { useIcons } from 'twenty-ui/display';
import { MenuItemSelect } from 'twenty-ui/navigation';
import { filterBySearchQuery } from '~/utils/filterBySearchQuery';
@@ -32,7 +31,7 @@ export const ChartFieldSelectionForAggregateOperationDropdownContent = () => {
if (
configuration?.__typename !== 'BarChartConfiguration' &&
configuration?.__typename !== 'LineChartConfiguration' &&
configuration?.__typename !== 'NumberChartConfiguration'
configuration?.__typename !== 'AggregateChartConfiguration'
) {
throw new Error('Invalid configuration type');
}
@@ -61,12 +60,7 @@ export const ChartFieldSelectionForAggregateOperationDropdownContent = () => {
searchQuery,
getSearchableValues: (item) => [item.label, item.name],
// TODO: remove the relation filter once group by is supported for relation fields
}).filter(
(field) =>
!isFieldRelation(field) &&
!field.isSystem &&
!isFieldMetadataDateKind(field.type),
);
}).filter((field) => !isFieldRelation(field) && !field.isSystem);
const { getIcon } = useIcons();
@@ -3,7 +3,7 @@ import { DATA_DISPLAY_AGGREGATE_SETTING } from '@/command-menu/pages/page-layout
import { FILTER_SETTING } from '@/command-menu/pages/page-layout/constants/settings/FilterSetting';
import { type ChartSettingsGroup } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
export const NUMBER_CHART_SETTINGS: ChartSettingsGroup[] = [
export const AGGREGATE_CHART_SETTINGS: ChartSettingsGroup[] = [
{
heading: 'Data',
items: [
@@ -1,19 +1,19 @@
import { AGGREGATE_CHART_SETTINGS } from '@/command-menu/pages/page-layout/constants/AggregateChartSettings';
import { GAUGE_CHART_SETTINGS } from '@/command-menu/pages/page-layout/constants/GaugeChartSettings';
import { LINE_CHART_SETTINGS } from '@/command-menu/pages/page-layout/constants/LineChartSettings';
import { NUMBER_CHART_SETTINGS } from '@/command-menu/pages/page-layout/constants/NumberChartSettings';
import { PIE_CHART_SETTINGS } from '@/command-menu/pages/page-layout/constants/PieChartSettings';
import { type ChartSettingsGroup } from '@/command-menu/pages/page-layout/types/ChartSettingsGroup';
import { getBarChartSettings } from '@/command-menu/pages/page-layout/utils/getBarChartSettings';
import { type MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import {
Icon123,
IconChartBar,
IconChartBarHorizontal,
IconChartLine,
IconChartPie,
type IconComponent,
IconGauge,
IconSum,
} from 'twenty-ui/display';
import { GraphType } from '~/generated-metadata/graphql';
@@ -26,12 +26,12 @@ export const GRAPH_TYPE_INFORMATION: Record<
}
> = {
[GraphType.VERTICAL_BAR]: {
label: msg`Vertical Bar`,
label: msg`Vertical`,
icon: IconChartBar,
settings: getBarChartSettings(GraphType.VERTICAL_BAR),
},
[GraphType.HORIZONTAL_BAR]: {
label: msg`Horizontal Bar`,
label: msg`Horizontal`,
icon: IconChartBarHorizontal,
settings: getBarChartSettings(GraphType.HORIZONTAL_BAR),
},
@@ -45,10 +45,10 @@ export const GRAPH_TYPE_INFORMATION: Record<
icon: IconChartLine,
settings: LINE_CHART_SETTINGS,
},
[GraphType.NUMBER]: {
label: msg`Number`,
icon: Icon123,
settings: NUMBER_CHART_SETTINGS,
[GraphType.AGGREGATE]: {
label: msg`Aggregate`,
icon: IconSum,
settings: AGGREGATE_CHART_SETTINGS,
},
[GraphType.GAUGE]: {
label: msg`Gauge`,
@@ -5,6 +5,6 @@ export const GRAPH_TYPE_TO_CONFIG_TYPENAME = {
[GraphType.HORIZONTAL_BAR]: 'BarChartConfiguration',
[GraphType.LINE]: 'LineChartConfiguration',
[GraphType.PIE]: 'PieChartConfiguration',
[GraphType.NUMBER]: 'NumberChartConfiguration',
[GraphType.AGGREGATE]: 'AggregateChartConfiguration',
[GraphType.GAUGE]: 'GaugeChartConfiguration',
} as const;
@@ -2,11 +2,11 @@ import { ChartFieldSelectionForAggregateOperationDropdownContent } from '@/comma
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 { Icon123 } from 'twenty-ui/display';
import { IconSum } from 'twenty-ui/display';
export const DATA_DISPLAY_AGGREGATE_SETTING: ChartSettingsItem = {
isBoolean: false,
Icon: Icon123,
Icon: IconSum,
label: CHART_CONFIGURATION_SETTING_LABELS.DATA_ON_DISPLAY_AGGREGATE,
id: CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_AGGREGATE,
dependsOn: [CHART_CONFIGURATION_SETTING_IDS.SOURCE],
@@ -6,6 +6,7 @@ import { getChartAxisNameDisplayOptions } from '@/command-menu/pages/page-layout
import { getFieldLabelWithSubField } from '@/command-menu/pages/page-layout/utils/getFieldLabelWithSubField';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { getAggregateOperationLabel } from '@/object-record/record-board/record-board-column/utils/getAggregateOperationLabel';
import { convertAggregateOperationToExtendedAggregateOperation } from '@/object-record/utils/convertAggregateOperationToExtendedAggregateOperation';
import { useRecoilValue } from 'recoil';
import { type CompositeFieldSubFieldName } from 'twenty-shared/types';
import { capitalize, isDefined } from 'twenty-shared/utils';
@@ -83,7 +84,11 @@ export const useChartSettingsValues = ({
fieldMetadataItem.id === configuration.aggregateFieldMetadataId,
);
const yAxisAggregateOperation = configuration.aggregateOperation;
const aggregateOperation =
convertAggregateOperationToExtendedAggregateOperation(
configuration.aggregateOperation,
aggregateField?.type,
);
const xAxisOrderByLabel =
isDefined(xAxisOrderBy) && isDefined(groupByFieldXId)
@@ -131,11 +136,11 @@ export const useChartSettingsValues = ({
case CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_Y:
case CHART_CONFIGURATION_SETTING_IDS.DATA_ON_DISPLAY_AGGREGATE: {
const hasAggregateLabel = isDefined(aggregateField?.label);
const hasAggregateOperation = isDefined(yAxisAggregateOperation);
const hasAggregateOperation = isDefined(aggregateOperation);
return `${aggregateField?.label ?? ''}${
hasAggregateLabel && hasAggregateOperation
? ` (${getAggregateOperationLabel(yAxisAggregateOperation)})`
? ` (${getAggregateOperationLabel(aggregateOperation)})`
: ''
}`;
}
@@ -1,8 +1,8 @@
import {
type AggregateChartConfiguration,
type BarChartConfiguration,
type GaugeChartConfiguration,
type LineChartConfiguration,
type NumberChartConfiguration,
type PieChartConfiguration,
} from '~/generated/graphql';
@@ -10,5 +10,5 @@ export type ChartConfiguration =
| BarChartConfiguration
| LineChartConfiguration
| PieChartConfiguration
| NumberChartConfiguration
| AggregateChartConfiguration
| GaugeChartConfiguration;
@@ -1,8 +1,8 @@
import {
type AggregateChartConfiguration,
type BarChartConfiguration,
type GaugeChartConfiguration,
type LineChartConfiguration,
type NumberChartConfiguration,
type PieChartConfiguration,
} from '~/generated/graphql';
@@ -10,5 +10,5 @@ export type ChartWidgetConfiguration =
| BarChartConfiguration
| GaugeChartConfiguration
| LineChartConfiguration
| NumberChartConfiguration
| AggregateChartConfiguration
| PieChartConfiguration;