[DASHBOARDS] Add cumulative setting for bar chart and line chart (#16248)
## Description - Add cumulative setting - This setting is only present for dates - It is preserved when switching from a date field to another date field but it is reset when switching to another field type ## Video QA https://github.com/user-attachments/assets/a070bf54-8ef6-4e35-9378-a514fe1c3848 https://github.com/user-attachments/assets/07edfe87-253c-45a5-b582-06f2df9a2dfc
This commit is contained in:
+8
@@ -123,6 +123,8 @@ export const ChartGroupByFieldSelectionDropdownContentBase = <
|
||||
subFieldNameKey,
|
||||
fieldId: fieldMetadataItem.id,
|
||||
subFieldName: null,
|
||||
objectMetadataItem: sourceObjectMetadataItem,
|
||||
objectMetadataItems,
|
||||
}),
|
||||
});
|
||||
closeDropdown();
|
||||
@@ -136,6 +138,8 @@ export const ChartGroupByFieldSelectionDropdownContentBase = <
|
||||
subFieldNameKey,
|
||||
fieldId: null,
|
||||
subFieldName: null,
|
||||
objectMetadataItem: sourceObjectMetadataItem,
|
||||
objectMetadataItems,
|
||||
}),
|
||||
});
|
||||
closeDropdown();
|
||||
@@ -161,6 +165,8 @@ export const ChartGroupByFieldSelectionDropdownContentBase = <
|
||||
subFieldNameKey,
|
||||
fieldId: selectedCompositeField.id,
|
||||
subFieldName,
|
||||
objectMetadataItem: sourceObjectMetadataItem,
|
||||
objectMetadataItems,
|
||||
}),
|
||||
});
|
||||
closeDropdown();
|
||||
@@ -178,6 +184,8 @@ export const ChartGroupByFieldSelectionDropdownContentBase = <
|
||||
subFieldNameKey,
|
||||
fieldId: selectedRelationField.id,
|
||||
subFieldName,
|
||||
objectMetadataItem: sourceObjectMetadataItem,
|
||||
objectMetadataItems,
|
||||
}),
|
||||
});
|
||||
closeDropdown();
|
||||
|
||||
+2
@@ -2,6 +2,7 @@ import { CHART_SETTINGS_HEADINGS } from '@/command-menu/pages/page-layout/consta
|
||||
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';
|
||||
import { CUMULATIVE_SETTING } from '@/command-menu/pages/page-layout/constants/settings/CumulativeSetting';
|
||||
import { DATA_DISPLAY_X_SETTING } from '@/command-menu/pages/page-layout/constants/settings/DataDisplayXSetting';
|
||||
import { DATA_DISPLAY_Y_SETTING } from '@/command-menu/pages/page-layout/constants/settings/DataDisplayYSetting';
|
||||
import { DATA_LABELS_SETTING } from '@/command-menu/pages/page-layout/constants/settings/DataLabelsSetting';
|
||||
@@ -39,6 +40,7 @@ export const LINE_CHART_SETTINGS: ChartSettingsGroup[] = [
|
||||
GROUP_BY_SETTING,
|
||||
DATE_GRANULARITY_Y_SETTING,
|
||||
SORT_BY_GROUP_BY_FIELD_SETTING,
|
||||
CUMULATIVE_SETTING,
|
||||
RANGE_MIN_SETTING,
|
||||
RANGE_MAX_SETTING,
|
||||
],
|
||||
|
||||
+1
@@ -26,4 +26,5 @@ export const CHART_CONFIGURATION_SETTING_LABELS = {
|
||||
SHOW_LEGEND: msg`Legend`,
|
||||
PREFIX: msg`Prefix`,
|
||||
SUFFIX: msg`Suffix`,
|
||||
CUMULATIVE: msg`Cumulative`,
|
||||
};
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
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 { IconChartBarPopular } from 'twenty-ui/display';
|
||||
|
||||
export const CUMULATIVE_SETTING: ChartSettingsItem = {
|
||||
isBoolean: true,
|
||||
Icon: IconChartBarPopular,
|
||||
label: CHART_CONFIGURATION_SETTING_LABELS.CUMULATIVE,
|
||||
id: CHART_CONFIGURATION_SETTING_IDS.CUMULATIVE,
|
||||
};
|
||||
+2
@@ -224,6 +224,8 @@ export const useChartSettingsValues = ({
|
||||
return configuration.__typename === 'LineChartConfiguration'
|
||||
? configuration.isStacked !== false
|
||||
: true;
|
||||
case CHART_CONFIGURATION_SETTING_IDS.CUMULATIVE:
|
||||
return isBarOrLineChart ? (configuration.isCumulative ?? false) : false;
|
||||
case CHART_CONFIGURATION_SETTING_IDS.OMIT_NULL_VALUES:
|
||||
return isBarOrLineChart
|
||||
? (configuration.omitNullValues ?? false)
|
||||
|
||||
+2
@@ -25,6 +25,7 @@ export enum CHART_CONFIGURATION_SETTING_IDS {
|
||||
SHOW_LEGEND = 'SHOW_LEGEND',
|
||||
PREFIX = 'PREFIX',
|
||||
SUFFIX = 'SUFFIX',
|
||||
CUMULATIVE = 'CUMULATIVE',
|
||||
}
|
||||
|
||||
export const CHART_CONFIGURATION_SETTING_TO_CONFIG_KEY_MAP = {
|
||||
@@ -43,4 +44,5 @@ export const CHART_CONFIGURATION_SETTING_TO_CONFIG_KEY_MAP = {
|
||||
[CHART_CONFIGURATION_SETTING_IDS.SHOW_LEGEND]: 'displayLegend',
|
||||
[CHART_CONFIGURATION_SETTING_IDS.PREFIX]: 'prefix',
|
||||
[CHART_CONFIGURATION_SETTING_IDS.SUFFIX]: 'suffix',
|
||||
[CHART_CONFIGURATION_SETTING_IDS.CUMULATIVE]: 'isCumulative',
|
||||
} as const;
|
||||
|
||||
+9
-6
@@ -2,6 +2,7 @@ import { CHART_SETTINGS_HEADINGS } from '@/command-menu/pages/page-layout/consta
|
||||
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';
|
||||
import { CUMULATIVE_SETTING } from '@/command-menu/pages/page-layout/constants/settings/CumulativeSetting';
|
||||
import { DATA_DISPLAY_X_SETTING } from '@/command-menu/pages/page-layout/constants/settings/DataDisplayXSetting';
|
||||
import { DATA_DISPLAY_Y_SETTING } from '@/command-menu/pages/page-layout/constants/settings/DataDisplayYSetting';
|
||||
import { DATA_LABELS_SETTING } from '@/command-menu/pages/page-layout/constants/settings/DataLabelsSetting';
|
||||
@@ -47,15 +48,16 @@ describe('getBarChartSettings', () => {
|
||||
);
|
||||
|
||||
expect(yAxisGroup).toBeDefined();
|
||||
expect(yAxisGroup?.items).toHaveLength(6);
|
||||
expect(yAxisGroup?.items).toHaveLength(7);
|
||||
expect(yAxisGroup?.items[0].id).toBe(DATA_DISPLAY_Y_SETTING.id);
|
||||
expect(yAxisGroup?.items[0].label).toBe(DATA_DISPLAY_Y_SETTING.label);
|
||||
expect(yAxisGroup?.items[0].Icon).toBe(IconAxisY);
|
||||
expect(yAxisGroup?.items[1]).toEqual(GROUP_BY_SETTING);
|
||||
expect(yAxisGroup?.items[2]).toEqual(DATE_GRANULARITY_Y_SETTING);
|
||||
expect(yAxisGroup?.items[3]).toEqual(SORT_BY_GROUP_BY_FIELD_SETTING);
|
||||
expect(yAxisGroup?.items[4]).toEqual(RANGE_MIN_SETTING);
|
||||
expect(yAxisGroup?.items[5]).toEqual(RANGE_MAX_SETTING);
|
||||
expect(yAxisGroup?.items[4]).toEqual(CUMULATIVE_SETTING);
|
||||
expect(yAxisGroup?.items[5]).toEqual(RANGE_MIN_SETTING);
|
||||
expect(yAxisGroup?.items[6]).toEqual(RANGE_MAX_SETTING);
|
||||
});
|
||||
|
||||
it('should have all expected groups in correct order', () => {
|
||||
@@ -78,15 +80,16 @@ describe('getBarChartSettings', () => {
|
||||
);
|
||||
|
||||
expect(xAxisGroup).toBeDefined();
|
||||
expect(xAxisGroup?.items).toHaveLength(6);
|
||||
expect(xAxisGroup?.items).toHaveLength(7);
|
||||
expect(xAxisGroup?.items[0].id).toBe(DATA_DISPLAY_Y_SETTING.id);
|
||||
expect(xAxisGroup?.items[0].label).toBe(DATA_DISPLAY_Y_SETTING.label);
|
||||
expect(xAxisGroup?.items[0].Icon).toBe(IconAxisX);
|
||||
expect(xAxisGroup?.items[1]).toEqual(GROUP_BY_SETTING);
|
||||
expect(xAxisGroup?.items[2]).toEqual(DATE_GRANULARITY_Y_SETTING);
|
||||
expect(xAxisGroup?.items[3]).toEqual(SORT_BY_GROUP_BY_FIELD_SETTING);
|
||||
expect(xAxisGroup?.items[4]).toEqual(RANGE_MIN_SETTING);
|
||||
expect(xAxisGroup?.items[5]).toEqual(RANGE_MAX_SETTING);
|
||||
expect(xAxisGroup?.items[4]).toEqual(CUMULATIVE_SETTING);
|
||||
expect(xAxisGroup?.items[5]).toEqual(RANGE_MIN_SETTING);
|
||||
expect(xAxisGroup?.items[6]).toEqual(RANGE_MAX_SETTING);
|
||||
});
|
||||
|
||||
it('should place PRIMARY axis items under "Y axis" heading', () => {
|
||||
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isFieldOrNestedFieldDateKind } from '../isFieldOrNestedFieldDateKind';
|
||||
|
||||
describe('isFieldOrNestedFieldDateKind', () => {
|
||||
it('returns false when fieldId is null', () => {
|
||||
const result = isFieldOrNestedFieldDateKind({
|
||||
fieldId: null,
|
||||
subFieldName: null,
|
||||
objectMetadataItem: {} as ObjectMetadataItem,
|
||||
});
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true for a DATE type field', () => {
|
||||
const objectMetadataItem = {
|
||||
fields: [{ id: 'date-field-id', type: FieldMetadataType.DATE }],
|
||||
} as ObjectMetadataItem;
|
||||
|
||||
const result = isFieldOrNestedFieldDateKind({
|
||||
fieldId: 'date-field-id',
|
||||
subFieldName: null,
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for a non-date type field', () => {
|
||||
const objectMetadataItem = {
|
||||
fields: [{ id: 'text-field-id', type: FieldMetadataType.TEXT }],
|
||||
} as ObjectMetadataItem;
|
||||
|
||||
const result = isFieldOrNestedFieldDateKind({
|
||||
fieldId: 'text-field-id',
|
||||
subFieldName: null,
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true for a relation subfield that is a date type', () => {
|
||||
const objectMetadataItem = {
|
||||
fields: [
|
||||
{
|
||||
id: 'relation-field-id',
|
||||
type: FieldMetadataType.RELATION,
|
||||
relation: { targetObjectMetadata: { nameSingular: 'company' } },
|
||||
},
|
||||
],
|
||||
} as ObjectMetadataItem;
|
||||
|
||||
const companyObjectMetadataItem = {
|
||||
nameSingular: 'company',
|
||||
fields: [
|
||||
{
|
||||
name: 'createdAt',
|
||||
type: FieldMetadataType.DATE,
|
||||
},
|
||||
],
|
||||
} as ObjectMetadataItem;
|
||||
|
||||
const result = isFieldOrNestedFieldDateKind({
|
||||
fieldId: 'relation-field-id',
|
||||
subFieldName: 'createdAt',
|
||||
objectMetadataItem,
|
||||
objectMetadataItems: [companyObjectMetadataItem],
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
+20
@@ -1,4 +1,6 @@
|
||||
import { type ChartConfiguration } from '@/command-menu/pages/page-layout/types/ChartConfiguration';
|
||||
import { isFieldOrNestedFieldDateKind } from '@/command-menu/pages/page-layout/utils/isFieldOrNestedFieldDateKind';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { ObjectRecordGroupByDateGranularity } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { BarChartGroupMode, GraphOrderBy } from '~/generated/graphql';
|
||||
@@ -9,6 +11,8 @@ type BuildChartGroupByFieldConfigUpdateArgs<T extends ChartConfiguration> = {
|
||||
subFieldNameKey: keyof T;
|
||||
fieldId: string | null;
|
||||
subFieldName: string | null;
|
||||
objectMetadataItem?: ObjectMetadataItem;
|
||||
objectMetadataItems?: ObjectMetadataItem[];
|
||||
};
|
||||
|
||||
export const buildChartGroupByFieldConfigUpdate = <
|
||||
@@ -19,6 +23,8 @@ export const buildChartGroupByFieldConfigUpdate = <
|
||||
subFieldNameKey,
|
||||
fieldId,
|
||||
subFieldName,
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
}: BuildChartGroupByFieldConfigUpdateArgs<T>) => {
|
||||
const isPrimaryAxis =
|
||||
fieldMetadataIdKey === 'primaryAxisGroupByFieldMetadataId';
|
||||
@@ -44,6 +50,19 @@ export const buildChartGroupByFieldConfigUpdate = <
|
||||
? configuration.primaryAxisDateGranularity
|
||||
: null;
|
||||
|
||||
const isNewFieldDateType = isFieldOrNestedFieldDateKind({
|
||||
fieldId,
|
||||
subFieldName,
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
});
|
||||
|
||||
const shouldResetCumulative =
|
||||
isDefined(fieldId) &&
|
||||
isDefined(objectMetadataItem) &&
|
||||
!isNewFieldDateType &&
|
||||
(isBarChart || isLineChart);
|
||||
|
||||
return {
|
||||
...baseConfig,
|
||||
primaryAxisOrderBy: isDefined(fieldId)
|
||||
@@ -52,6 +71,7 @@ export const buildChartGroupByFieldConfigUpdate = <
|
||||
primaryAxisDateGranularity: isDefined(fieldId)
|
||||
? (existingDateGranularity ?? ObjectRecordGroupByDateGranularity.DAY)
|
||||
: null,
|
||||
...(shouldResetCumulative ? { isCumulative: false } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -2,6 +2,7 @@ import { CHART_SETTINGS_HEADINGS } from '@/command-menu/pages/page-layout/consta
|
||||
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';
|
||||
import { CUMULATIVE_SETTING } from '@/command-menu/pages/page-layout/constants/settings/CumulativeSetting';
|
||||
import { DATA_DISPLAY_X_SETTING } from '@/command-menu/pages/page-layout/constants/settings/DataDisplayXSetting';
|
||||
import { DATA_DISPLAY_Y_SETTING } from '@/command-menu/pages/page-layout/constants/settings/DataDisplayYSetting';
|
||||
import { DATA_LABELS_SETTING } from '@/command-menu/pages/page-layout/constants/settings/DataLabelsSetting';
|
||||
@@ -40,6 +41,7 @@ export const getBarChartSettings = (
|
||||
GROUP_BY_SETTING,
|
||||
DATE_GRANULARITY_Y_SETTING,
|
||||
SORT_BY_GROUP_BY_FIELD_SETTING,
|
||||
CUMULATIVE_SETTING,
|
||||
RANGE_MIN_SETTING,
|
||||
RANGE_MAX_SETTING,
|
||||
];
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { isFieldRelation } from '@/object-record/record-field/ui/types/guards/isFieldRelation';
|
||||
import { isNestedFieldDateType } from '@/page-layout/widgets/graph/utils/isNestedFieldDateType';
|
||||
import { isDefined, isFieldMetadataDateKind } from 'twenty-shared/utils';
|
||||
|
||||
export const isFieldOrNestedFieldDateKind = ({
|
||||
fieldId,
|
||||
subFieldName,
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
}: {
|
||||
fieldId: string | null;
|
||||
subFieldName: string | null;
|
||||
objectMetadataItem?: ObjectMetadataItem;
|
||||
objectMetadataItems?: ObjectMetadataItem[];
|
||||
}): boolean => {
|
||||
if (!isDefined(fieldId) || !isDefined(objectMetadataItem)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const field = objectMetadataItem.fields.find(
|
||||
(fieldMetadataItem) => fieldMetadataItem.id === fieldId,
|
||||
);
|
||||
|
||||
if (!isDefined(field)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isFieldRelation(field) && isDefined(subFieldName)) {
|
||||
return isNestedFieldDateType(
|
||||
field,
|
||||
subFieldName,
|
||||
objectMetadataItems ?? [],
|
||||
);
|
||||
}
|
||||
|
||||
return isFieldMetadataDateKind(field.type);
|
||||
};
|
||||
+15
@@ -90,6 +90,21 @@ export const shouldHideChartSetting = (
|
||||
}
|
||||
}
|
||||
|
||||
if (item.id === CHART_CONFIGURATION_SETTING_IDS.CUMULATIVE) {
|
||||
const isBarOrLineChart =
|
||||
configuration.__typename === 'BarChartConfiguration' ||
|
||||
configuration.__typename === 'LineChartConfiguration';
|
||||
|
||||
if (isBarOrLineChart) {
|
||||
return shouldHideDateGranularityBasedOnFieldType(
|
||||
configuration.primaryAxisGroupByFieldMetadataId,
|
||||
configuration.primaryAxisGroupBySubFieldName,
|
||||
objectMetadataItem,
|
||||
objectMetadataItems ?? [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (item.id === CHART_CONFIGURATION_SETTING_IDS.SHOW_LEGEND) {
|
||||
if (configuration.__typename === 'PieChartConfiguration') {
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user