Update chart limit error message (#16133)

## Description

- Display days, weeks, months or years instead of bars in the error
message
- Update the banner position
- Add translations on section titles

## Before
<img width="824" height="1378" alt="CleanShot 2025-11-27 at 14 51 40@2x"
src="https://github.com/user-attachments/assets/b2d7d1e6-e6d9-419b-8d7a-21f43e951898"
/>


## After
<img width="832" height="1382" alt="CleanShot 2025-11-27 at 14 51 15@2x"
src="https://github.com/user-attachments/assets/fe66d202-71be-45dc-8dff-502946e33aac"
/>
This commit is contained in:
Raphaël Bosi
2025-11-27 15:32:12 +01:00
committed by GitHub
parent d217767600
commit ec53302ba8
12 changed files with 194 additions and 57 deletions
@@ -0,0 +1,35 @@
import { getChartLimitMessage } from '@/command-menu/pages/page-layout/utils/getChartLimitMessage';
import { t } from '@lingui/core/macro';
import { SidePanelInformationBanner } from 'twenty-ui/display';
import { type GraphType } from '~/generated/graphql';
type ChartLimitInfoBannerProps = {
graphType: GraphType;
isPrimaryAxisDate: boolean;
primaryAxisDateGranularity: Parameters<
typeof getChartLimitMessage
>[0]['primaryAxisDateGranularity'];
};
export const ChartLimitInfoBanner = ({
graphType,
isPrimaryAxisDate,
primaryAxisDateGranularity,
}: ChartLimitInfoBannerProps) => {
return (
<SidePanelInformationBanner
message={getChartLimitMessage({
graphType,
isPrimaryAxisDate,
primaryAxisDateGranularity,
})}
tooltipMessage={
isPrimaryAxisDate
? t`Consider adding a filter or changing the date granularity to display more data.`
: t`Consider adding a filter to display more data.`
}
variant="warning"
/>
);
};
@@ -3,7 +3,9 @@ import { CommandMenuList } from '@/command-menu/components/CommandMenuList';
import { COMMAND_MENU_LIST_SELECTABLE_LIST_ID } from '@/command-menu/constants/CommandMenuListSelectableListId';
import { useUpdateCommandMenuPageInfo } from '@/command-menu/hooks/useUpdateCommandMenuPageInfo';
import { ChartSettingItem } from '@/command-menu/pages/page-layout/components/chart-settings/ChartSettingItem';
import { ChartLimitInfoBanner } from '@/command-menu/pages/page-layout/components/ChartLimitInfoBanner';
import { ChartTypeSelectionSection } from '@/command-menu/pages/page-layout/components/ChartTypeSelectionSection';
import { CHART_SETTINGS_HEADINGS } from '@/command-menu/pages/page-layout/constants/ChartSettingsHeadings';
import { GRAPH_TYPE_INFORMATION } from '@/command-menu/pages/page-layout/constants/GraphTypeInformation';
import { useChartSettingsValues } from '@/command-menu/pages/page-layout/hooks/useChartSettingsValues';
import { useNavigatePageLayoutCommandMenu } from '@/command-menu/pages/page-layout/hooks/useNavigatePageLayoutCommandMenu';
@@ -17,16 +19,12 @@ import { CHART_CONFIGURATION_SETTING_IDS } from '@/command-menu/pages/page-layou
import { shouldHideChartSetting } from '@/command-menu/pages/page-layout/utils/shouldHideChartSetting';
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';
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { SidePanelInformationBanner } from 'twenty-ui/display';
import {
FieldMetadataType,
@@ -41,10 +39,6 @@ const StyledCommandMenuContainer = styled.div`
overflow: hidden;
`;
const StyledSidePanelInformationBanner = styled(SidePanelInformationBanner)`
margin-top: ${({ theme }) => theme.spacing(2)};
`;
export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => {
const { updateCommandMenuPageInfo } = useUpdateCommandMenuPageInfo();
const { navigatePageLayoutCommandMenu } = useNavigatePageLayoutCommandMenu();
@@ -146,6 +140,19 @@ export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => {
primaryAxisField?.type === FieldMetadataType.DATE ||
primaryAxisField?.type === FieldMetadataType.DATE_TIME;
const primaryAxisDateGranularity =
configuration.__typename === 'BarChartConfiguration' ||
configuration.__typename === 'LineChartConfiguration'
? configuration.primaryAxisDateGranularity
: configuration.__typename === 'PieChartConfiguration'
? configuration.dateGranularity
: null;
const bannerTargetHeading =
currentGraphType === GraphType.PIE
? CHART_SETTINGS_HEADINGS.DATA
: CHART_SETTINGS_HEADINGS.X_AXIS;
return (
<StyledCommandMenuContainer>
<CommandMenuList commandGroups={[]} selectableItemIds={visibleItemIds}>
@@ -153,24 +160,6 @@ export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => {
currentGraphType={currentGraphType}
setCurrentGraphType={handleGraphTypeChange}
/>
{hasWidgetTooManyGroups && (
<StyledSidePanelInformationBanner
message={
currentGraphType === GraphType.LINE
? t`Undisplayed data: max ${LINE_CHART_MAXIMUM_NUMBER_OF_DATA_POINTS} data points 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
? t`Consider adding a filter or changing the date granularity to display more data.`
: t`Consider adding a filter to display more data.`
}
variant="warning"
/>
)}
{chartSettings.map((group) => {
const visibleItems = group.items.filter(
(item) =>
@@ -183,8 +172,17 @@ export const ChartSettings = ({ widget }: { widget: PageLayoutWidget }) => {
),
);
const shouldShowBanner = group.heading.id === bannerTargetHeading.id;
return (
<CommandGroup key={group.heading} heading={group.heading}>
<CommandGroup key={group.heading.id} heading={t(group.heading)}>
{shouldShowBanner && hasWidgetTooManyGroups && (
<ChartLimitInfoBanner
graphType={currentGraphType}
isPrimaryAxisDate={isPrimaryAxisDate}
primaryAxisDateGranularity={primaryAxisDateGranularity}
/>
)}
{visibleItems.map((item) => {
const handleItemToggleChange = () => {
setSelectedItemId(item.id);
@@ -1,3 +1,4 @@
import { CHART_SETTINGS_HEADINGS } from '@/command-menu/pages/page-layout/constants/ChartSettingsHeadings';
import { CHART_DATA_SOURCE_SETTING } from '@/command-menu/pages/page-layout/constants/settings/ChartDataSourceSetting';
import { DATA_DISPLAY_AGGREGATE_SETTING } from '@/command-menu/pages/page-layout/constants/settings/DataDisplayAggregateSetting';
import { FILTER_SETTING } from '@/command-menu/pages/page-layout/constants/settings/FilterSetting';
@@ -5,7 +6,7 @@ import { type ChartSettingsGroup } from '@/command-menu/pages/page-layout/types/
export const AGGREGATE_CHART_SETTINGS: ChartSettingsGroup[] = [
{
heading: 'Data',
heading: CHART_SETTINGS_HEADINGS.DATA,
items: [
CHART_DATA_SOURCE_SETTING,
FILTER_SETTING,
@@ -0,0 +1,8 @@
import { msg } from '@lingui/core/macro';
export const CHART_SETTINGS_HEADINGS = {
DATA: msg`Data`,
X_AXIS: msg`X axis`,
Y_AXIS: msg`Y axis`,
STYLE: msg`Style`,
} as const;
@@ -1,3 +1,4 @@
import { CHART_SETTINGS_HEADINGS } from '@/command-menu/pages/page-layout/constants/ChartSettingsHeadings';
import { CHART_DATA_SOURCE_SETTING } from '@/command-menu/pages/page-layout/constants/settings/ChartDataSourceSetting';
import { DATA_LABELS_SETTING } from '@/command-menu/pages/page-layout/constants/settings/DataLabelsSetting';
import { FILTER_SETTING } from '@/command-menu/pages/page-layout/constants/settings/FilterSetting';
@@ -5,11 +6,11 @@ import { type ChartSettingsGroup } from '@/command-menu/pages/page-layout/types/
export const GAUGE_CHART_SETTINGS: ChartSettingsGroup[] = [
{
heading: 'Data',
heading: CHART_SETTINGS_HEADINGS.DATA,
items: [CHART_DATA_SOURCE_SETTING, FILTER_SETTING],
},
{
heading: 'Style',
heading: CHART_SETTINGS_HEADINGS.STYLE,
items: [DATA_LABELS_SETTING],
},
];
@@ -1,3 +1,4 @@
import { CHART_SETTINGS_HEADINGS } from '@/command-menu/pages/page-layout/constants/ChartSettingsHeadings';
import { AXIS_NAME_SETTING } from '@/command-menu/pages/page-layout/constants/settings/AxisNameSetting';
import { CHART_DATA_SOURCE_SETTING } from '@/command-menu/pages/page-layout/constants/settings/ChartDataSourceSetting';
import { COLORS_SETTING } from '@/command-menu/pages/page-layout/constants/settings/ColorsSetting';
@@ -19,11 +20,11 @@ import { type ChartSettingsGroup } from '@/command-menu/pages/page-layout/types/
export const LINE_CHART_SETTINGS: ChartSettingsGroup[] = [
{
heading: 'Data',
heading: CHART_SETTINGS_HEADINGS.DATA,
items: [CHART_DATA_SOURCE_SETTING, FILTER_SETTING],
},
{
heading: 'X axis',
heading: CHART_SETTINGS_HEADINGS.X_AXIS,
items: [
DATA_DISPLAY_X_SETTING,
DATE_GRANULARITY_X_SETTING,
@@ -32,7 +33,7 @@ export const LINE_CHART_SETTINGS: ChartSettingsGroup[] = [
],
},
{
heading: 'Y axis',
heading: CHART_SETTINGS_HEADINGS.Y_AXIS,
items: [
DATA_DISPLAY_Y_SETTING,
GROUP_BY_SETTING,
@@ -43,7 +44,7 @@ export const LINE_CHART_SETTINGS: ChartSettingsGroup[] = [
],
},
{
heading: 'Style',
heading: CHART_SETTINGS_HEADINGS.STYLE,
items: [
COLORS_SETTING,
AXIS_NAME_SETTING,
@@ -1,3 +1,4 @@
import { CHART_SETTINGS_HEADINGS } from '@/command-menu/pages/page-layout/constants/ChartSettingsHeadings';
import { CENTER_METRIC_SETTING } from '@/command-menu/pages/page-layout/constants/settings/CenterMetricSetting';
import { CHART_DATA_SOURCE_SETTING } from '@/command-menu/pages/page-layout/constants/settings/ChartDataSourceSetting';
import { COLORS_SETTING } from '@/command-menu/pages/page-layout/constants/settings/ColorsSetting';
@@ -12,7 +13,7 @@ import { type ChartSettingsGroup } from '@/command-menu/pages/page-layout/types/
export const PIE_CHART_SETTINGS: ChartSettingsGroup[] = [
{
heading: 'Data',
heading: CHART_SETTINGS_HEADINGS.DATA,
items: [
CHART_DATA_SOURCE_SETTING,
FILTER_SETTING,
@@ -23,7 +24,7 @@ export const PIE_CHART_SETTINGS: ChartSettingsGroup[] = [
],
},
{
heading: 'Style',
heading: CHART_SETTINGS_HEADINGS.STYLE,
items: [
COLORS_SETTING,
DATA_LABELS_SETTING,
@@ -4,7 +4,7 @@ import { type ComponentType } from 'react';
import { type IconComponent } from 'twenty-ui/display';
export type ChartSettingsGroup = {
heading: string;
heading: MessageDescriptor;
items: ChartSettingsItem[];
};
@@ -1,3 +1,4 @@
import { CHART_SETTINGS_HEADINGS } from '@/command-menu/pages/page-layout/constants/ChartSettingsHeadings';
import { AXIS_NAME_SETTING } from '@/command-menu/pages/page-layout/constants/settings/AxisNameSetting';
import { CHART_DATA_SOURCE_SETTING } from '@/command-menu/pages/page-layout/constants/settings/ChartDataSourceSetting';
import { COLORS_SETTING } from '@/command-menu/pages/page-layout/constants/settings/ColorsSetting';
@@ -24,7 +25,9 @@ describe('getBarChartSettings', () => {
it('should place primary axis items under "X axis" heading', () => {
const result = getBarChartSettings(GraphType.VERTICAL_BAR);
const xAxisGroup = result.find((group) => group.heading === 'X axis');
const xAxisGroup = result.find(
(group) => group.heading.id === CHART_SETTINGS_HEADINGS.X_AXIS.id,
);
expect(xAxisGroup).toBeDefined();
expect(xAxisGroup?.items).toHaveLength(4);
@@ -39,7 +42,9 @@ describe('getBarChartSettings', () => {
it('should place secondary axis items under "Y axis" heading', () => {
const result = getBarChartSettings(GraphType.VERTICAL_BAR);
const yAxisGroup = result.find((group) => group.heading === 'Y axis');
const yAxisGroup = result.find(
(group) => group.heading.id === CHART_SETTINGS_HEADINGS.Y_AXIS.id,
);
expect(yAxisGroup).toBeDefined();
expect(yAxisGroup?.items).toHaveLength(6);
@@ -57,10 +62,10 @@ describe('getBarChartSettings', () => {
const result = getBarChartSettings(GraphType.VERTICAL_BAR);
expect(result).toHaveLength(4);
expect(result[0].heading).toBe('Data');
expect(result[1].heading).toBe('X axis');
expect(result[2].heading).toBe('Y axis');
expect(result[3].heading).toBe('Style');
expect(result[0].heading).toBe(CHART_SETTINGS_HEADINGS.DATA);
expect(result[1].heading).toBe(CHART_SETTINGS_HEADINGS.X_AXIS);
expect(result[2].heading).toBe(CHART_SETTINGS_HEADINGS.Y_AXIS);
expect(result[3].heading).toBe(CHART_SETTINGS_HEADINGS.STYLE);
});
});
@@ -68,7 +73,9 @@ describe('getBarChartSettings', () => {
it('should place SECONDARY axis items under "X axis" heading', () => {
const result = getBarChartSettings(GraphType.HORIZONTAL_BAR);
const xAxisGroup = result.find((group) => group.heading === 'X axis');
const xAxisGroup = result.find(
(group) => group.heading.id === CHART_SETTINGS_HEADINGS.X_AXIS.id,
);
expect(xAxisGroup).toBeDefined();
expect(xAxisGroup?.items).toHaveLength(6);
@@ -85,7 +92,9 @@ describe('getBarChartSettings', () => {
it('should place PRIMARY axis items under "Y axis" heading', () => {
const result = getBarChartSettings(GraphType.HORIZONTAL_BAR);
const yAxisGroup = result.find((group) => group.heading === 'Y axis');
const yAxisGroup = result.find(
(group) => group.heading.id === CHART_SETTINGS_HEADINGS.Y_AXIS.id,
);
expect(yAxisGroup).toBeDefined();
expect(yAxisGroup?.items).toHaveLength(4);
@@ -101,10 +110,10 @@ describe('getBarChartSettings', () => {
const result = getBarChartSettings(GraphType.HORIZONTAL_BAR);
expect(result).toHaveLength(4);
expect(result[0].heading).toBe('Data');
expect(result[1].heading).toBe('X axis');
expect(result[2].heading).toBe('Y axis');
expect(result[3].heading).toBe('Style');
expect(result[0].heading).toBe(CHART_SETTINGS_HEADINGS.DATA);
expect(result[1].heading).toBe(CHART_SETTINGS_HEADINGS.X_AXIS);
expect(result[2].heading).toBe(CHART_SETTINGS_HEADINGS.Y_AXIS);
expect(result[3].heading).toBe(CHART_SETTINGS_HEADINGS.STYLE);
});
});
@@ -114,10 +123,10 @@ describe('getBarChartSettings', () => {
const horizontalResult = getBarChartSettings(GraphType.HORIZONTAL_BAR);
const verticalDataGroup = verticalResult.find(
(group) => group.heading === 'Data',
(group) => group.heading.id === CHART_SETTINGS_HEADINGS.DATA.id,
);
const horizontalDataGroup = horizontalResult.find(
(group) => group.heading === 'Data',
(group) => group.heading.id === CHART_SETTINGS_HEADINGS.DATA.id,
);
expect(verticalDataGroup?.items).toEqual(horizontalDataGroup?.items);
@@ -132,10 +141,10 @@ describe('getBarChartSettings', () => {
const horizontalResult = getBarChartSettings(GraphType.HORIZONTAL_BAR);
const verticalStyleGroup = verticalResult.find(
(group) => group.heading === 'Style',
(group) => group.heading.id === CHART_SETTINGS_HEADINGS.STYLE.id,
);
const horizontalStyleGroup = horizontalResult.find(
(group) => group.heading === 'Style',
(group) => group.heading.id === CHART_SETTINGS_HEADINGS.STYLE.id,
);
expect(verticalStyleGroup?.items).toEqual(horizontalStyleGroup?.items);
@@ -1,3 +1,4 @@
import { CHART_SETTINGS_HEADINGS } from '@/command-menu/pages/page-layout/constants/ChartSettingsHeadings';
import { AXIS_NAME_SETTING } from '@/command-menu/pages/page-layout/constants/settings/AxisNameSetting';
import { CHART_DATA_SOURCE_SETTING } from '@/command-menu/pages/page-layout/constants/settings/ChartDataSourceSetting';
import { COLORS_SETTING } from '@/command-menu/pages/page-layout/constants/settings/ColorsSetting';
@@ -48,19 +49,19 @@ export const getBarChartSettings = (
return [
{
heading: 'Data',
heading: CHART_SETTINGS_HEADINGS.DATA,
items: [CHART_DATA_SOURCE_SETTING, FILTER_SETTING],
},
{
heading: 'X axis',
heading: CHART_SETTINGS_HEADINGS.X_AXIS,
items: xAxisItems,
},
{
heading: 'Y axis',
heading: CHART_SETTINGS_HEADINGS.Y_AXIS,
items: yAxisItems,
},
{
heading: 'Style',
heading: CHART_SETTINGS_HEADINGS.STYLE,
items: [
COLORS_SETTING,
AXIS_NAME_SETTING,
@@ -0,0 +1,52 @@
import { getDateGranularityPluralLabel } from '@/command-menu/pages/page-layout/utils/getDateGranularityPluralLabel';
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 { t } from '@lingui/core/macro';
import { type ObjectRecordGroupByDateGranularity } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { GraphType } from '~/generated/graphql';
type GetChartLimitMessageParams = {
graphType: GraphType;
isPrimaryAxisDate: boolean;
primaryAxisDateGranularity:
| ObjectRecordGroupByDateGranularity
| null
| undefined;
};
export const getChartLimitMessage = ({
graphType,
isPrimaryAxisDate,
primaryAxisDateGranularity,
}: GetChartLimitMessageParams): string => {
const maxItems =
graphType === GraphType.LINE
? LINE_CHART_MAXIMUM_NUMBER_OF_DATA_POINTS
: graphType === GraphType.VERTICAL_BAR ||
graphType === GraphType.HORIZONTAL_BAR
? BAR_CHART_MAXIMUM_NUMBER_OF_BARS
: PIE_CHART_MAXIMUM_NUMBER_OF_SLICES;
if (isPrimaryAxisDate && isDefined(primaryAxisDateGranularity)) {
const granularityLabel = getDateGranularityPluralLabel(
primaryAxisDateGranularity,
);
return t`Undisplayed data: max ${maxItems} ${granularityLabel} per chart.`;
}
if (graphType === GraphType.LINE) {
return t`Undisplayed data: max ${maxItems} data points per chart.`;
}
if (
graphType === GraphType.VERTICAL_BAR ||
graphType === GraphType.HORIZONTAL_BAR
) {
return t`Undisplayed data: max ${maxItems} bars per chart.`;
}
return t`Undisplayed data: max ${maxItems} slices per chart.`;
};
@@ -0,0 +1,30 @@
import { t } from '@lingui/core/macro';
import { ObjectRecordGroupByDateGranularity } from 'twenty-shared/types';
import { assertUnreachable } from 'twenty-shared/utils';
export const getDateGranularityPluralLabel = (
granularity: ObjectRecordGroupByDateGranularity,
): string => {
switch (granularity) {
case ObjectRecordGroupByDateGranularity.DAY:
return t`days`;
case ObjectRecordGroupByDateGranularity.WEEK:
return t`weeks`;
case ObjectRecordGroupByDateGranularity.MONTH:
return t`months`;
case ObjectRecordGroupByDateGranularity.QUARTER:
return t`quarters`;
case ObjectRecordGroupByDateGranularity.YEAR:
return t`years`;
case ObjectRecordGroupByDateGranularity.DAY_OF_THE_WEEK:
return t`days`;
case ObjectRecordGroupByDateGranularity.MONTH_OF_THE_YEAR:
return t`months`;
case ObjectRecordGroupByDateGranularity.QUARTER_OF_THE_YEAR:
return t`quarters`;
case ObjectRecordGroupByDateGranularity.NONE:
return t`items`;
default:
assertUnreachable(granularity);
}
};