diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 535c59768f..8b160b8c85 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -1255,6 +1255,7 @@ export enum FeatureFlagKey { IS_AIRTABLE_INTEGRATION_ENABLED = 'IS_AIRTABLE_INTEGRATION_ENABLED', IS_AI_ENABLED = 'IS_AI_ENABLED', IS_APPLICATION_ENABLED = 'IS_APPLICATION_ENABLED', + IS_DASHBOARD_V2_ENABLED = 'IS_DASHBOARD_V2_ENABLED', IS_EMAILING_DOMAIN_ENABLED = 'IS_EMAILING_DOMAIN_ENABLED', IS_IMAP_SMTP_CALDAV_ENABLED = 'IS_IMAP_SMTP_CALDAV_ENABLED', IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED', diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index 9cbd9a2caf..55e0968424 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -1219,6 +1219,7 @@ export enum FeatureFlagKey { IS_AIRTABLE_INTEGRATION_ENABLED = 'IS_AIRTABLE_INTEGRATION_ENABLED', IS_AI_ENABLED = 'IS_AI_ENABLED', IS_APPLICATION_ENABLED = 'IS_APPLICATION_ENABLED', + IS_DASHBOARD_V2_ENABLED = 'IS_DASHBOARD_V2_ENABLED', IS_EMAILING_DOMAIN_ENABLED = 'IS_EMAILING_DOMAIN_ENABLED', IS_IMAP_SMTP_CALDAV_ENABLED = 'IS_IMAP_SMTP_CALDAV_ENABLED', IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED', diff --git a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/ChartTypeSelectionSection.tsx b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/ChartTypeSelectionSection.tsx index 9571465963..0beed69933 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/ChartTypeSelectionSection.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/ChartTypeSelectionSection.tsx @@ -1,6 +1,7 @@ import { GRAPH_TYPE_INFORMATION } from '@/command-menu/pages/page-layout/constants/GraphTypeInformation'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import styled from '@emotion/styled'; -import { GraphType } from '~/generated-metadata/graphql'; +import { FeatureFlagKey, GraphType } from '~/generated-metadata/graphql'; import { t } from '@lingui/core/macro'; import { MenuPicker } from 'twenty-ui/navigation'; @@ -14,12 +15,6 @@ const graphTypeOptions = [ GraphType.GAUGE, ]; -const disabledGraphTypeOptions = [ - GraphType.PIE, - GraphType.LINE, - GraphType.GAUGE, -]; - const StyledChartTypeSelectionContainer = styled.div` display: flex; flex-direction: row; @@ -35,10 +30,20 @@ export const ChartTypeSelectionSection = ({ currentGraphType, setCurrentGraphType, }: ChartTypeSelectionSectionProps) => { + const isDashboardV2Enabled = useIsFeatureEnabled( + FeatureFlagKey.IS_DASHBOARD_V2_ENABLED, + ); + return ( {graphTypeOptions.map((graphType) => { - const isDisabled = disabledGraphTypeOptions.includes(graphType); + const isChartV2Type = [ + GraphType.PIE, + GraphType.LINE, + GraphType.GAUGE, + ].includes(graphType); + + const isDisabled = isChartV2Type && !isDashboardV2Enabled; return ( { findByIdOrThrow: jest.fn(), }, }, + { + provide: FeatureFlagService, + useValue: { + isFeatureEnabled: jest.fn(), + }, + }, ], }).compile(); diff --git a/packages/twenty-server/src/engine/core-modules/page-layout/services/page-layout-widget.service.ts b/packages/twenty-server/src/engine/core-modules/page-layout/services/page-layout-widget.service.ts index 9c176be778..b6908a15ed 100644 --- a/packages/twenty-server/src/engine/core-modules/page-layout/services/page-layout-widget.service.ts +++ b/packages/twenty-server/src/engine/core-modules/page-layout/services/page-layout-widget.service.ts @@ -5,6 +5,8 @@ import { isDefined } from 'twenty-shared/utils'; import { EntityManager, IsNull, Repository } from 'typeorm'; import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { CreatePageLayoutWidgetInput } from 'src/engine/core-modules/page-layout/dtos/inputs/create-page-layout-widget.input'; import { UpdatePageLayoutWidgetInput } from 'src/engine/core-modules/page-layout/dtos/inputs/update-page-layout-widget.input'; import { WidgetConfigurationInterface } from 'src/engine/core-modules/page-layout/dtos/widget-configuration.interface'; @@ -29,6 +31,7 @@ export class PageLayoutWidgetService { @InjectRepository(PageLayoutWidgetEntity) private readonly pageLayoutWidgetRepository: Repository, private readonly pageLayoutTabService: PageLayoutTabService, + private readonly featureFlagService: FeatureFlagService, ) {} private getPageLayoutWidgetRepository( @@ -131,11 +134,18 @@ export class PageLayoutWidgetService { let validatedConfig: WidgetConfigurationInterface | null = null; if (pageLayoutWidgetData.configuration && pageLayoutWidgetData.type) { - try { - validatedConfig = validateAndTransformWidgetConfiguration( - pageLayoutWidgetData.type, - pageLayoutWidgetData.configuration, + const isDashboardV2Enabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IS_DASHBOARD_V2_ENABLED, + workspaceId, ); + + try { + validatedConfig = validateAndTransformWidgetConfiguration({ + type: pageLayoutWidgetData.type, + configuration: pageLayoutWidgetData.configuration, + isDashboardV2Enabled, + }); } catch (error) { throw new PageLayoutWidgetException( generatePageLayoutWidgetExceptionMessage( @@ -228,11 +238,18 @@ export class PageLayoutWidgetService { const titleForError = updateData.title ?? existingWidget.title; if (typeForValidation) { - try { - validatedConfig = validateAndTransformWidgetConfiguration( - typeForValidation, - updateData.configuration, + const isDashboardV2Enabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IS_DASHBOARD_V2_ENABLED, + workspaceId, ); + + try { + validatedConfig = validateAndTransformWidgetConfiguration({ + type: typeForValidation, + configuration: updateData.configuration, + isDashboardV2Enabled, + }); } catch (error) { throw new PageLayoutWidgetException( generatePageLayoutWidgetExceptionMessage( diff --git a/packages/twenty-server/src/engine/core-modules/page-layout/utils/__tests__/is-widget-configuration-valid.util.spec.ts b/packages/twenty-server/src/engine/core-modules/page-layout/utils/__tests__/is-widget-configuration-valid.util.spec.ts deleted file mode 100644 index 6b4e4876b0..0000000000 --- a/packages/twenty-server/src/engine/core-modules/page-layout/utils/__tests__/is-widget-configuration-valid.util.spec.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { - INVALID_IFRAME_CONFIG_BAD_URL, - TEST_IFRAME_CONFIG, - TEST_NUMBER_CHART_CONFIG, -} from 'test/integration/constants/widget-configuration-test-data.constants'; - -import { WidgetType } from 'src/engine/core-modules/page-layout/enums/widget-type.enum'; -import { isWidgetConfigurationValid } from 'src/engine/core-modules/page-layout/utils/is-widget-configuration-valid.util'; - -describe('isWidgetConfigurationValid', () => { - it('should return true for valid configuration', () => { - const result = isWidgetConfigurationValid( - WidgetType.IFRAME, - TEST_IFRAME_CONFIG, - ); - - expect(result).toBe(true); - }); - - it('should return false for invalid configuration', () => { - const result = isWidgetConfigurationValid( - WidgetType.IFRAME, - INVALID_IFRAME_CONFIG_BAD_URL, - ); - - expect(result).toBe(false); - }); - - it('should return false for null configuration', () => { - const result = isWidgetConfigurationValid(WidgetType.IFRAME, null); - - expect(result).toBe(false); - }); - - it('should return false for unsupported widget type', () => { - const result = isWidgetConfigurationValid( - 'UNSUPPORTED' as WidgetType, - TEST_NUMBER_CHART_CONFIG, - ); - - expect(result).toBe(false); - }); -}); diff --git a/packages/twenty-server/src/engine/core-modules/page-layout/utils/__tests__/validate-and-transform-widget-configuration.util.spec.ts b/packages/twenty-server/src/engine/core-modules/page-layout/utils/__tests__/validate-and-transform-widget-configuration.util.spec.ts index 2e486691c1..f532f54f66 100644 --- a/packages/twenty-server/src/engine/core-modules/page-layout/utils/__tests__/validate-and-transform-widget-configuration.util.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/page-layout/utils/__tests__/validate-and-transform-widget-configuration.util.spec.ts @@ -6,16 +6,13 @@ import { INVALID_NUMBER_CHART_CONFIG_MISSING_FIELDS, INVALID_VERTICAL_BAR_CHART_CONFIG_MISSING_GROUP_BY, TEST_GAUGE_CHART_CONFIG, - TEST_GAUGE_CHART_CONFIG_MINIMAL, TEST_HORIZONTAL_BAR_CHART_CONFIG, TEST_HORIZONTAL_BAR_CHART_CONFIG_MINIMAL, TEST_IFRAME_CONFIG, TEST_LINE_CHART_CONFIG, - TEST_LINE_CHART_CONFIG_MINIMAL, TEST_NUMBER_CHART_CONFIG, TEST_NUMBER_CHART_CONFIG_MINIMAL, TEST_PIE_CHART_CONFIG, - TEST_PIE_CHART_CONFIG_MINIMAL, TEST_VERTICAL_BAR_CHART_CONFIG, TEST_VERTICAL_BAR_CHART_CONFIG_MINIMAL, } from 'test/integration/constants/widget-configuration-test-data.constants'; @@ -26,29 +23,32 @@ import { validateAndTransformWidgetConfiguration } from 'src/engine/core-modules describe('validateAndTransformWidgetConfiguration', () => { describe('IFRAME widget', () => { it('should validate and transform valid iframe configuration', () => { - const result = validateAndTransformWidgetConfiguration( - WidgetType.IFRAME, - TEST_IFRAME_CONFIG, - ); + const result = validateAndTransformWidgetConfiguration({ + type: WidgetType.IFRAME, + configuration: TEST_IFRAME_CONFIG, + isDashboardV2Enabled: false, + }); expect(result).toMatchObject(TEST_IFRAME_CONFIG); }); it('should throw error for invalid URL', () => { expect(() => - validateAndTransformWidgetConfiguration( - WidgetType.IFRAME, - INVALID_IFRAME_CONFIG_BAD_URL, - ), + validateAndTransformWidgetConfiguration({ + type: WidgetType.IFRAME, + configuration: INVALID_IFRAME_CONFIG_BAD_URL, + isDashboardV2Enabled: false, + }), ).toThrow(/url must be a URL address/); }); it('should throw error for empty URL', () => { expect(() => - validateAndTransformWidgetConfiguration( - WidgetType.IFRAME, - INVALID_IFRAME_CONFIG_EMPTY_URL, - ), + validateAndTransformWidgetConfiguration({ + type: WidgetType.IFRAME, + configuration: INVALID_IFRAME_CONFIG_EMPTY_URL, + isDashboardV2Enabled: false, + }), ).toThrow(/url must be a URL address/); }); }); @@ -56,170 +56,121 @@ describe('validateAndTransformWidgetConfiguration', () => { describe('GRAPH widget', () => { describe('NUMBER graph', () => { it('should validate full number graph configuration', () => { - const result = validateAndTransformWidgetConfiguration( - WidgetType.GRAPH, - TEST_NUMBER_CHART_CONFIG, - ); + const result = validateAndTransformWidgetConfiguration({ + type: WidgetType.GRAPH, + configuration: TEST_NUMBER_CHART_CONFIG, + isDashboardV2Enabled: false, + }); expect(result).toMatchObject(TEST_NUMBER_CHART_CONFIG); }); it('should validate minimal number graph configuration', () => { - const result = validateAndTransformWidgetConfiguration( - WidgetType.GRAPH, - TEST_NUMBER_CHART_CONFIG_MINIMAL, - ); + const result = validateAndTransformWidgetConfiguration({ + type: WidgetType.GRAPH, + configuration: TEST_NUMBER_CHART_CONFIG_MINIMAL, + isDashboardV2Enabled: false, + }); expect(result).toMatchObject(TEST_NUMBER_CHART_CONFIG_MINIMAL); }); it('should throw error for partial number graph configuration with missing required fields', () => { expect(() => - validateAndTransformWidgetConfiguration( - WidgetType.GRAPH, - INVALID_NUMBER_CHART_CONFIG_MISSING_FIELDS, - ), + validateAndTransformWidgetConfiguration({ + type: WidgetType.GRAPH, + configuration: INVALID_NUMBER_CHART_CONFIG_MISSING_FIELDS, + isDashboardV2Enabled: false, + }), ).toThrow(/aggregateFieldMetadataId.*aggregateOperation/); }); it('should throw error for invalid UUID', () => { expect(() => - validateAndTransformWidgetConfiguration( - WidgetType.GRAPH, - INVALID_NUMBER_CHART_CONFIG_BAD_UUID, - ), + validateAndTransformWidgetConfiguration({ + type: WidgetType.GRAPH, + configuration: INVALID_NUMBER_CHART_CONFIG_BAD_UUID, + isDashboardV2Enabled: false, + }), ).toThrow(/aggregateFieldMetadataId must be a UUID/); }); }); describe('VERTICAL_BAR graph', () => { it('should validate full vertical bar graph configuration', () => { - const result = validateAndTransformWidgetConfiguration( - WidgetType.GRAPH, - TEST_VERTICAL_BAR_CHART_CONFIG, - ); + const result = validateAndTransformWidgetConfiguration({ + type: WidgetType.GRAPH, + configuration: TEST_VERTICAL_BAR_CHART_CONFIG, + isDashboardV2Enabled: false, + }); expect(result).toMatchObject(TEST_VERTICAL_BAR_CHART_CONFIG); }); it('should validate minimal vertical bar graph configuration', () => { - const result = validateAndTransformWidgetConfiguration( - WidgetType.GRAPH, - TEST_VERTICAL_BAR_CHART_CONFIG_MINIMAL, - ); + const result = validateAndTransformWidgetConfiguration({ + type: WidgetType.GRAPH, + configuration: TEST_VERTICAL_BAR_CHART_CONFIG_MINIMAL, + isDashboardV2Enabled: false, + }); expect(result).toMatchObject(TEST_VERTICAL_BAR_CHART_CONFIG_MINIMAL); }); it('should throw error for partial vertical bar graph configuration with missing required fields', () => { expect(() => - validateAndTransformWidgetConfiguration( - WidgetType.GRAPH, - INVALID_VERTICAL_BAR_CHART_CONFIG_MISSING_GROUP_BY, - ), + validateAndTransformWidgetConfiguration({ + type: WidgetType.GRAPH, + configuration: INVALID_VERTICAL_BAR_CHART_CONFIG_MISSING_GROUP_BY, + isDashboardV2Enabled: false, + }), ).toThrow(/primaryAxisGroupByFieldMetadataId/); }); }); describe('HORIZONTAL_BAR graph', () => { it('should validate full horizontal bar graph configuration', () => { - const result = validateAndTransformWidgetConfiguration( - WidgetType.GRAPH, - TEST_HORIZONTAL_BAR_CHART_CONFIG, - ); + const result = validateAndTransformWidgetConfiguration({ + type: WidgetType.GRAPH, + configuration: TEST_HORIZONTAL_BAR_CHART_CONFIG, + isDashboardV2Enabled: false, + }); expect(result).toMatchObject(TEST_HORIZONTAL_BAR_CHART_CONFIG); }); it('should validate minimal horizontal bar graph configuration', () => { - const result = validateAndTransformWidgetConfiguration( - WidgetType.GRAPH, - TEST_HORIZONTAL_BAR_CHART_CONFIG_MINIMAL, - ); + const result = validateAndTransformWidgetConfiguration({ + type: WidgetType.GRAPH, + configuration: TEST_HORIZONTAL_BAR_CHART_CONFIG_MINIMAL, + isDashboardV2Enabled: false, + }); expect(result).toMatchObject(TEST_HORIZONTAL_BAR_CHART_CONFIG_MINIMAL); }); it('should throw error for partial horizontal bar graph configuration with missing required fields', () => { expect(() => - validateAndTransformWidgetConfiguration( - WidgetType.GRAPH, - INVALID_HORIZONTAL_BAR_CHART_CONFIG_MISSING_GROUP_BY, - ), + validateAndTransformWidgetConfiguration({ + type: WidgetType.GRAPH, + configuration: INVALID_HORIZONTAL_BAR_CHART_CONFIG_MISSING_GROUP_BY, + isDashboardV2Enabled: false, + }), ).toThrow(/primaryAxisGroupByFieldMetadataId/); }); }); - describe('LINE graph', () => { - it('should validate full line graph configuration', () => { - const result = validateAndTransformWidgetConfiguration( - WidgetType.GRAPH, - TEST_LINE_CHART_CONFIG, - ); - - expect(result).toMatchObject(TEST_LINE_CHART_CONFIG); - }); - - it('should validate minimal line graph configuration', () => { - const result = validateAndTransformWidgetConfiguration( - WidgetType.GRAPH, - TEST_LINE_CHART_CONFIG_MINIMAL, - ); - - expect(result).toMatchObject(TEST_LINE_CHART_CONFIG_MINIMAL); - }); - }); - - describe('PIE graph', () => { - it('should validate full pie graph configuration', () => { - const result = validateAndTransformWidgetConfiguration( - WidgetType.GRAPH, - TEST_PIE_CHART_CONFIG, - ); - - expect(result).toMatchObject(TEST_PIE_CHART_CONFIG); - }); - - it('should validate minimal pie graph configuration', () => { - const result = validateAndTransformWidgetConfiguration( - WidgetType.GRAPH, - TEST_PIE_CHART_CONFIG_MINIMAL, - ); - - expect(result).toMatchObject(TEST_PIE_CHART_CONFIG_MINIMAL); - }); - }); - - describe('GAUGE graph', () => { - it('should validate full gauge graph configuration', () => { - const result = validateAndTransformWidgetConfiguration( - WidgetType.GRAPH, - TEST_GAUGE_CHART_CONFIG, - ); - - expect(result).toMatchObject(TEST_GAUGE_CHART_CONFIG); - }); - - it('should validate minimal gauge graph configuration', () => { - const result = validateAndTransformWidgetConfiguration( - WidgetType.GRAPH, - TEST_GAUGE_CHART_CONFIG_MINIMAL, - ); - - expect(result).toMatchObject(TEST_GAUGE_CHART_CONFIG_MINIMAL); - }); - }); - it('should return null for unsupported graph type', () => { const configuration = { graphType: 'UNSUPPORTED', viewId: '550e8400-e29b-41d4-a716-446655440000', }; - const result = validateAndTransformWidgetConfiguration( - WidgetType.GRAPH, - configuration, - ); + const result = validateAndTransformWidgetConfiguration({ + type: WidgetType.GRAPH, + configuration: configuration, + isDashboardV2Enabled: false, + }); expect(result).toBeNull(); }); @@ -229,10 +180,11 @@ describe('validateAndTransformWidgetConfiguration', () => { viewId: '550e8400-e29b-41d4-a716-446655440000', }; - const result = validateAndTransformWidgetConfiguration( - WidgetType.GRAPH, - configuration, - ); + const result = validateAndTransformWidgetConfiguration({ + type: WidgetType.GRAPH, + configuration: configuration, + isDashboardV2Enabled: false, + }); expect(result).toBeNull(); }); @@ -241,29 +193,42 @@ describe('validateAndTransformWidgetConfiguration', () => { describe('Edge cases', () => { it('should throw error for null configuration', () => { expect(() => - validateAndTransformWidgetConfiguration(WidgetType.IFRAME, null), + validateAndTransformWidgetConfiguration({ + type: WidgetType.IFRAME, + configuration: null, + isDashboardV2Enabled: false, + }), ).toThrow('Invalid configuration: not an object'); }); it('should throw error for undefined configuration', () => { expect(() => - validateAndTransformWidgetConfiguration(WidgetType.IFRAME, undefined), + validateAndTransformWidgetConfiguration({ + type: WidgetType.IFRAME, + configuration: undefined, + isDashboardV2Enabled: false, + }), ).toThrow('Invalid configuration: not an object'); }); it('should throw error for non-object configuration', () => { expect(() => - validateAndTransformWidgetConfiguration(WidgetType.IFRAME, 'string'), + validateAndTransformWidgetConfiguration({ + type: WidgetType.IFRAME, + configuration: 'string', + isDashboardV2Enabled: false, + }), ).toThrow('Invalid configuration: not an object'); }); it('should return null for unsupported widget type', () => { const configuration = { someField: 'value' }; - const result = validateAndTransformWidgetConfiguration( - 'UNSUPPORTED' as WidgetType, - configuration, - ); + const result = validateAndTransformWidgetConfiguration({ + type: 'UNSUPPORTED' as WidgetType, + configuration: configuration, + isDashboardV2Enabled: false, + }); expect(result).toBeNull(); }); @@ -272,11 +237,70 @@ describe('validateAndTransformWidgetConfiguration', () => { describe('Error messages', () => { it('should include validation details in error message', () => { expect(() => - validateAndTransformWidgetConfiguration( - WidgetType.GRAPH, - INVALID_NUMBER_CHART_CONFIG_BAD_UUID, - ), + validateAndTransformWidgetConfiguration({ + type: WidgetType.GRAPH, + configuration: INVALID_NUMBER_CHART_CONFIG_BAD_UUID, + isDashboardV2Enabled: false, + }), ).toThrow(/aggregateFieldMetadataId must be a UUID/); }); }); + + describe('Feature flags', () => { + it('should throw error for unsupported graph type', () => { + expect(() => + validateAndTransformWidgetConfiguration({ + type: WidgetType.GRAPH, + configuration: TEST_PIE_CHART_CONFIG, + isDashboardV2Enabled: false, + }), + ).toThrow(/IS_DASHBOARD_V2_ENABLED feature flag/); + + expect(() => + validateAndTransformWidgetConfiguration({ + type: WidgetType.GRAPH, + configuration: TEST_LINE_CHART_CONFIG, + isDashboardV2Enabled: false, + }), + ).toThrow(/IS_DASHBOARD_V2_ENABLED feature flag/); + + expect(() => + validateAndTransformWidgetConfiguration({ + type: WidgetType.GRAPH, + configuration: TEST_GAUGE_CHART_CONFIG, + isDashboardV2Enabled: false, + }), + ).toThrow(/IS_DASHBOARD_V2_ENABLED feature flag/); + }); + + it('should not throw error when IS_DASHBOARD_V2_ENABLED feature flag is enabled', () => { + expect(() => + validateAndTransformWidgetConfiguration({ + type: WidgetType.GRAPH, + configuration: TEST_PIE_CHART_CONFIG, + isDashboardV2Enabled: true, + }), + ).not.toThrow(); + }); + + it('should not throw error when IS_DASHBOARD_V2_ENABLED feature flag is enabled', () => { + expect(() => + validateAndTransformWidgetConfiguration({ + type: WidgetType.GRAPH, + configuration: TEST_LINE_CHART_CONFIG, + isDashboardV2Enabled: true, + }), + ).not.toThrow(); + }); + + it('should not throw error when IS_DASHBOARD_V2_ENABLED feature flag is enabled', () => { + expect(() => + validateAndTransformWidgetConfiguration({ + type: WidgetType.GRAPH, + configuration: TEST_GAUGE_CHART_CONFIG, + isDashboardV2Enabled: true, + }), + ).not.toThrow(); + }); + }); }); diff --git a/packages/twenty-server/src/engine/core-modules/page-layout/utils/is-widget-configuration-valid.util.ts b/packages/twenty-server/src/engine/core-modules/page-layout/utils/is-widget-configuration-valid.util.ts deleted file mode 100644 index cdcb7ba687..0000000000 --- a/packages/twenty-server/src/engine/core-modules/page-layout/utils/is-widget-configuration-valid.util.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { type WidgetType } from 'src/engine/core-modules/page-layout/enums/widget-type.enum'; -import { validateAndTransformWidgetConfiguration } from 'src/engine/core-modules/page-layout/utils/validate-and-transform-widget-configuration.util'; - -export const isWidgetConfigurationValid = ( - type: WidgetType, - configuration: unknown, -): boolean => { - try { - const validatedConfig = validateAndTransformWidgetConfiguration( - type, - configuration, - ); - - return validatedConfig !== null; - } catch { - return false; - } -}; diff --git a/packages/twenty-server/src/engine/core-modules/page-layout/utils/validate-and-transform-widget-configuration.util.ts b/packages/twenty-server/src/engine/core-modules/page-layout/utils/validate-and-transform-widget-configuration.util.ts index b555af5537..fd33201e92 100644 --- a/packages/twenty-server/src/engine/core-modules/page-layout/utils/validate-and-transform-widget-configuration.util.ts +++ b/packages/twenty-server/src/engine/core-modules/page-layout/utils/validate-and-transform-widget-configuration.util.ts @@ -23,15 +23,27 @@ const formatValidationErrors = (errors: ValidationError[]): string => { .join('; '); }; -const validateGraphConfiguration = ( - configuration: Record, -): WidgetConfigurationInterface | null => { +const validateGraphConfiguration = ({ + configuration, + isDashboardV2Enabled, +}: { + configuration: Record; + isDashboardV2Enabled: boolean; +}): WidgetConfigurationInterface | null => { const graphType = configuration.graphType as GraphType; if (!graphType || !Object.values(GraphType).includes(graphType)) { return null; } + const v2ChartTypes = [GraphType.PIE, GraphType.LINE, GraphType.GAUGE]; + + if (v2ChartTypes.includes(graphType) && !isDashboardV2Enabled) { + throw new Error( + `Chart type ${graphType} requires IS_DASHBOARD_V2_ENABLED feature flag`, + ); + } + switch (graphType) { case GraphType.VERTICAL_BAR: case GraphType.HORIZONTAL_BAR: { @@ -135,10 +147,15 @@ const validateIframeConfiguration = ( return instance; }; -export const validateAndTransformWidgetConfiguration = ( - type: WidgetType, - configuration: unknown, -): WidgetConfigurationInterface | null => { +export const validateAndTransformWidgetConfiguration = ({ + type, + configuration, + isDashboardV2Enabled, +}: { + type: WidgetType; + configuration: unknown; + isDashboardV2Enabled: boolean; +}): WidgetConfigurationInterface | null => { if (!configuration || typeof configuration !== 'object') { throw new Error('Invalid configuration: not an object'); } @@ -146,9 +163,10 @@ export const validateAndTransformWidgetConfiguration = ( try { switch (type) { case WidgetType.GRAPH: - return validateGraphConfiguration( - configuration as Record, - ); + return validateGraphConfiguration({ + configuration: configuration as Record, + isDashboardV2Enabled, + }); case WidgetType.IFRAME: return validateIframeConfiguration(configuration); default: diff --git a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts index 1cce1e9861..c10c30b707 100644 --- a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts +++ b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts @@ -138,6 +138,7 @@ describe('WorkspaceEntityManager', () => { IS_PUBLIC_DOMAIN_ENABLED: false, IS_EMAILING_DOMAIN_ENABLED: false, IS_WORKFLOW_RUN_STOPPAGE_ENABLED: false, + IS_DASHBOARD_V2_ENABLED: false, }, eventEmitterService: { emitMutationEvent: jest.fn(), @@ -163,6 +164,8 @@ describe('WorkspaceEntityManager', () => { IS_MESSAGE_FOLDER_CONTROL_ENABLED: false, IS_PUBLIC_DOMAIN_ENABLED: false, IS_EMAILING_DOMAIN_ENABLED: false, + IS_WORKFLOW_RUN_STOPPAGE_ENABLED: false, + IS_DASHBOARD_V2_ENABLED: false, }, permissionsPerRoleId: {}, } as WorkspaceDataSource; diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts index 91c217a9f7..a2302e7262 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts @@ -81,6 +81,11 @@ export const seedFeatureFlags = async ( workspaceId: workspaceId, value: true, }, + { + key: FeatureFlagKey.IS_DASHBOARD_V2_ENABLED, + workspaceId: workspaceId, + value: true, + }, ]) .execute(); }; diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-page-layout-widgets.util.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-page-layout-widgets.util.ts index c0b0a28810..481cb58861 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-page-layout-widgets.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-page-layout-widgets.util.ts @@ -4,21 +4,29 @@ import { validateAndTransformWidgetConfiguration } from 'src/engine/core-modules import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { getPageLayoutWidgetDataSeeds } from 'src/engine/workspace-manager/dev-seeder/core/utils/get-page-layout-widget-data-seeds.util'; -export const seedPageLayoutWidgets = async ( - dataSource: DataSource, - schemaName: string, - workspaceId: string, - objectMetadataItems: ObjectMetadataEntity[], -) => { +export const seedPageLayoutWidgets = async ({ + dataSource, + schemaName, + workspaceId, + objectMetadataItems, + isDashboardV2Enabled, +}: { + dataSource: DataSource; + schemaName: string; + workspaceId: string; + objectMetadataItems: ObjectMetadataEntity[]; + isDashboardV2Enabled: boolean; +}) => { const pageLayoutWidgets = getPageLayoutWidgetDataSeeds( workspaceId, objectMetadataItems, ).map((widget) => { const validatedConfiguration = widget.configuration - ? validateAndTransformWidgetConfiguration( - widget.type, - widget.configuration, - ) + ? validateAndTransformWidgetConfiguration({ + type: widget.type, + configuration: widget.configuration, + isDashboardV2Enabled, + }) : null; return { diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/services/dev-seeder.service.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/services/dev-seeder.service.ts index 91c291f455..88eeff8195 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/services/dev-seeder.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/services/dev-seeder.service.ts @@ -3,6 +3,7 @@ import { InjectDataSource } from '@nestjs/typeorm'; import { DataSource } from 'typeorm'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; @@ -89,12 +90,18 @@ export class DevSeederService { relations: { fields: true }, }); - await seedPageLayoutWidgets( - this.coreDataSource, - 'core', + const isDashboardV2Enabled = await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IS_DASHBOARD_V2_ENABLED, + workspaceId, + ); + + await seedPageLayoutWidgets({ + dataSource: this.coreDataSource, + schemaName: 'core', workspaceId, objectMetadataItems, - ); + isDashboardV2Enabled, + }); await this.devSeederDataService.seed({ schemaName: dataSourceMetadata.schema,