[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:
@@ -350,6 +350,7 @@ export type BarChartConfiguration = {
|
||||
firstDayOfTheWeek?: Maybe<Scalars['Int']>;
|
||||
graphType: GraphType;
|
||||
groupMode?: Maybe<BarChartGroupMode>;
|
||||
isCumulative?: Maybe<Scalars['Boolean']>;
|
||||
omitNullValues?: Maybe<Scalars['Boolean']>;
|
||||
primaryAxisDateGranularity?: Maybe<ObjectRecordGroupByDateGranularity>;
|
||||
primaryAxisGroupByFieldMetadataId: Scalars['UUID'];
|
||||
@@ -1699,6 +1700,7 @@ export type LineChartConfiguration = {
|
||||
filter?: Maybe<Scalars['JSON']>;
|
||||
firstDayOfTheWeek?: Maybe<Scalars['Int']>;
|
||||
graphType: GraphType;
|
||||
isCumulative?: Maybe<Scalars['Boolean']>;
|
||||
isStacked?: Maybe<Scalars['Boolean']>;
|
||||
omitNullValues?: Maybe<Scalars['Boolean']>;
|
||||
primaryAxisDateGranularity?: Maybe<ObjectRecordGroupByDateGranularity>;
|
||||
|
||||
@@ -350,6 +350,7 @@ export type BarChartConfiguration = {
|
||||
firstDayOfTheWeek?: Maybe<Scalars['Int']>;
|
||||
graphType: GraphType;
|
||||
groupMode?: Maybe<BarChartGroupMode>;
|
||||
isCumulative?: Maybe<Scalars['Boolean']>;
|
||||
omitNullValues?: Maybe<Scalars['Boolean']>;
|
||||
primaryAxisDateGranularity?: Maybe<ObjectRecordGroupByDateGranularity>;
|
||||
primaryAxisGroupByFieldMetadataId: Scalars['UUID'];
|
||||
@@ -1675,6 +1676,7 @@ export type LineChartConfiguration = {
|
||||
filter?: Maybe<Scalars['JSON']>;
|
||||
firstDayOfTheWeek?: Maybe<Scalars['Int']>;
|
||||
graphType: GraphType;
|
||||
isCumulative?: Maybe<Scalars['Boolean']>;
|
||||
isStacked?: Maybe<Scalars['Boolean']>;
|
||||
omitNullValues?: Maybe<Scalars['Boolean']>;
|
||||
primaryAxisDateGranularity?: Maybe<ObjectRecordGroupByDateGranularity>;
|
||||
@@ -4788,7 +4790,7 @@ export type SearchQueryVariables = Exact<{
|
||||
|
||||
export type SearchQuery = { __typename?: 'Query', search: { __typename?: 'SearchResultConnection', edges: Array<{ __typename?: 'SearchResultEdge', cursor: string, node: { __typename?: 'SearchRecord', recordId: any, objectNameSingular: string, label: string, imageUrl?: string | null, tsRankCD: number, tsRank: number } }>, pageInfo: { __typename?: 'SearchResultPageInfo', hasNextPage: boolean, endCursor?: string | null } } };
|
||||
|
||||
export type PageLayoutWidgetFragmentFragment = { __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration?: { __typename?: 'AggregateChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'BarChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'GaugeChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', url?: string | null } | { __typename?: 'LineChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, isStacked?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'PieChartConfiguration', graphType: GraphType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, displayDataLabel?: boolean | null, showCenterMetric?: boolean | null, displayLegend?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | null };
|
||||
export type PageLayoutWidgetFragmentFragment = { __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration?: { __typename?: 'AggregateChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, prefix?: string | null, suffix?: string | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'BarChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, isCumulative?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'GaugeChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', url?: string | null } | { __typename?: 'LineChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, isStacked?: boolean | null, isCumulative?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'PieChartConfiguration', graphType: GraphType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, displayDataLabel?: boolean | null, showCenterMetric?: boolean | null, displayLegend?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | null };
|
||||
|
||||
export type UpdatePageLayoutWithTabsAndWidgetsMutationVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
@@ -4796,7 +4798,7 @@ export type UpdatePageLayoutWithTabsAndWidgetsMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdatePageLayoutWithTabsAndWidgetsMutation = { __typename?: 'Mutation', updatePageLayoutWithTabsAndWidgets: { __typename?: 'PageLayout', id: any, name: string, type: PageLayoutType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, tabs?: Array<{ __typename?: 'PageLayoutTab', id: any, title: string, position: number, pageLayoutId: any, createdAt: string, updatedAt: string, widgets?: Array<{ __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration?: { __typename?: 'AggregateChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'BarChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'GaugeChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', url?: string | null } | { __typename?: 'LineChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, isStacked?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'PieChartConfiguration', graphType: GraphType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, displayDataLabel?: boolean | null, showCenterMetric?: boolean | null, displayLegend?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | null }> | null }> | null } };
|
||||
export type UpdatePageLayoutWithTabsAndWidgetsMutation = { __typename?: 'Mutation', updatePageLayoutWithTabsAndWidgets: { __typename?: 'PageLayout', id: any, name: string, type: PageLayoutType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, tabs?: Array<{ __typename?: 'PageLayoutTab', id: any, title: string, position: number, pageLayoutId: any, createdAt: string, updatedAt: string, widgets?: Array<{ __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration?: { __typename?: 'AggregateChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, prefix?: string | null, suffix?: string | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'BarChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, isCumulative?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'GaugeChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', url?: string | null } | { __typename?: 'LineChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, isStacked?: boolean | null, isCumulative?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'PieChartConfiguration', graphType: GraphType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, displayDataLabel?: boolean | null, showCenterMetric?: boolean | null, displayLegend?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | null }> | null }> | null } };
|
||||
|
||||
export type OnDbEventSubscriptionVariables = Exact<{
|
||||
input: OnDbEventInput;
|
||||
@@ -5129,6 +5131,7 @@ export const PageLayoutWidgetFragmentFragmentDoc = gql`
|
||||
description
|
||||
filter
|
||||
groupMode
|
||||
isCumulative
|
||||
timezone
|
||||
firstDayOfTheWeek
|
||||
}
|
||||
@@ -5154,6 +5157,7 @@ export const PageLayoutWidgetFragmentFragmentDoc = gql`
|
||||
description
|
||||
filter
|
||||
isStacked
|
||||
isCumulative
|
||||
timezone
|
||||
firstDayOfTheWeek
|
||||
}
|
||||
@@ -5183,6 +5187,8 @@ export const PageLayoutWidgetFragmentFragmentDoc = gql`
|
||||
format
|
||||
description
|
||||
filter
|
||||
prefix
|
||||
suffix
|
||||
timezone
|
||||
firstDayOfTheWeek
|
||||
}
|
||||
|
||||
+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;
|
||||
|
||||
+4
@@ -38,6 +38,7 @@ export const PAGE_LAYOUT_WIDGET_FRAGMENT = gql`
|
||||
description
|
||||
filter
|
||||
groupMode
|
||||
isCumulative
|
||||
timezone
|
||||
firstDayOfTheWeek
|
||||
}
|
||||
@@ -63,6 +64,7 @@ export const PAGE_LAYOUT_WIDGET_FRAGMENT = gql`
|
||||
description
|
||||
filter
|
||||
isStacked
|
||||
isCumulative
|
||||
timezone
|
||||
firstDayOfTheWeek
|
||||
}
|
||||
@@ -92,6 +94,8 @@ export const PAGE_LAYOUT_WIDGET_FRAGMENT = gql`
|
||||
format
|
||||
description
|
||||
filter
|
||||
prefix
|
||||
suffix
|
||||
timezone
|
||||
firstDayOfTheWeek
|
||||
}
|
||||
|
||||
+6
-2
@@ -121,8 +121,12 @@ export const transformGroupByDataToBarChartData = ({
|
||||
const filteredResults = filterGroupByResults({
|
||||
rawResults,
|
||||
filterOptions: {
|
||||
rangeMin: configuration.rangeMin ?? undefined,
|
||||
rangeMax: configuration.rangeMax ?? undefined,
|
||||
rangeMin: configuration.isCumulative
|
||||
? undefined
|
||||
: (configuration.rangeMin ?? undefined),
|
||||
rangeMax: configuration.isCumulative
|
||||
? undefined
|
||||
: (configuration.rangeMax ?? undefined),
|
||||
omitNullValues: configuration.omitNullValues ?? false,
|
||||
},
|
||||
aggregateField,
|
||||
|
||||
+11
-1
@@ -7,6 +7,7 @@ import { type BarChartSeries } from '@/page-layout/widgets/graph/graphWidgetBarC
|
||||
import { type GraphColor } from '@/page-layout/widgets/graph/types/GraphColor';
|
||||
import { type GroupByRawResult } from '@/page-layout/widgets/graph/types/GroupByRawResult';
|
||||
import { type RawDimensionValue } from '@/page-layout/widgets/graph/types/RawDimensionValue';
|
||||
import { applyCumulativeTransformToBarChartData } from '@/page-layout/widgets/graph/utils/applyCumulativeTransformToBarChartData';
|
||||
import { buildFormattedToRawLookup } from '@/page-layout/widgets/graph/utils/buildFormattedToRawLookup';
|
||||
import { computeAggregateValueFromGroupByResult } from '@/page-layout/widgets/graph/utils/computeAggregateValueFromGroupByResult';
|
||||
import { formatDimensionValue } from '@/page-layout/widgets/graph/utils/formatDimensionValue';
|
||||
@@ -104,8 +105,17 @@ export const transformOneDimensionalGroupByToBarChartData = ({
|
||||
},
|
||||
];
|
||||
|
||||
const finalData = configuration.isCumulative
|
||||
? applyCumulativeTransformToBarChartData({
|
||||
data,
|
||||
aggregateKey: aggregateValueKey,
|
||||
rangeMin: configuration.rangeMin ?? undefined,
|
||||
rangeMax: configuration.rangeMax ?? undefined,
|
||||
})
|
||||
: data;
|
||||
|
||||
return {
|
||||
data,
|
||||
data: finalData,
|
||||
indexBy: indexByKey,
|
||||
keys: [aggregateValueKey],
|
||||
series,
|
||||
|
||||
+12
-2
@@ -7,6 +7,7 @@ import { sortBarChartDataBySecondaryDimensionSum } from '@/page-layout/widgets/g
|
||||
import { type GraphColor } from '@/page-layout/widgets/graph/types/GraphColor';
|
||||
import { type GroupByRawResult } from '@/page-layout/widgets/graph/types/GroupByRawResult';
|
||||
import { type RawDimensionValue } from '@/page-layout/widgets/graph/types/RawDimensionValue';
|
||||
import { applyCumulativeTransformToTwoDimensionalBarChartData } from '@/page-layout/widgets/graph/utils/applyCumulativeTransformToTwoDimensionalBarChartData';
|
||||
import { buildFormattedToRawLookup } from '@/page-layout/widgets/graph/utils/buildFormattedToRawLookup';
|
||||
import { computeAggregateValueFromGroupByResult } from '@/page-layout/widgets/graph/utils/computeAggregateValueFromGroupByResult';
|
||||
import { formatDimensionValue } from '@/page-layout/widgets/graph/utils/formatDimensionValue';
|
||||
@@ -153,7 +154,7 @@ export const transformTwoDimensionalGroupByToBarChartData = ({
|
||||
}));
|
||||
|
||||
const unsortedData = Array.from(dataMap.values());
|
||||
const data = isDefined(configuration.primaryAxisOrderBy)
|
||||
const sortedData = isDefined(configuration.primaryAxisOrderBy)
|
||||
? sortBarChartDataBySecondaryDimensionSum({
|
||||
data: unsortedData,
|
||||
keys,
|
||||
@@ -161,8 +162,17 @@ export const transformTwoDimensionalGroupByToBarChartData = ({
|
||||
})
|
||||
: unsortedData;
|
||||
|
||||
const finalData = configuration.isCumulative
|
||||
? applyCumulativeTransformToTwoDimensionalBarChartData({
|
||||
data: sortedData,
|
||||
keys,
|
||||
rangeMin: configuration.rangeMin ?? undefined,
|
||||
rangeMax: configuration.rangeMax ?? undefined,
|
||||
})
|
||||
: sortedData;
|
||||
|
||||
return {
|
||||
data,
|
||||
data: finalData,
|
||||
indexBy: indexByKey,
|
||||
keys,
|
||||
series,
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`applyCumulativeTransformToBarChartData should handle basic accumulation 1`] = `
|
||||
[
|
||||
{
|
||||
"id": "a",
|
||||
"value": 10,
|
||||
},
|
||||
{
|
||||
"id": "b",
|
||||
"value": 30,
|
||||
},
|
||||
{
|
||||
"id": "c",
|
||||
"value": 60,
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`applyCumulativeTransformToBarChartData should handle empty data 1`] = `[]`;
|
||||
|
||||
exports[`applyCumulativeTransformToBarChartData should handle filter above rangeMax 1`] = `
|
||||
[
|
||||
{
|
||||
"id": "a",
|
||||
"value": 10,
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`applyCumulativeTransformToBarChartData should handle filter below rangeMin 1`] = `
|
||||
[
|
||||
{
|
||||
"id": "b",
|
||||
"value": 20,
|
||||
},
|
||||
{
|
||||
"id": "c",
|
||||
"value": 30,
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`applyCumulativeTransformToBarChartData should handle skip non-numeric values 1`] = `
|
||||
[
|
||||
{
|
||||
"id": "a",
|
||||
"value": 10,
|
||||
},
|
||||
{
|
||||
"id": "b",
|
||||
"value": 10,
|
||||
},
|
||||
{
|
||||
"id": "c",
|
||||
"value": 30,
|
||||
},
|
||||
]
|
||||
`;
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`applyCumulativeTransformToLineChartData should handle basic accumulation 1`] = `
|
||||
[
|
||||
{
|
||||
"x": "Jan",
|
||||
"y": 10,
|
||||
},
|
||||
{
|
||||
"x": "Feb",
|
||||
"y": 30,
|
||||
},
|
||||
{
|
||||
"x": "Mar",
|
||||
"y": 60,
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`applyCumulativeTransformToLineChartData should handle empty data 1`] = `[]`;
|
||||
|
||||
exports[`applyCumulativeTransformToLineChartData should handle filter above rangeMax 1`] = `
|
||||
[
|
||||
{
|
||||
"x": "a",
|
||||
"y": 10,
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`applyCumulativeTransformToLineChartData should handle filter below rangeMin 1`] = `
|
||||
[
|
||||
{
|
||||
"x": "b",
|
||||
"y": 20,
|
||||
},
|
||||
{
|
||||
"x": "c",
|
||||
"y": 30,
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`applyCumulativeTransformToLineChartData should handle skip null y values 1`] = `
|
||||
[
|
||||
{
|
||||
"x": "a",
|
||||
"y": 10,
|
||||
},
|
||||
{
|
||||
"x": "b",
|
||||
"y": 10,
|
||||
},
|
||||
{
|
||||
"x": "c",
|
||||
"y": 30,
|
||||
},
|
||||
]
|
||||
`;
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`applyCumulativeTransformToTwoDimensionalBarChartData should handle accumulate each key independently 1`] = `
|
||||
[
|
||||
{
|
||||
"id": "Jan",
|
||||
"revenue": 100,
|
||||
"sales": 10,
|
||||
},
|
||||
{
|
||||
"id": "Feb",
|
||||
"revenue": 300,
|
||||
"sales": 30,
|
||||
},
|
||||
{
|
||||
"id": "Mar",
|
||||
"revenue": 600,
|
||||
"sales": 60,
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`applyCumulativeTransformToTwoDimensionalBarChartData should handle empty data 1`] = `[]`;
|
||||
|
||||
exports[`applyCumulativeTransformToTwoDimensionalBarChartData should handle filter above rangeMax based on total sum 1`] = `
|
||||
[
|
||||
{
|
||||
"id": "a",
|
||||
"x": 10,
|
||||
"y": 10,
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`applyCumulativeTransformToTwoDimensionalBarChartData should handle filter below rangeMin based on total sum 1`] = `
|
||||
[
|
||||
{
|
||||
"id": "c",
|
||||
"x": 30,
|
||||
"y": 30,
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`applyCumulativeTransformToTwoDimensionalBarChartData should handle skip non-numeric values 1`] = `
|
||||
[
|
||||
{
|
||||
"id": "a",
|
||||
"x": 10,
|
||||
},
|
||||
{
|
||||
"id": "b",
|
||||
"x": 10,
|
||||
},
|
||||
{
|
||||
"id": "c",
|
||||
"x": 30,
|
||||
},
|
||||
]
|
||||
`;
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
import { applyCumulativeTransformToBarChartData } from '../applyCumulativeTransformToBarChartData';
|
||||
|
||||
describe('applyCumulativeTransformToBarChartData', () => {
|
||||
const testCases = [
|
||||
{
|
||||
name: 'basic accumulation',
|
||||
data: [
|
||||
{ id: 'a', value: 10 },
|
||||
{ id: 'b', value: 20 },
|
||||
{ id: 'c', value: 30 },
|
||||
],
|
||||
aggregateKey: 'value',
|
||||
},
|
||||
{
|
||||
name: 'filter below rangeMin',
|
||||
data: [
|
||||
{ id: 'a', value: 10 },
|
||||
{ id: 'b', value: 10 },
|
||||
{ id: 'c', value: 10 },
|
||||
],
|
||||
aggregateKey: 'value',
|
||||
rangeMin: 15,
|
||||
},
|
||||
{
|
||||
name: 'filter above rangeMax',
|
||||
data: [
|
||||
{ id: 'a', value: 10 },
|
||||
{ id: 'b', value: 20 },
|
||||
{ id: 'c', value: 30 },
|
||||
],
|
||||
aggregateKey: 'value',
|
||||
rangeMax: 25,
|
||||
},
|
||||
{
|
||||
name: 'empty data',
|
||||
data: [],
|
||||
aggregateKey: 'value',
|
||||
},
|
||||
{
|
||||
name: 'skip non-numeric values',
|
||||
data: [
|
||||
{ id: 'a', value: 10 },
|
||||
{ id: 'b', value: 'not a number' },
|
||||
{ id: 'c', value: 20 },
|
||||
],
|
||||
aggregateKey: 'value',
|
||||
},
|
||||
];
|
||||
|
||||
it.each(testCases)(
|
||||
'should handle $name',
|
||||
({ data, aggregateKey, rangeMin, rangeMax }) => {
|
||||
const result = applyCumulativeTransformToBarChartData({
|
||||
data,
|
||||
aggregateKey,
|
||||
rangeMin,
|
||||
rangeMax,
|
||||
});
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
},
|
||||
);
|
||||
});
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import { applyCumulativeTransformToLineChartData } from '../applyCumulativeTransformToLineChartData';
|
||||
|
||||
describe('applyCumulativeTransformToLineChartData', () => {
|
||||
const testCases = [
|
||||
{
|
||||
name: 'basic accumulation',
|
||||
data: [
|
||||
{ x: 'Jan', y: 10 },
|
||||
{ x: 'Feb', y: 20 },
|
||||
{ x: 'Mar', y: 30 },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'filter below rangeMin',
|
||||
data: [
|
||||
{ x: 'a', y: 10 },
|
||||
{ x: 'b', y: 10 },
|
||||
{ x: 'c', y: 10 },
|
||||
],
|
||||
rangeMin: 15,
|
||||
},
|
||||
{
|
||||
name: 'filter above rangeMax',
|
||||
data: [
|
||||
{ x: 'a', y: 10 },
|
||||
{ x: 'b', y: 20 },
|
||||
{ x: 'c', y: 30 },
|
||||
],
|
||||
rangeMax: 25,
|
||||
},
|
||||
{
|
||||
name: 'empty data',
|
||||
data: [],
|
||||
},
|
||||
{
|
||||
name: 'skip null y values',
|
||||
data: [
|
||||
{ x: 'a', y: 10 },
|
||||
{ x: 'b', y: null },
|
||||
{ x: 'c', y: 20 },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
it.each(testCases)('should handle $name', ({ data, rangeMin, rangeMax }) => {
|
||||
const result = applyCumulativeTransformToLineChartData({
|
||||
data,
|
||||
rangeMin,
|
||||
rangeMax,
|
||||
});
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
import { applyCumulativeTransformToTwoDimensionalBarChartData } from '../applyCumulativeTransformToTwoDimensionalBarChartData';
|
||||
|
||||
describe('applyCumulativeTransformToTwoDimensionalBarChartData', () => {
|
||||
const testCases = [
|
||||
{
|
||||
name: 'accumulate each key independently',
|
||||
data: [
|
||||
{ id: 'Jan', sales: 10, revenue: 100 },
|
||||
{ id: 'Feb', sales: 20, revenue: 200 },
|
||||
{ id: 'Mar', sales: 30, revenue: 300 },
|
||||
],
|
||||
keys: ['sales', 'revenue'],
|
||||
},
|
||||
{
|
||||
name: 'filter below rangeMin based on total sum',
|
||||
data: [
|
||||
{ id: 'a', x: 10, y: 10 },
|
||||
{ id: 'b', x: 10, y: 10 },
|
||||
{ id: 'c', x: 10, y: 10 },
|
||||
],
|
||||
keys: ['x', 'y'],
|
||||
rangeMin: 50,
|
||||
},
|
||||
{
|
||||
name: 'filter above rangeMax based on total sum',
|
||||
data: [
|
||||
{ id: 'a', x: 10, y: 10 },
|
||||
{ id: 'b', x: 20, y: 20 },
|
||||
{ id: 'c', x: 30, y: 30 },
|
||||
],
|
||||
keys: ['x', 'y'],
|
||||
rangeMax: 50,
|
||||
},
|
||||
{
|
||||
name: 'empty data',
|
||||
data: [],
|
||||
keys: ['x', 'y'],
|
||||
},
|
||||
{
|
||||
name: 'skip non-numeric values',
|
||||
data: [
|
||||
{ id: 'a', x: 10 },
|
||||
{ id: 'b', x: 'not a number' },
|
||||
{ id: 'c', x: 20 },
|
||||
],
|
||||
keys: ['x'],
|
||||
},
|
||||
];
|
||||
|
||||
it.each(testCases)(
|
||||
'should handle $name',
|
||||
({ data, keys, rangeMin, rangeMax }) => {
|
||||
const result = applyCumulativeTransformToTwoDimensionalBarChartData({
|
||||
data,
|
||||
keys,
|
||||
rangeMin,
|
||||
rangeMax,
|
||||
});
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
},
|
||||
);
|
||||
});
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
import { type BarDatum } from '@nivo/bar';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type ApplyCumulativeTransformToBarChartDataOptions = {
|
||||
data: BarDatum[];
|
||||
aggregateKey: string;
|
||||
rangeMin?: number;
|
||||
rangeMax?: number;
|
||||
};
|
||||
|
||||
export const applyCumulativeTransformToBarChartData = ({
|
||||
data,
|
||||
aggregateKey,
|
||||
rangeMin,
|
||||
rangeMax,
|
||||
}: ApplyCumulativeTransformToBarChartDataOptions): BarDatum[] => {
|
||||
const { result } = data.reduce<{ result: BarDatum[]; runningTotal: number }>(
|
||||
(accumulator, datum) => {
|
||||
const value = datum[aggregateKey];
|
||||
|
||||
if (typeof value === 'number') {
|
||||
accumulator.runningTotal += value;
|
||||
}
|
||||
|
||||
const cumulativeValue = accumulator.runningTotal;
|
||||
|
||||
const isOutOfRange =
|
||||
(isDefined(rangeMin) && cumulativeValue < rangeMin) ||
|
||||
(isDefined(rangeMax) && cumulativeValue > rangeMax);
|
||||
|
||||
if (!isOutOfRange) {
|
||||
accumulator.result.push({ ...datum, [aggregateKey]: cumulativeValue });
|
||||
}
|
||||
|
||||
return accumulator;
|
||||
},
|
||||
{ result: [], runningTotal: 0 },
|
||||
);
|
||||
|
||||
return result;
|
||||
};
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
import { type LineChartDataPoint } from '@/page-layout/widgets/graph/graphWidgetLineChart/types/LineChartDataPoint';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type ApplyCumulativeTransformToLineChartDataOptions = {
|
||||
data: LineChartDataPoint[];
|
||||
rangeMin?: number;
|
||||
rangeMax?: number;
|
||||
};
|
||||
|
||||
export const applyCumulativeTransformToLineChartData = ({
|
||||
data,
|
||||
rangeMin,
|
||||
rangeMax,
|
||||
}: ApplyCumulativeTransformToLineChartDataOptions): LineChartDataPoint[] => {
|
||||
const { result } = data.reduce<{
|
||||
result: LineChartDataPoint[];
|
||||
runningTotal: number;
|
||||
}>(
|
||||
(accumulator, point) => {
|
||||
if (point.y !== null) {
|
||||
accumulator.runningTotal += point.y;
|
||||
}
|
||||
|
||||
const cumulativeValue = accumulator.runningTotal;
|
||||
|
||||
const isOutOfRange =
|
||||
(isDefined(rangeMin) && cumulativeValue < rangeMin) ||
|
||||
(isDefined(rangeMax) && cumulativeValue > rangeMax);
|
||||
|
||||
if (!isOutOfRange) {
|
||||
accumulator.result.push({ ...point, y: cumulativeValue });
|
||||
}
|
||||
|
||||
return accumulator;
|
||||
},
|
||||
{ result: [], runningTotal: 0 },
|
||||
);
|
||||
|
||||
return result;
|
||||
};
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
import { type BarDatum } from '@nivo/bar';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type ApplyCumulativeTransformToTwoDimensionalBarChartDataOptions = {
|
||||
data: BarDatum[];
|
||||
keys: string[];
|
||||
rangeMin?: number;
|
||||
rangeMax?: number;
|
||||
};
|
||||
|
||||
export const applyCumulativeTransformToTwoDimensionalBarChartData = ({
|
||||
data,
|
||||
keys,
|
||||
rangeMin,
|
||||
rangeMax,
|
||||
}: ApplyCumulativeTransformToTwoDimensionalBarChartDataOptions): BarDatum[] => {
|
||||
const { result } = data.reduce<{
|
||||
result: BarDatum[];
|
||||
runningTotals: Record<string, number>;
|
||||
}>(
|
||||
(accumulator, datum) => {
|
||||
const newDatum = { ...datum };
|
||||
|
||||
for (const key of keys) {
|
||||
const value = datum[key];
|
||||
|
||||
if (typeof value === 'number') {
|
||||
accumulator.runningTotals[key] += value;
|
||||
}
|
||||
|
||||
newDatum[key] = accumulator.runningTotals[key];
|
||||
}
|
||||
|
||||
const totalValue = keys.reduce((sum, key) => {
|
||||
const value = newDatum[key];
|
||||
return sum + (typeof value === 'number' ? value : 0);
|
||||
}, 0);
|
||||
|
||||
const isOutOfRange =
|
||||
(isDefined(rangeMin) && totalValue < rangeMin) ||
|
||||
(isDefined(rangeMax) && totalValue > rangeMax);
|
||||
|
||||
if (!isOutOfRange) {
|
||||
accumulator.result.push(newDatum);
|
||||
}
|
||||
|
||||
return accumulator;
|
||||
},
|
||||
{
|
||||
result: [],
|
||||
runningTotals: Object.fromEntries(keys.map((key) => [key, 0])),
|
||||
},
|
||||
);
|
||||
|
||||
return result;
|
||||
};
|
||||
+6
-2
@@ -89,8 +89,12 @@ export const transformGroupByDataToLineChartData = ({
|
||||
const filteredResults = filterGroupByResults({
|
||||
rawResults,
|
||||
filterOptions: {
|
||||
rangeMin: configuration.rangeMin ?? undefined,
|
||||
rangeMax: configuration.rangeMax ?? undefined,
|
||||
rangeMin: configuration.isCumulative
|
||||
? undefined
|
||||
: (configuration.rangeMin ?? undefined),
|
||||
rangeMax: configuration.isCumulative
|
||||
? undefined
|
||||
: (configuration.rangeMax ?? undefined),
|
||||
omitNullValues: configuration.omitNullValues ?? false,
|
||||
},
|
||||
aggregateField,
|
||||
|
||||
+11
-2
@@ -8,10 +8,11 @@ import { type LineChartSeries } from '@/page-layout/widgets/graph/graphWidgetLin
|
||||
import { type GraphColor } from '@/page-layout/widgets/graph/types/GraphColor';
|
||||
import { type GroupByRawResult } from '@/page-layout/widgets/graph/types/GroupByRawResult';
|
||||
import { type RawDimensionValue } from '@/page-layout/widgets/graph/types/RawDimensionValue';
|
||||
import { applyCumulativeTransformToLineChartData } from '@/page-layout/widgets/graph/utils/applyCumulativeTransformToLineChartData';
|
||||
import { buildFormattedToRawLookup } from '@/page-layout/widgets/graph/utils/buildFormattedToRawLookup';
|
||||
import { formatPrimaryDimensionValues } from '@/page-layout/widgets/graph/utils/formatPrimaryDimensionValues';
|
||||
import { computeAggregateValueFromGroupByResult } from '@/page-layout/widgets/graph/utils/computeAggregateValueFromGroupByResult';
|
||||
import { formatDimensionValue } from '@/page-layout/widgets/graph/utils/formatDimensionValue';
|
||||
import { formatPrimaryDimensionValues } from '@/page-layout/widgets/graph/utils/formatPrimaryDimensionValues';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type LineChartConfiguration } from '~/generated/graphql';
|
||||
|
||||
@@ -91,12 +92,20 @@ export const transformOneDimensionalGroupByToLineChartData = ({
|
||||
})
|
||||
.filter((point) => isDefined(point));
|
||||
|
||||
const transformedData = configuration.isCumulative
|
||||
? applyCumulativeTransformToLineChartData({
|
||||
data,
|
||||
rangeMin: configuration.rangeMin ?? undefined,
|
||||
rangeMax: configuration.rangeMax ?? undefined,
|
||||
})
|
||||
: data;
|
||||
|
||||
const series: LineChartSeries[] = [
|
||||
{
|
||||
id: aggregateField.name,
|
||||
label: aggregateField.label,
|
||||
color: (configuration.color ?? GRAPH_DEFAULT_COLOR) as GraphColor,
|
||||
data,
|
||||
data: transformedData,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
+11
-2
@@ -7,10 +7,11 @@ import { type LineChartSeries } from '@/page-layout/widgets/graph/graphWidgetLin
|
||||
import { type GraphColor } from '@/page-layout/widgets/graph/types/GraphColor';
|
||||
import { type GroupByRawResult } from '@/page-layout/widgets/graph/types/GroupByRawResult';
|
||||
import { type RawDimensionValue } from '@/page-layout/widgets/graph/types/RawDimensionValue';
|
||||
import { applyCumulativeTransformToLineChartData } from '@/page-layout/widgets/graph/utils/applyCumulativeTransformToLineChartData';
|
||||
import { buildFormattedToRawLookup } from '@/page-layout/widgets/graph/utils/buildFormattedToRawLookup';
|
||||
import { formatPrimaryDimensionValues } from '@/page-layout/widgets/graph/utils/formatPrimaryDimensionValues';
|
||||
import { computeAggregateValueFromGroupByResult } from '@/page-layout/widgets/graph/utils/computeAggregateValueFromGroupByResult';
|
||||
import { formatDimensionValue } from '@/page-layout/widgets/graph/utils/formatDimensionValue';
|
||||
import { formatPrimaryDimensionValues } from '@/page-layout/widgets/graph/utils/formatPrimaryDimensionValues';
|
||||
import { sortLineChartSeries } from '@/page-layout/widgets/graph/utils/sortLineChartSeries';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type LineChartConfiguration } from '~/generated/graphql';
|
||||
@@ -117,11 +118,19 @@ export const transformTwoDimensionalGroupByToLineChartData = ({
|
||||
y: xToYMap.get(xValue) ?? 0,
|
||||
}));
|
||||
|
||||
const transformedData = configuration.isCumulative
|
||||
? applyCumulativeTransformToLineChartData({
|
||||
data,
|
||||
rangeMin: configuration.rangeMin ?? undefined,
|
||||
rangeMax: configuration.rangeMax ?? undefined,
|
||||
})
|
||||
: data;
|
||||
|
||||
return {
|
||||
id: seriesKey,
|
||||
label: seriesKey,
|
||||
color: configuration.color as GraphColor,
|
||||
data,
|
||||
data: transformedData,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
+7
@@ -145,6 +145,13 @@ export class BarChartConfigurationDTO {
|
||||
@IsOptional()
|
||||
groupMode?: BarChartGroupMode;
|
||||
|
||||
@Field(() => Boolean, {
|
||||
nullable: true,
|
||||
})
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
isCumulative?: boolean;
|
||||
|
||||
@Field(() => String, { nullable: true, defaultValue: 'UTC' })
|
||||
@IsTimeZone()
|
||||
@IsOptional()
|
||||
|
||||
+7
@@ -146,6 +146,13 @@ export class LineChartConfigurationDTO {
|
||||
@IsOptional()
|
||||
isStacked?: boolean;
|
||||
|
||||
@Field(() => Boolean, {
|
||||
nullable: true,
|
||||
})
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
isCumulative?: boolean;
|
||||
|
||||
@Field(() => String, { nullable: true, defaultValue: 'UTC' })
|
||||
@IsTimeZone()
|
||||
@IsOptional()
|
||||
|
||||
@@ -58,6 +58,7 @@ export {
|
||||
IconCaretLeft,
|
||||
IconCaretRight,
|
||||
IconChartBar,
|
||||
IconChartBarPopular,
|
||||
IconChartCandle,
|
||||
IconChartDots3,
|
||||
IconChartLine,
|
||||
|
||||
@@ -123,6 +123,7 @@ export {
|
||||
IconCaretLeft,
|
||||
IconCaretRight,
|
||||
IconChartBar,
|
||||
IconChartBarPopular,
|
||||
IconChartCandle,
|
||||
IconChartDots3,
|
||||
IconChartLine,
|
||||
|
||||
Reference in New Issue
Block a user