From 477fbc08656cbcec6134e4b152abeab51c297c71 Mon Sep 17 00:00:00 2001
From: nitin <142569587+ehconitin@users.noreply.github.com>
Date: Wed, 18 Feb 2026 16:05:08 +0530
Subject: [PATCH] fixes: loosen up front validation, add
resolveEntityRelationUniversalIdentifiers to update and restore (#18015)
closes https://github.com/twentyhq/private-issues/issues/419
---
.../types/BarLineChartConvertibleFields.ts | 1 +
.../types/PieChartConvertibleFields.ts | 1 +
...vertBarOrLineChartConfigToPieChart.test.ts | 7 +
...vertPieChartConfigToBarOrLineChart.test.ts | 6 +
.../convertBarOrLineChartConfigToPieChart.ts | 1 +
.../convertPieChartConfigToBarOrLineChart.ts | 1 +
.../widgets/components/WidgetRenderer.tsx | 5 +
.../widgets/graph/components/GraphWidget.tsx | 23 +-
.../graph/components/GraphWidgetRenderer.tsx | 13 +-
...rtConfigurationFieldsValidForQuery.test.ts | 213 ------------------
...reChartConfigurationFieldsValidForQuery.ts | 119 ----------
.../utils/hasMinimalRequiredConfigForGraph.ts | 35 +++
.../mock-data/widget-configurations.ts | 3 +
.../services/page-layout-update.service.ts | 33 ++-
14 files changed, 97 insertions(+), 364 deletions(-)
delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/areChartConfigurationFieldsValidForQuery.test.ts
delete mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/utils/areChartConfigurationFieldsValidForQuery.ts
create mode 100644 packages/twenty-front/src/modules/page-layout/widgets/graph/utils/hasMinimalRequiredConfigForGraph.ts
diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/BarLineChartConvertibleFields.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/BarLineChartConvertibleFields.ts
index 2d6d93e7fe..c27e7c26bf 100644
--- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/BarLineChartConvertibleFields.ts
+++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/BarLineChartConvertibleFields.ts
@@ -4,6 +4,7 @@ import {
} from '~/generated-metadata/graphql';
export type BarLineChartConvertibleFields = {
+ aggregateFieldMetadataId?: string;
primaryAxisGroupByFieldMetadataId?: string;
primaryAxisGroupBySubFieldName?: string | null;
primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null;
diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/PieChartConvertibleFields.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/PieChartConvertibleFields.ts
index 9196403d00..77c079db2d 100644
--- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/PieChartConvertibleFields.ts
+++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/types/PieChartConvertibleFields.ts
@@ -4,6 +4,7 @@ import {
} from '~/generated-metadata/graphql';
export type PieChartConvertibleFields = {
+ aggregateFieldMetadataId?: string;
groupByFieldMetadataId?: string;
groupBySubFieldName?: string | null;
dateGranularity?: ObjectRecordGroupByDateGranularity | null;
diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/__tests__/convertBarOrLineChartConfigToPieChart.test.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/__tests__/convertBarOrLineChartConfigToPieChart.test.ts
index d13967a9eb..bf5adb294d 100644
--- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/__tests__/convertBarOrLineChartConfigToPieChart.test.ts
+++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/__tests__/convertBarOrLineChartConfigToPieChart.test.ts
@@ -19,6 +19,9 @@ describe('convertBarOrLineChartConfigToPieChart', () => {
TEST_BAR_CHART_CONFIGURATION.primaryAxisGroupBySubFieldName,
dateGranularity: TEST_BAR_CHART_CONFIGURATION.primaryAxisDateGranularity,
orderBy: TEST_BAR_CHART_CONFIGURATION.primaryAxisOrderBy,
+ splitMultiValueFields: TEST_BAR_CHART_CONFIGURATION.splitMultiValueFields,
+ aggregateFieldMetadataId:
+ TEST_BAR_CHART_CONFIGURATION.aggregateFieldMetadataId,
});
});
@@ -34,6 +37,10 @@ describe('convertBarOrLineChartConfigToPieChart', () => {
TEST_LINE_CHART_CONFIGURATION.primaryAxisGroupBySubFieldName,
dateGranularity: TEST_LINE_CHART_CONFIGURATION.primaryAxisDateGranularity,
orderBy: TEST_LINE_CHART_CONFIGURATION.primaryAxisOrderBy,
+ splitMultiValueFields:
+ TEST_LINE_CHART_CONFIGURATION.splitMultiValueFields,
+ aggregateFieldMetadataId:
+ TEST_LINE_CHART_CONFIGURATION.aggregateFieldMetadataId,
});
});
diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/__tests__/convertPieChartConfigToBarOrLineChart.test.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/__tests__/convertPieChartConfigToBarOrLineChart.test.ts
index 66714158c3..8c9a6c3340 100644
--- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/__tests__/convertPieChartConfigToBarOrLineChart.test.ts
+++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/__tests__/convertPieChartConfigToBarOrLineChart.test.ts
@@ -12,12 +12,15 @@ describe('convertPieChartConfigToBarOrLineChart', () => {
);
expect(result).toEqual({
+ aggregateFieldMetadataId:
+ TEST_PIE_CHART_CONFIGURATION.aggregateFieldMetadataId,
primaryAxisGroupByFieldMetadataId:
TEST_PIE_CHART_CONFIGURATION.groupByFieldMetadataId,
primaryAxisGroupBySubFieldName:
TEST_PIE_CHART_CONFIGURATION.groupBySubFieldName,
primaryAxisDateGranularity: TEST_PIE_CHART_CONFIGURATION.dateGranularity,
primaryAxisOrderBy: TEST_PIE_CHART_CONFIGURATION.orderBy,
+ splitMultiValueFields: TEST_PIE_CHART_CONFIGURATION.splitMultiValueFields,
});
});
@@ -33,10 +36,13 @@ describe('convertPieChartConfigToBarOrLineChart', () => {
const result = convertPieChartConfigToBarOrLineChart(minimalPieConfig);
expect(result).toEqual({
+ aggregateFieldMetadataId:
+ TEST_PIE_CHART_CONFIGURATION.aggregateFieldMetadataId,
primaryAxisGroupByFieldMetadataId: undefined,
primaryAxisGroupBySubFieldName: null,
primaryAxisDateGranularity: null,
primaryAxisOrderBy: null,
+ splitMultiValueFields: TEST_PIE_CHART_CONFIGURATION.splitMultiValueFields,
});
});
diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/convertBarOrLineChartConfigToPieChart.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/convertBarOrLineChartConfigToPieChart.ts
index f78c3d7e89..0b7e76eb08 100644
--- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/convertBarOrLineChartConfigToPieChart.ts
+++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/convertBarOrLineChartConfigToPieChart.ts
@@ -15,6 +15,7 @@ export const convertBarOrLineChartConfigToPieChart = (
}
return {
+ aggregateFieldMetadataId: configuration.aggregateFieldMetadataId,
groupByFieldMetadataId: configuration.primaryAxisGroupByFieldMetadataId,
groupBySubFieldName: configuration.primaryAxisGroupBySubFieldName,
dateGranularity: configuration.primaryAxisDateGranularity,
diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/convertPieChartConfigToBarOrLineChart.ts b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/convertPieChartConfigToBarOrLineChart.ts
index 6b006ca9ee..deac52765b 100644
--- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/convertPieChartConfigToBarOrLineChart.ts
+++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/utils/convertPieChartConfigToBarOrLineChart.ts
@@ -10,6 +10,7 @@ export const convertPieChartConfigToBarOrLineChart = (
}
return {
+ aggregateFieldMetadataId: configuration.aggregateFieldMetadataId,
primaryAxisGroupByFieldMetadataId: configuration.groupByFieldMetadataId,
primaryAxisGroupBySubFieldName: configuration.groupBySubFieldName,
primaryAxisDateGranularity: configuration.dateGranularity,
diff --git a/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetRenderer.tsx b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetRenderer.tsx
index 7cbe823e26..574aea62e1 100644
--- a/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetRenderer.tsx
+++ b/packages/twenty-front/src/modules/page-layout/widgets/components/WidgetRenderer.tsx
@@ -179,6 +179,11 @@ export const WidgetRenderer = ({ widget }: WidgetRendererProps) => {
{hasAccess ? (
diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidget.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidget.tsx
index f75dc84fbe..dd8d8334c2 100644
--- a/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidget.tsx
+++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidget.tsx
@@ -1,14 +1,10 @@
-import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
-import { PageLayoutWidgetNoDataDisplay } from '@/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay';
import { WidgetSkeletonLoader } from '@/page-layout/widgets/components/WidgetSkeletonLoader';
import { GraphWidgetAggregateChartRenderer } from '@/page-layout/widgets/graph/graphWidgetAggregateChart/components/GraphWidgetAggregateChartRenderer';
import { GraphWidgetBarChartRenderer } from '@/page-layout/widgets/graph/graphWidgetBarChart/components/GraphWidgetBarChartRenderer';
import { GraphWidgetLineChartRenderer } from '@/page-layout/widgets/graph/graphWidgetLineChart/components/GraphWidgetLineChartRenderer';
import { GraphWidgetPieChartRenderer } from '@/page-layout/widgets/graph/graphWidgetPieChart/components/GraphWidgetPieChartRenderer';
-import { areChartConfigurationFieldsValidForQuery } from '@/page-layout/widgets/graph/utils/areChartConfigurationFieldsValidForQuery';
import { useCurrentWidget } from '@/page-layout/widgets/hooks/useCurrentWidget';
import { lazy, Suspense } from 'react';
-import { isDefined } from 'twenty-shared/utils';
import { WidgetConfigurationType } from '~/generated-metadata/graphql';
const GraphWidgetGaugeChart = lazy(() =>
@@ -19,26 +15,9 @@ const GraphWidgetGaugeChart = lazy(() =>
})),
);
-export type GraphWidgetProps = {
- objectMetadataId: string;
-};
-
-export const GraphWidget = ({ objectMetadataId }: GraphWidgetProps) => {
+export const GraphWidget = () => {
const widget = useCurrentWidget();
- const { objectMetadataItem } = useObjectMetadataItemById({
- objectId: objectMetadataId,
- });
-
- const hasValidConfiguration = areChartConfigurationFieldsValidForQuery(
- widget.configuration,
- objectMetadataItem,
- );
-
- if (!isDefined(widget.configuration) || !hasValidConfiguration) {
- return ;
- }
-
const configurationType = widget.configuration?.configurationType;
switch (configurationType) {
diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetRenderer.tsx b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetRenderer.tsx
index 2028b0fd6e..5451d39a8e 100644
--- a/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetRenderer.tsx
+++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/components/GraphWidgetRenderer.tsx
@@ -1,6 +1,7 @@
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
import { PageLayoutWidgetNoDataDisplay } from '@/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay';
import { GraphWidget } from '@/page-layout/widgets/graph/components/GraphWidget';
+import { hasMinimalRequiredConfigForGraph } from '@/page-layout/widgets/graph/utils/hasMinimalRequiredConfigForGraph';
import { isDefined } from 'twenty-shared/utils';
type GraphWidgetRendererProps = {
@@ -8,13 +9,13 @@ type GraphWidgetRendererProps = {
};
export const GraphWidgetRenderer = ({ widget }: GraphWidgetRendererProps) => {
- if (!isDefined(widget.configuration)) {
+ if (
+ !isDefined(widget.configuration) ||
+ !isDefined(widget.objectMetadataId) ||
+ !hasMinimalRequiredConfigForGraph(widget.configuration)
+ ) {
return ;
}
- if (!isDefined(widget.objectMetadataId)) {
- return ;
- }
-
- return ;
+ return ;
};
diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/areChartConfigurationFieldsValidForQuery.test.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/areChartConfigurationFieldsValidForQuery.test.ts
deleted file mode 100644
index 3c84828063..0000000000
--- a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/__tests__/areChartConfigurationFieldsValidForQuery.test.ts
+++ /dev/null
@@ -1,213 +0,0 @@
-import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
-import {
- AggregateOperations,
- FieldMetadataType,
- WidgetConfigurationType,
- type AggregateChartConfiguration,
-} from '~/generated-metadata/graphql';
-
-import { areChartConfigurationFieldsValidForQuery } from '@/page-layout/widgets/graph/utils/areChartConfigurationFieldsValidForQuery';
-
-describe('areChartConfigurationFieldsValidForQuery', () => {
- const createMockObjectMetadataItem = (
- fields: Array<{
- id: string;
- type: FieldMetadataType;
- options?: Array<{ value: string; label: string }>;
- }>,
- ): ObjectMetadataItem =>
- ({
- id: 'object-1',
- nameSingular: 'opportunity',
- namePlural: 'opportunities',
- fields: fields.map((f) => ({
- id: f.id,
- name: f.id,
- type: f.type,
- label: f.id,
- options: f.options,
- })),
- }) as ObjectMetadataItem;
-
- const createAggregateConfig = (ratioAggregateConfig?: {
- fieldMetadataId: string;
- optionValue: string;
- }): AggregateChartConfiguration => ({
- __typename: 'AggregateChartConfiguration',
- configurationType: WidgetConfigurationType.AGGREGATE_CHART,
- aggregateFieldMetadataId: 'aggregate-field',
- aggregateOperation: AggregateOperations.COUNT,
- ratioAggregateConfig: ratioAggregateConfig ?? null,
- });
-
- describe('AggregateChartConfiguration with ratioAggregateConfig', () => {
- it('should return true when ratioAggregateConfig is undefined', () => {
- const objectMetadataItem = createMockObjectMetadataItem([
- { id: 'aggregate-field', type: FieldMetadataType.NUMBER },
- ]);
-
- const result = areChartConfigurationFieldsValidForQuery(
- createAggregateConfig(undefined),
- objectMetadataItem,
- );
-
- expect(result).toBe(true);
- });
-
- it('should return false when ratio field does not exist', () => {
- const objectMetadataItem = createMockObjectMetadataItem([
- { id: 'aggregate-field', type: FieldMetadataType.NUMBER },
- ]);
-
- const result = areChartConfigurationFieldsValidForQuery(
- createAggregateConfig({
- fieldMetadataId: 'non-existent-field',
- optionValue: 'true',
- }),
- objectMetadataItem,
- );
-
- expect(result).toBe(false);
- });
-
- it('should return true for BOOLEAN field with "true" optionValue', () => {
- const objectMetadataItem = createMockObjectMetadataItem([
- { id: 'aggregate-field', type: FieldMetadataType.NUMBER },
- { id: 'boolean-field', type: FieldMetadataType.BOOLEAN },
- ]);
-
- const result = areChartConfigurationFieldsValidForQuery(
- createAggregateConfig({
- fieldMetadataId: 'boolean-field',
- optionValue: 'true',
- }),
- objectMetadataItem,
- );
-
- expect(result).toBe(true);
- });
-
- it('should return true for BOOLEAN field with "false" optionValue', () => {
- const objectMetadataItem = createMockObjectMetadataItem([
- { id: 'aggregate-field', type: FieldMetadataType.NUMBER },
- { id: 'boolean-field', type: FieldMetadataType.BOOLEAN },
- ]);
-
- const result = areChartConfigurationFieldsValidForQuery(
- createAggregateConfig({
- fieldMetadataId: 'boolean-field',
- optionValue: 'false',
- }),
- objectMetadataItem,
- );
-
- expect(result).toBe(true);
- });
-
- it('should return false for BOOLEAN field with invalid optionValue', () => {
- const objectMetadataItem = createMockObjectMetadataItem([
- { id: 'aggregate-field', type: FieldMetadataType.NUMBER },
- { id: 'boolean-field', type: FieldMetadataType.BOOLEAN },
- ]);
-
- const result = areChartConfigurationFieldsValidForQuery(
- createAggregateConfig({
- fieldMetadataId: 'boolean-field',
- optionValue: 'invalid',
- }),
- objectMetadataItem,
- );
-
- expect(result).toBe(false);
- });
-
- it('should return true for SELECT field with valid option', () => {
- const objectMetadataItem = createMockObjectMetadataItem([
- { id: 'aggregate-field', type: FieldMetadataType.NUMBER },
- {
- id: 'select-field',
- type: FieldMetadataType.SELECT,
- options: [
- { value: 'WON', label: 'Won' },
- { value: 'LOST', label: 'Lost' },
- ],
- },
- ]);
-
- const result = areChartConfigurationFieldsValidForQuery(
- createAggregateConfig({
- fieldMetadataId: 'select-field',
- optionValue: 'WON',
- }),
- objectMetadataItem,
- );
-
- expect(result).toBe(true);
- });
-
- it('should return false for SELECT field with invalid option', () => {
- const objectMetadataItem = createMockObjectMetadataItem([
- { id: 'aggregate-field', type: FieldMetadataType.NUMBER },
- {
- id: 'select-field',
- type: FieldMetadataType.SELECT,
- options: [
- { value: 'WON', label: 'Won' },
- { value: 'LOST', label: 'Lost' },
- ],
- },
- ]);
-
- const result = areChartConfigurationFieldsValidForQuery(
- createAggregateConfig({
- fieldMetadataId: 'select-field',
- optionValue: 'INVALID_OPTION',
- }),
- objectMetadataItem,
- );
-
- expect(result).toBe(false);
- });
-
- it('should return true for MULTI_SELECT field with valid option', () => {
- const objectMetadataItem = createMockObjectMetadataItem([
- { id: 'aggregate-field', type: FieldMetadataType.NUMBER },
- {
- id: 'multiselect-field',
- type: FieldMetadataType.MULTI_SELECT,
- options: [
- { value: 'urgent', label: 'Urgent' },
- { value: 'important', label: 'Important' },
- ],
- },
- ]);
-
- const result = areChartConfigurationFieldsValidForQuery(
- createAggregateConfig({
- fieldMetadataId: 'multiselect-field',
- optionValue: 'urgent',
- }),
- objectMetadataItem,
- );
-
- expect(result).toBe(true);
- });
-
- it('should return false for unsupported field type with ratioConfig', () => {
- const objectMetadataItem = createMockObjectMetadataItem([
- { id: 'aggregate-field', type: FieldMetadataType.NUMBER },
- { id: 'text-field', type: FieldMetadataType.TEXT },
- ]);
-
- const result = areChartConfigurationFieldsValidForQuery(
- createAggregateConfig({
- fieldMetadataId: 'text-field',
- optionValue: 'some-value',
- }),
- objectMetadataItem,
- );
-
- expect(result).toBe(false);
- });
- });
-});
diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/areChartConfigurationFieldsValidForQuery.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/areChartConfigurationFieldsValidForQuery.ts
deleted file mode 100644
index 101191718b..0000000000
--- a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/areChartConfigurationFieldsValidForQuery.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-import { isString } from '@sniptt/guards';
-
-import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
-import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
-import { isDefined } from 'twenty-shared/utils';
-import {
- FieldMetadataType,
- type RatioAggregateConfig,
-} from '~/generated-metadata/graphql';
-
-const fieldExists = (
- fieldId: string | undefined | null,
- objectMetadataItem: ObjectMetadataItem,
-): boolean => {
- if (!isDefined(fieldId)) {
- return false;
- }
-
- return objectMetadataItem.fields.some((field) => field.id === fieldId);
-};
-
-const isRatioConfigValid = (
- ratioConfig: RatioAggregateConfig | null | undefined,
- objectMetadataItem: ObjectMetadataItem,
-): boolean => {
- if (!isDefined(ratioConfig)) {
- return true;
- }
-
- const field = objectMetadataItem.fields.find(
- (f) => f.id === ratioConfig.fieldMetadataId,
- );
-
- if (!isDefined(field)) {
- return false;
- }
-
- if (field.type === FieldMetadataType.BOOLEAN) {
- return (
- ratioConfig.optionValue === 'true' || ratioConfig.optionValue === 'false'
- );
- }
-
- if (
- field.type === FieldMetadataType.SELECT ||
- field.type === FieldMetadataType.MULTI_SELECT
- ) {
- const options = field.options ?? [];
- return options.some((option) => option.value === ratioConfig.optionValue);
- }
-
- return false;
-};
-
-export const areChartConfigurationFieldsValidForQuery = (
- configuration: PageLayoutWidget['configuration'],
- objectMetadataItem?: ObjectMetadataItem | null,
-): boolean => {
- if (!isDefined(configuration)) {
- return false;
- }
-
- if (!isDefined(objectMetadataItem) || !isDefined(objectMetadataItem.fields)) {
- return false;
- }
-
- switch (configuration.__typename) {
- case 'BarChartConfiguration':
- case 'LineChartConfiguration':
- return (
- fieldExists(
- configuration.aggregateFieldMetadataId,
- objectMetadataItem,
- ) &&
- fieldExists(
- configuration.primaryAxisGroupByFieldMetadataId,
- objectMetadataItem,
- ) &&
- (!isDefined(configuration.secondaryAxisGroupByFieldMetadataId) ||
- fieldExists(
- configuration.secondaryAxisGroupByFieldMetadataId,
- objectMetadataItem,
- ))
- );
-
- case 'PieChartConfiguration':
- return (
- fieldExists(
- configuration.aggregateFieldMetadataId,
- objectMetadataItem,
- ) &&
- fieldExists(configuration.groupByFieldMetadataId, objectMetadataItem)
- );
-
- case 'AggregateChartConfiguration':
- return (
- fieldExists(
- configuration.aggregateFieldMetadataId,
- objectMetadataItem,
- ) &&
- isRatioConfigValid(
- configuration.ratioAggregateConfig,
- objectMetadataItem,
- )
- );
-
- case 'GaugeChartConfiguration':
- return fieldExists(
- configuration.aggregateFieldMetadataId,
- objectMetadataItem,
- );
-
- case 'IframeConfiguration':
- return isString(configuration.url) && configuration.url.trim().length > 0;
-
- default:
- return false;
- }
-};
diff --git a/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/hasMinimalRequiredConfigForGraph.ts b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/hasMinimalRequiredConfigForGraph.ts
new file mode 100644
index 0000000000..824718233a
--- /dev/null
+++ b/packages/twenty-front/src/modules/page-layout/widgets/graph/utils/hasMinimalRequiredConfigForGraph.ts
@@ -0,0 +1,35 @@
+import { isWidgetConfigurationOfType } from '@/command-menu/pages/page-layout/utils/isWidgetConfigurationOfType';
+import { type FieldConfiguration } from '@/page-layout/types/FieldConfiguration';
+import { type FieldsConfiguration } from '@/page-layout/types/FieldsConfiguration';
+import { isDefined } from 'twenty-shared/utils';
+import { type WidgetConfiguration } from '~/generated-metadata/graphql';
+
+export const hasMinimalRequiredConfigForGraph = (
+ configuration: WidgetConfiguration | FieldsConfiguration | FieldConfiguration,
+): boolean => {
+ if (
+ isWidgetConfigurationOfType(configuration, 'BarChartConfiguration') ||
+ isWidgetConfigurationOfType(configuration, 'LineChartConfiguration')
+ ) {
+ return (
+ isDefined(configuration.aggregateFieldMetadataId) &&
+ isDefined(configuration.primaryAxisGroupByFieldMetadataId)
+ );
+ }
+
+ if (isWidgetConfigurationOfType(configuration, 'PieChartConfiguration')) {
+ return (
+ isDefined(configuration.aggregateFieldMetadataId) &&
+ isDefined(configuration.groupByFieldMetadataId)
+ );
+ }
+
+ if (
+ isWidgetConfigurationOfType(configuration, 'AggregateChartConfiguration') ||
+ isWidgetConfigurationOfType(configuration, 'GaugeChartConfiguration')
+ ) {
+ return isDefined(configuration.aggregateFieldMetadataId);
+ }
+
+ return false;
+};
diff --git a/packages/twenty-front/src/testing/mock-data/widget-configurations.ts b/packages/twenty-front/src/testing/mock-data/widget-configurations.ts
index 68fd09e627..e7b56732f0 100644
--- a/packages/twenty-front/src/testing/mock-data/widget-configurations.ts
+++ b/packages/twenty-front/src/testing/mock-data/widget-configurations.ts
@@ -32,6 +32,7 @@ export const TEST_BAR_CHART_CONFIGURATION: TypedBarChartConfiguration = {
primaryAxisOrderBy: GraphOrderBy.FIELD_ASC,
displayDataLabel: true,
axisNameDisplay: AxisNameDisplay.NONE,
+ splitMultiValueFields: false,
};
export const TEST_BAR_CHART_CONFIGURATION_HORIZONTAL: TypedBarChartConfiguration =
@@ -50,6 +51,7 @@ export const TEST_LINE_CHART_CONFIGURATION: TypedLineChartConfiguration = {
primaryAxisDateGranularity: ObjectRecordGroupByDateGranularity.DAY,
displayDataLabel: true,
axisNameDisplay: AxisNameDisplay.NONE,
+ splitMultiValueFields: false,
};
export const TEST_PIE_CHART_CONFIGURATION: TypedPieChartConfiguration = {
@@ -62,6 +64,7 @@ export const TEST_PIE_CHART_CONFIGURATION: TypedPieChartConfiguration = {
dateGranularity: ObjectRecordGroupByDateGranularity.MONTH,
orderBy: GraphOrderBy.VALUE_DESC,
displayDataLabel: true,
+ splitMultiValueFields: false,
};
export const TEST_AGGREGATE_CHART_CONFIGURATION: TypedAggregateChartConfiguration =
diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout-update.service.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout-update.service.ts
index e23a20634c..fa583964f9 100644
--- a/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout-update.service.ts
+++ b/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout-update.service.ts
@@ -459,10 +459,7 @@ export class PageLayoutUpdateService {
pageLayoutTabId: widgetInput.pageLayoutTabId,
objectMetadataId: widgetInput.objectMetadataId,
},
- flatEntityMaps: {
- flatPageLayoutTabMaps,
- flatObjectMetadataMaps,
- },
+ flatEntityMaps: { flatPageLayoutTabMaps, flatObjectMetadataMaps },
});
return {
@@ -505,12 +502,26 @@ export class PageLayoutUpdateService {
const updatedConfiguration = widgetInput.configuration ?? null;
+ const {
+ pageLayoutTabUniversalIdentifier,
+ objectMetadataUniversalIdentifier,
+ } = resolveEntityRelationUniversalIdentifiers({
+ metadataName: 'pageLayoutWidget',
+ foreignKeyValues: {
+ pageLayoutTabId: widgetInput.pageLayoutTabId,
+ objectMetadataId: widgetInput.objectMetadataId,
+ },
+ flatEntityMaps: { flatPageLayoutTabMaps, flatObjectMetadataMaps },
+ });
+
return {
...existingWidget,
pageLayoutTabId: widgetInput.pageLayoutTabId,
+ pageLayoutTabUniversalIdentifier,
title: widgetInput.title,
type: widgetInput.type,
objectMetadataId: widgetInput.objectMetadataId ?? null,
+ objectMetadataUniversalIdentifier,
gridPosition: widgetInput.gridPosition,
position: widgetInput.position ?? null,
configuration: updatedConfiguration,
@@ -536,12 +547,26 @@ export class PageLayoutUpdateService {
const restoredConfiguration = widgetInput.configuration ?? null;
+ const {
+ pageLayoutTabUniversalIdentifier,
+ objectMetadataUniversalIdentifier,
+ } = resolveEntityRelationUniversalIdentifiers({
+ metadataName: 'pageLayoutWidget',
+ foreignKeyValues: {
+ pageLayoutTabId: widgetInput.pageLayoutTabId,
+ objectMetadataId: widgetInput.objectMetadataId,
+ },
+ flatEntityMaps: { flatPageLayoutTabMaps, flatObjectMetadataMaps },
+ });
+
return {
...existingWidget,
pageLayoutTabId: widgetInput.pageLayoutTabId,
+ pageLayoutTabUniversalIdentifier,
title: widgetInput.title,
type: widgetInput.type,
objectMetadataId: widgetInput.objectMetadataId ?? null,
+ objectMetadataUniversalIdentifier,
gridPosition: widgetInput.gridPosition,
position: widgetInput.position ?? null,
configuration: restoredConfiguration,