[DASHBOARDS] Pie Chart (#16048)

## Description

- Connect Pie Chart to the backend
- Update from the old design to the new design
- Switch seamlessly from/to other types of chart from the Pie Chart by
converting the configuration

Note: There are a couple things to implement before the pie chart is
ready to go live:
- Add a new setting on Bar, Line and Pie chart to hide and display
legends (toggle)
- Add data labels next to each slice

## Video QA


https://github.com/user-attachments/assets/b1561e32-0434-43ad-8855-d617b209ce27
This commit is contained in:
Raphaël Bosi
2025-11-25 14:30:16 +01:00
committed by GitHub
parent 88fbe6c51d
commit 6d141c2439
43 changed files with 1147 additions and 476 deletions
@@ -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
@@ -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');
}
@@ -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';
@@ -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');
}
@@ -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 (
<DropdownMenuItemsContainer>
<SelectableList
@@ -82,18 +115,16 @@ export const ChartXAxisSortBySelectionDropdownContent = () => {
<MenuItemSelect
text={getXSortOptionLabel({
graphOrderBy: sortOption.value,
groupByFieldMetadataIdX:
configuration.primaryAxisGroupByFieldMetadataId,
groupBySubFieldNameX:
configuration.primaryAxisGroupBySubFieldName as
| CompositeFieldSubFieldName
| undefined,
groupByFieldMetadataIdX: groupByFieldMetadataId ?? '',
groupBySubFieldNameX: groupBySubFieldName as
| CompositeFieldSubFieldName
| undefined,
aggregateFieldMetadataId:
configuration.aggregateFieldMetadataId ?? undefined,
aggregateOperation:
configuration.aggregateOperation ?? undefined,
})}
selected={configuration.primaryAxisOrderBy === sortOption.value}
selected={currentOrderBy === sortOption.value}
focused={selectedItemId === sortOption.value}
LeftIcon={sortOption.icon}
onClick={() => {
@@ -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,
],
},
@@ -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,
],
},
{
@@ -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`,
@@ -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,
};
@@ -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,
};
@@ -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,
};
@@ -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,
@@ -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:
@@ -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<string, any> = {
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<string, any> = {
__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 };
};
@@ -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',
@@ -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);
});
@@ -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;
};
@@ -0,0 +1,30 @@
import {
type BarChartConfiguration,
type LineChartConfiguration,
} from '~/generated/graphql';
export const convertBarOrLineChartConfigToPieChart = (
configuration: BarChartConfiguration | LineChartConfiguration,
): Record<string, any> => {
const configToUpdate: Record<string, any> = {};
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;
};
@@ -0,0 +1,27 @@
import { type PieChartConfiguration } from '~/generated/graphql';
export const convertPieChartConfigToBarOrLineChart = (
configuration: PieChartConfiguration,
): Record<string, any> => {
const configToUpdate: Record<string, any> = {};
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;
};
@@ -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,
];