From f9da3735deea75dcd32cf0a387fccc2020b62b70 Mon Sep 17 00:00:00 2001 From: Thomas Trompette Date: Thu, 19 Jun 2025 15:26:00 +0200 Subject: [PATCH] Remove workflow feature flag (#12732) Removing workflows from the lab --- .../src/generated-metadata/graphql.ts | 8 +++- .../twenty-front/src/generated/graphql.tsx | 3 +- .../constants/DefaultRecordActionsConfig.tsx | 4 +- .../useRunWorkflowRecordAgnosticActions.tsx | 10 ----- .../types/ShouldBeRegisteredFunctionParams.ts | 1 - .../contexts/ActionMenuContextProvider.tsx | 9 +--- .../useShouldActionBeRegisteredParams.ts | 7 ---- .../hooks/useFilteredObjectMetadataItems.ts | 31 +++----------- .../hooks/useObjectMetadataItem.ts | 16 -------- .../hooks/useRecordShowContainerTabs.ts | 7 ++-- .../generated/mock-metadata-query-result.ts | 1 - .../src/testing/mock-data/users.ts | 4 -- .../constants/public-feature-flag.const.ts | 10 +---- .../enums/feature-flag-key.enum.ts | 1 - .../__tests__/feature-flag.service.spec.ts | 11 +---- .../validates/feature-flag.validate.spec.ts | 2 +- .../message-queue/drivers/bullmq.driver.ts | 2 +- .../twenty-config/config-variables.ts | 2 +- .../serverless-function.exception.ts | 1 - .../serverless-function.resolver.ts | 41 +------------------ ...ion-graphql-api-exception-handler.utils.ts | 1 - .../core/utils/seed-feature-flags.util.ts | 5 --- .../automated-trigger.module.ts | 8 ++-- .../database-event-trigger.listener.spec.ts | 18 -------- .../database-event-trigger.listener.ts | 11 +---- 25 files changed, 28 insertions(+), 186 deletions(-) diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index ef07a1e90..74cc90eb1 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -326,6 +326,7 @@ export type ClientConfig = { api: ApiConfig; authProviders: AuthProviders; billing: Billing; + calendarBookingPageId?: Maybe; canManageFeatureFlags: Scalars['Boolean']['output']; captcha: Captcha; chromeExtensionId?: Maybe; @@ -658,8 +659,7 @@ export enum FeatureFlagKey { IS_PERMISSIONS_V2_ENABLED = 'IS_PERMISSIONS_V2_ENABLED', IS_POSTGRESQL_INTEGRATION_ENABLED = 'IS_POSTGRESQL_INTEGRATION_ENABLED', IS_STRIPE_INTEGRATION_ENABLED = 'IS_STRIPE_INTEGRATION_ENABLED', - IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED', - IS_WORKFLOW_ENABLED = 'IS_WORKFLOW_ENABLED' + IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED' } export type Field = { @@ -1001,6 +1001,7 @@ export type Mutation = { signUp: AvailableWorkspacesAndAccessTokensOutput; signUpInNewWorkspace: SignUpOutput; signUpInWorkspace: SignUpOutput; + skipBookOnboardingStep: OnboardingStepSuccess; skipSyncEmailOnboardingStep: OnboardingStepSuccess; submitFormStep: Scalars['Boolean']['output']; switchToEnterprisePlan: BillingUpdateOutput; @@ -1574,6 +1575,7 @@ export type OnDbEventInput = { /** Onboarding status */ export enum OnboardingStatus { + BOOK_ONBOARDING = 'BOOK_ONBOARDING', COMPLETED = 'COMPLETED', INVITE_TEAM = 'INVITE_TEAM', PLAN_REQUIRED = 'PLAN_REQUIRED', @@ -1945,6 +1947,8 @@ export type Role = { export type RunWorkflowVersionInput = { /** Execution result in JSON format */ payload?: InputMaybe; + /** Workflow run ID */ + workflowRunId?: InputMaybe; /** Workflow version ID */ workflowVersionId: Scalars['String']['input']; }; diff --git a/packages/twenty-front/src/generated/graphql.tsx b/packages/twenty-front/src/generated/graphql.tsx index 490f67c86..ad41cef42 100644 --- a/packages/twenty-front/src/generated/graphql.tsx +++ b/packages/twenty-front/src/generated/graphql.tsx @@ -615,8 +615,7 @@ export enum FeatureFlagKey { IS_PERMISSIONS_V2_ENABLED = 'IS_PERMISSIONS_V2_ENABLED', IS_POSTGRESQL_INTEGRATION_ENABLED = 'IS_POSTGRESQL_INTEGRATION_ENABLED', IS_STRIPE_INTEGRATION_ENABLED = 'IS_STRIPE_INTEGRATION_ENABLED', - IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED', - IS_WORKFLOW_ENABLED = 'IS_WORKFLOW_ENABLED' + IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED' } export type Field = { diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig.tsx index f3f5b31b9..66e59e112 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig.tsx @@ -413,13 +413,11 @@ export const DEFAULT_RECORD_ACTIONS_CONFIG: Record< shouldBeRegistered: ({ objectMetadataItem, viewType, - isWorkflowEnabled, getTargetObjectReadPermission, }) => getTargetObjectReadPermission(CoreObjectNameSingular.Workflow) === true && (objectMetadataItem?.nameSingular !== CoreObjectNameSingular.Workflow || - viewType === ActionViewType.SHOW_PAGE) && - isWorkflowEnabled, + viewType === ActionViewType.SHOW_PAGE), availableOn: [ ActionViewType.INDEX_PAGE_NO_SELECTION, ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION, diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-agnostic-actions/run-workflow-actions/hooks/useRunWorkflowRecordAgnosticActions.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-agnostic-actions/run-workflow-actions/hooks/useRunWorkflowRecordAgnosticActions.tsx index f5debd10e..89a03c585 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-agnostic-actions/run-workflow-actions/hooks/useRunWorkflowRecordAgnosticActions.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-agnostic-actions/run-workflow-actions/hooks/useRunWorkflowRecordAgnosticActions.tsx @@ -4,18 +4,12 @@ import { ActionType } from '@/action-menu/actions/types/ActionType'; import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; import { useActiveWorkflowVersionsWithManualTrigger } from '@/workflow/hooks/useActiveWorkflowVersionsWithManualTrigger'; import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion'; -import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { msg } from '@lingui/core/macro'; import { useContext } from 'react'; import { capitalize, isDefined } from 'twenty-shared/utils'; import { IconSettingsAutomation } from 'twenty-ui/display'; -import { FeatureFlagKey } from '~/generated/graphql'; export const useRunWorkflowRecordAgnosticActions = () => { - const isWorkflowEnabled = useIsFeatureEnabled( - FeatureFlagKey.IS_WORKFLOW_ENABLED, - ); - const { actionMenuType } = useContext(ActionMenuContext); const { records: activeWorkflowVersions } = @@ -27,10 +21,6 @@ export const useRunWorkflowRecordAgnosticActions = () => { const { runWorkflowVersion } = useRunWorkflowVersion(); - if (!isWorkflowEnabled) { - return []; - } - return activeWorkflowVersions .map((activeWorkflowVersion, index) => { if (!isDefined(activeWorkflowVersion.workflow)) { diff --git a/packages/twenty-front/src/modules/action-menu/actions/types/ShouldBeRegisteredFunctionParams.ts b/packages/twenty-front/src/modules/action-menu/actions/types/ShouldBeRegisteredFunctionParams.ts index 01b8e7eea..7ed44f7e7 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/types/ShouldBeRegisteredFunctionParams.ts +++ b/packages/twenty-front/src/modules/action-menu/actions/types/ShouldBeRegisteredFunctionParams.ts @@ -8,7 +8,6 @@ import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow'; export type ShouldBeRegisteredFunctionParams = { objectMetadataItem?: ObjectMetadataItem; objectPermissions: ObjectPermissions; - isWorkflowEnabled: boolean; recordFilters?: RecordFilter[]; isShowPage?: boolean; isSoftDeleteFilterActive?: boolean; diff --git a/packages/twenty-front/src/modules/action-menu/contexts/ActionMenuContextProvider.tsx b/packages/twenty-front/src/modules/action-menu/contexts/ActionMenuContextProvider.tsx index 68718cdc0..a85911890 100644 --- a/packages/twenty-front/src/modules/action-menu/contexts/ActionMenuContextProvider.tsx +++ b/packages/twenty-front/src/modules/action-menu/contexts/ActionMenuContextProvider.tsx @@ -6,10 +6,8 @@ import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; -import { FeatureFlagKey } from '~/generated-metadata/graphql'; export const ActionMenuContextProvider = ({ children, @@ -19,10 +17,6 @@ export const ActionMenuContextProvider = ({ }: Omit & { children: React.ReactNode; }) => { - const isWorkflowEnabled = useIsFeatureEnabled( - FeatureFlagKey.IS_WORKFLOW_ENABLED, - ); - const contextStoreCurrentObjectMetadataItemId = useRecoilComponentValueV2( contextStoreCurrentObjectMetadataItemIdComponentState, ); @@ -39,7 +33,7 @@ export const ActionMenuContextProvider = ({ objectMetadataItem?.nameSingular === CoreObjectNameSingular.WorkflowRun || objectMetadataItem?.nameSingular === CoreObjectNameSingular.WorkflowVersion; - if (isWorkflowEnabled && isDefined(objectMetadataItem) && isWorkflowObject) { + if (isDefined(objectMetadataItem) && isWorkflowObject) { return ( { const objectMetadataItems = useRecoilValue(objectMetadataItemsState); - const isWorkflowEnabled = useIsFeatureEnabled( - FeatureFlagKey.IS_WORKFLOW_ENABLED, - ); - - const isWorkflowToBeFiltered = useCallback( - (nameSingular: string) => { - return ( - !isWorkflowEnabled && isWorkflowRelatedObjectMetadata(nameSingular) - ); - }, - [isWorkflowEnabled], - ); - const activeNonSystemObjectMetadataItems = useMemo( () => objectMetadataItems.filter( - ({ isActive, isSystem, nameSingular }) => - isActive && !isSystem && !isWorkflowToBeFiltered(nameSingular), + ({ isActive, isSystem }) => isActive && !isSystem, ), - [isWorkflowToBeFiltered, objectMetadataItems], + [objectMetadataItems], ); const activeObjectMetadataItems = useMemo( - () => - objectMetadataItems.filter( - ({ isActive, nameSingular }) => - isActive && !isWorkflowToBeFiltered(nameSingular), - ), - [isWorkflowToBeFiltered, objectMetadataItems], + () => objectMetadataItems.filter(({ isActive }) => isActive), + [objectMetadataItems], ); const alphaSortedActiveNonSystemObjectMetadataItems = diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts index 44bcbf456..e85f0ab2d 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts @@ -4,10 +4,7 @@ import { ObjectMetadataItemNotFoundError } from '@/object-metadata/errors/Object import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; -import { isWorkflowRelatedObjectMetadata } from '@/object-metadata/utils/isWorkflowRelatedObjectMetadata'; -import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { isDefined } from 'twenty-shared/utils'; -import { FeatureFlagKey } from '~/generated-metadata/graphql'; import { ObjectMetadataItemIdentifier } from '../types/ObjectMetadataItemIdentifier'; export const useObjectMetadataItem = ({ @@ -20,21 +17,8 @@ export const useObjectMetadataItem = ({ }), ); - const isWorkflowEnabled = useIsFeatureEnabled( - FeatureFlagKey.IS_WORKFLOW_ENABLED, - ); - - const isWorkflowToBeFiltered = - !isWorkflowEnabled && isWorkflowRelatedObjectMetadata(objectNameSingular); - const objectMetadataItems = useRecoilValue(objectMetadataItemsState); - if (isWorkflowToBeFiltered) { - throw new Error( - 'Workflow is not enabled. If you want to use it, please enable it in the lab.', - ); - } - if (!isDefined(objectMetadataItem)) { throw new ObjectMetadataItemNotFoundError( objectNameSingular, diff --git a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerTabs.ts b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerTabs.ts index e78755e5a..dbea4f953 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerTabs.ts +++ b/packages/twenty-front/src/modules/object-record/record-show/hooks/useRecordShowContainerTabs.ts @@ -20,7 +20,6 @@ import { IconSettings, } from 'twenty-ui/display'; import { FieldMetadataType } from '~/generated-metadata/graphql'; -import { FeatureFlagKey } from '~/generated/graphql'; export const useRecordShowContainerTabs = ( loading: boolean, @@ -155,7 +154,7 @@ export const useRecordShowContainerTabs = ( ifMobile: false, ifDesktop: false, ifInRightDrawer: false, - ifFeaturesDisabled: [FeatureFlagKey.IS_WORKFLOW_ENABLED], + ifFeaturesDisabled: [], ifRequiredObjectsInactive: [], ifRelationsMissing: [], }, @@ -178,7 +177,7 @@ export const useRecordShowContainerTabs = ( ifMobile: false, ifDesktop: false, ifInRightDrawer: false, - ifFeaturesDisabled: [FeatureFlagKey.IS_WORKFLOW_ENABLED], + ifFeaturesDisabled: [], ifRequiredObjectsInactive: [], ifRelationsMissing: [], }, @@ -200,7 +199,7 @@ export const useRecordShowContainerTabs = ( ifMobile: false, ifDesktop: false, ifInRightDrawer: false, - ifFeaturesDisabled: [FeatureFlagKey.IS_WORKFLOW_ENABLED], + ifFeaturesDisabled: [], ifRequiredObjectsInactive: [], ifRelationsMissing: [], }, diff --git a/packages/twenty-front/src/testing/mock-data/generated/mock-metadata-query-result.ts b/packages/twenty-front/src/testing/mock-data/generated/mock-metadata-query-result.ts index ffbcb62a3..16290a5c8 100644 --- a/packages/twenty-front/src/testing/mock-data/generated/mock-metadata-query-result.ts +++ b/packages/twenty-front/src/testing/mock-data/generated/mock-metadata-query-result.ts @@ -4,7 +4,6 @@ import { ObjectMetadataItemsQuery } from '~/generated-metadata/graphql'; // This file is not designed to be manually edited. // It's an extract from the dev seeded environment metadata call // TODO: automate the generation of this file -// ⚠️ WARNING ⚠️: Be sure to activate the workflow feature flag (IsWorkflowEnabled) before updating that mock. // prettier-ignore export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery = diff --git a/packages/twenty-front/src/testing/mock-data/users.ts b/packages/twenty-front/src/testing/mock-data/users.ts index e6da563ae..4ae052b3c 100644 --- a/packages/twenty-front/src/testing/mock-data/users.ts +++ b/packages/twenty-front/src/testing/mock-data/users.ts @@ -68,10 +68,6 @@ export const mockCurrentWorkspace: Workspace = { key: FeatureFlagKey.IS_POSTGRESQL_INTEGRATION_ENABLED, value: true, }, - { - key: FeatureFlagKey.IS_WORKFLOW_ENABLED, - value: true, - }, ], createdAt: '2023-04-26T10:23:42.33625+00:00', updatedAt: '2023-04-26T10:23:42.33625+00:00', diff --git a/packages/twenty-server/src/engine/core-modules/feature-flag/constants/public-feature-flag.const.ts b/packages/twenty-server/src/engine/core-modules/feature-flag/constants/public-feature-flag.const.ts index 2505b6cf2..30eb3371b 100644 --- a/packages/twenty-server/src/engine/core-modules/feature-flag/constants/public-feature-flag.const.ts +++ b/packages/twenty-server/src/engine/core-modules/feature-flag/constants/public-feature-flag.const.ts @@ -7,19 +7,11 @@ type FeatureFlagMetadata = { }; export type PublicFeatureFlag = { - key: Extract; + key: FeatureFlagKey; metadata: FeatureFlagMetadata; }; export const PUBLIC_FEATURE_FLAGS: PublicFeatureFlag[] = [ - { - key: FeatureFlagKey.IS_WORKFLOW_ENABLED, - metadata: { - label: 'Workflows', - description: 'Create custom workflows to automate your work.', - imagePath: 'https://twenty.com/images/lab/is-workflow-enabled.png', - }, - }, ...(process.env.CLOUDFLARE_API_KEY ? [ // { diff --git a/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts b/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts index 8da3100a6..304ad099c 100644 --- a/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts +++ b/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts @@ -2,7 +2,6 @@ export enum FeatureFlagKey { IS_AIRTABLE_INTEGRATION_ENABLED = 'IS_AIRTABLE_INTEGRATION_ENABLED', IS_POSTGRESQL_INTEGRATION_ENABLED = 'IS_POSTGRESQL_INTEGRATION_ENABLED', IS_STRIPE_INTEGRATION_ENABLED = 'IS_STRIPE_INTEGRATION_ENABLED', - IS_WORKFLOW_ENABLED = 'IS_WORKFLOW_ENABLED', IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED', IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED', IS_PERMISSIONS_V2_ENABLED = 'IS_PERMISSIONS_V2_ENABLED', diff --git a/packages/twenty-server/src/engine/core-modules/feature-flag/services/__tests__/feature-flag.service.spec.ts b/packages/twenty-server/src/engine/core-modules/feature-flag/services/__tests__/feature-flag.service.spec.ts index d892b3da7..64c263ae1 100644 --- a/packages/twenty-server/src/engine/core-modules/feature-flag/services/__tests__/feature-flag.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/feature-flag/services/__tests__/feature-flag.service.spec.ts @@ -41,7 +41,7 @@ describe('FeatureFlagService', () => { }; const workspaceId = 'workspace-id'; - const featureFlag = FeatureFlagKey.IS_WORKFLOW_ENABLED; + const featureFlag = FeatureFlagKey.IS_AI_ENABLED; beforeEach(async () => { jest.clearAllMocks(); @@ -130,12 +130,10 @@ describe('FeatureFlagService', () => { // Prepare mockWorkspaceFeatureFlagsMapCacheService.getWorkspaceFeatureFlagsMap.mockResolvedValue( { - [FeatureFlagKey.IS_WORKFLOW_ENABLED]: true, [FeatureFlagKey.IS_AI_ENABLED]: false, }, ); const mockFeatureFlags = [ - { key: FeatureFlagKey.IS_WORKFLOW_ENABLED, value: true }, { key: FeatureFlagKey.IS_AI_ENABLED, value: false }, ]; @@ -154,7 +152,6 @@ describe('FeatureFlagService', () => { it('should return a map of feature flags for a workspace', async () => { // Prepare const mockFeatureFlags = [ - { key: FeatureFlagKey.IS_WORKFLOW_ENABLED, value: true, workspaceId }, { key: FeatureFlagKey.IS_AI_ENABLED, value: false, workspaceId }, ]; @@ -165,7 +162,6 @@ describe('FeatureFlagService', () => { // Assert expect(result).toEqual({ - [FeatureFlagKey.IS_WORKFLOW_ENABLED]: true, [FeatureFlagKey.IS_AI_ENABLED]: false, }); }); @@ -174,10 +170,7 @@ describe('FeatureFlagService', () => { describe('enableFeatureFlags', () => { it('should enable multiple feature flags for a workspace', async () => { // Prepare - const keys = [ - FeatureFlagKey.IS_WORKFLOW_ENABLED, - FeatureFlagKey.IS_AI_ENABLED, - ]; + const keys = [FeatureFlagKey.IS_AI_ENABLED]; mockFeatureFlagRepository.upsert.mockResolvedValue({}); diff --git a/packages/twenty-server/src/engine/core-modules/feature-flag/validates/feature-flag.validate.spec.ts b/packages/twenty-server/src/engine/core-modules/feature-flag/validates/feature-flag.validate.spec.ts index 919f8e496..5bc5c308c 100644 --- a/packages/twenty-server/src/engine/core-modules/feature-flag/validates/feature-flag.validate.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/feature-flag/validates/feature-flag.validate.spec.ts @@ -6,7 +6,7 @@ describe('featureFlagValidator', () => { it('should not throw error if featureFlagKey is valid', () => { expect(() => featureFlagValidator.assertIsFeatureFlagKey( - 'IS_WORKFLOW_ENABLED', + 'IS_AI_ENABLED', new CustomException('Error', 'Error'), ), ).not.toThrow(); diff --git a/packages/twenty-server/src/engine/core-modules/message-queue/drivers/bullmq.driver.ts b/packages/twenty-server/src/engine/core-modules/message-queue/drivers/bullmq.driver.ts index a6da2c6fc..5c90b019d 100644 --- a/packages/twenty-server/src/engine/core-modules/message-queue/drivers/bullmq.driver.ts +++ b/packages/twenty-server/src/engine/core-modules/message-queue/drivers/bullmq.driver.ts @@ -1,8 +1,8 @@ import { OnModuleDestroy } from '@nestjs/common'; import { JobsOptions, MetricsTime, Queue, QueueOptions, Worker } from 'bullmq'; -import { v4 } from 'uuid'; import { isDefined } from 'twenty-shared/utils'; +import { v4 } from 'uuid'; import { QueueCronJobOptions, diff --git a/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts b/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts index 4c851638f..ca292d9e1 100644 --- a/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts +++ b/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts @@ -1042,7 +1042,7 @@ export class ConfigVariables { type: ConfigVariableType.NUMBER, }) @CastToPositiveNumber() - WORKFLOW_EXEC_THROTTLE_LIMIT = 100; + WORKFLOW_EXEC_THROTTLE_LIMIT = 10; @ConfigVariablesMetadata({ group: ConfigVariablesGroup.RateLimiting, diff --git a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.exception.ts b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.exception.ts index 191fa2c92..f05c16c84 100644 --- a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.exception.ts @@ -10,7 +10,6 @@ export class ServerlessFunctionException extends CustomException { export enum ServerlessFunctionExceptionCode { SERVERLESS_FUNCTION_NOT_FOUND = 'SERVERLESS_FUNCTION_NOT_FOUND', SERVERLESS_FUNCTION_VERSION_NOT_FOUND = 'SERVERLESS_FUNCTION_VERSION_NOT_FOUND', - FEATURE_FLAG_INVALID = 'FEATURE_FLAG_INVALID', SERVERLESS_FUNCTION_ALREADY_EXIST = 'SERVERLESS_FUNCTION_ALREADY_EXIST', SERVERLESS_FUNCTION_NOT_READY = 'SERVERLESS_FUNCTION_NOT_READY', SERVERLESS_FUNCTION_BUILDING = 'SERVERLESS_FUNCTION_BUILDING', diff --git a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.resolver.ts index c076e3c17..1a990c626 100644 --- a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.resolver.ts @@ -5,7 +5,6 @@ import { InjectRepository } from '@nestjs/typeorm'; import graphqlTypeJson from 'graphql-type-json'; import { Repository } from 'typeorm'; -import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; @@ -19,10 +18,6 @@ import { ServerlessFunctionIdInput } from 'src/engine/metadata-modules/serverles import { ServerlessFunctionDTO } from 'src/engine/metadata-modules/serverless-function/dtos/serverless-function.dto'; import { UpdateServerlessFunctionInput } from 'src/engine/metadata-modules/serverless-function/dtos/update-serverless-function.input'; import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity'; -import { - ServerlessFunctionException, - ServerlessFunctionExceptionCode, -} from 'src/engine/metadata-modules/serverless-function/serverless-function.exception'; import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service'; import { serverlessFunctionGraphQLApiExceptionHandler } from 'src/engine/metadata-modules/serverless-function/utils/serverless-function-graphql-api-exception-handler.utils'; @@ -37,29 +32,12 @@ export class ServerlessFunctionResolver { private readonly serverlessFunctionRepository: Repository, ) {} - async checkFeatureFlag(workspaceId: string) { - const isWorkflowEnabled = await this.featureFlagRepository.findOneBy({ - workspaceId, - key: FeatureFlagKey.IS_WORKFLOW_ENABLED, - value: true, - }); - - if (!isWorkflowEnabled) { - throw new ServerlessFunctionException( - `IS_WORKFLOW_ENABLED feature flag is not set to true for this workspace`, - ServerlessFunctionExceptionCode.FEATURE_FLAG_INVALID, - ); - } - } - @Query(() => ServerlessFunctionDTO) async findOneServerlessFunction( @Args('input') { id }: ServerlessFunctionIdInput, @AuthWorkspace() { id: workspaceId }: Workspace, ) { try { - await this.checkFeatureFlag(workspaceId); - return await this.serverlessFunctionRepository.findOneOrFail({ where: { id, @@ -76,8 +54,6 @@ export class ServerlessFunctionResolver { @AuthWorkspace() { id: workspaceId }: Workspace, ) { try { - await this.checkFeatureFlag(workspaceId); - return await this.serverlessFunctionService.findManyServerlessFunctions({ workspaceId, }); @@ -87,13 +63,8 @@ export class ServerlessFunctionResolver { } @Query(() => graphqlTypeJson) - async getAvailablePackages( - @Args('input') { id }: ServerlessFunctionIdInput, - @AuthWorkspace() { id: workspaceId }: Workspace, - ) { + async getAvailablePackages(@Args('input') { id }: ServerlessFunctionIdInput) { try { - await this.checkFeatureFlag(workspaceId); - return await this.serverlessFunctionService.getAvailablePackages(id); } catch (error) { serverlessFunctionGraphQLApiExceptionHandler(error); @@ -106,8 +77,6 @@ export class ServerlessFunctionResolver { @AuthWorkspace() { id: workspaceId }: Workspace, ) { try { - await this.checkFeatureFlag(workspaceId); - return await this.serverlessFunctionService.getServerlessFunctionSourceCode( workspaceId, input.id, @@ -124,8 +93,6 @@ export class ServerlessFunctionResolver { @AuthWorkspace() { id: workspaceId }: Workspace, ) { try { - await this.checkFeatureFlag(workspaceId); - return await this.serverlessFunctionService.deleteOneServerlessFunction({ id: input.id, workspaceId, @@ -142,8 +109,6 @@ export class ServerlessFunctionResolver { @AuthWorkspace() { id: workspaceId }: Workspace, ) { try { - await this.checkFeatureFlag(workspaceId); - return await this.serverlessFunctionService.updateOneServerlessFunction( input, workspaceId, @@ -160,8 +125,6 @@ export class ServerlessFunctionResolver { @AuthWorkspace() { id: workspaceId }: Workspace, ) { try { - await this.checkFeatureFlag(workspaceId); - return await this.serverlessFunctionService.createOneServerlessFunction( input, workspaceId, @@ -177,7 +140,6 @@ export class ServerlessFunctionResolver { @AuthWorkspace() { id: workspaceId }: Workspace, ) { try { - await this.checkFeatureFlag(workspaceId); const { id, payload, version } = input; return await this.serverlessFunctionService.executeOneServerlessFunction( @@ -197,7 +159,6 @@ export class ServerlessFunctionResolver { @AuthWorkspace() { id: workspaceId }: Workspace, ) { try { - await this.checkFeatureFlag(workspaceId); const { id } = input; return await this.serverlessFunctionService.publishOneServerlessFunction( diff --git a/packages/twenty-server/src/engine/metadata-modules/serverless-function/utils/serverless-function-graphql-api-exception-handler.utils.ts b/packages/twenty-server/src/engine/metadata-modules/serverless-function/utils/serverless-function-graphql-api-exception-handler.utils.ts index cd67c3cc5..a2b90a5b5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/serverless-function/utils/serverless-function-graphql-api-exception-handler.utils.ts +++ b/packages/twenty-server/src/engine/metadata-modules/serverless-function/utils/serverless-function-graphql-api-exception-handler.utils.ts @@ -19,7 +19,6 @@ export const serverlessFunctionGraphQLApiExceptionHandler = (error: any) => { throw new ConflictError(error.message); case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_READY: case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_BUILDING: - case ServerlessFunctionExceptionCode.FEATURE_FLAG_INVALID: case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_EXECUTION_LIMIT_REACHED: throw new ForbiddenError(error.message); case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_CODE_UNCHANGED: 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 3f4a4bcbe..24019f97e 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 @@ -30,11 +30,6 @@ export const seedFeatureFlags = async ( workspaceId: workspaceId, value: true, }, - { - key: FeatureFlagKey.IS_WORKFLOW_ENABLED, - workspaceId: workspaceId, - value: true, - }, { key: FeatureFlagKey.IS_UNIQUE_INDEXES_ENABLED, workspaceId: workspaceId, diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/automated-trigger.module.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/automated-trigger.module.ts index 130352611..bfb0d2ca8 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/automated-trigger.module.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/automated-trigger.module.ts @@ -1,17 +1,15 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { AutomatedTriggerWorkspaceService } from 'src/modules/workflow/workflow-trigger/automated-trigger/automated-trigger.workspace-service'; -import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; -import { DatabaseEventTriggerListener } from 'src/modules/workflow/workflow-trigger/automated-trigger/listeners/database-event-trigger.listener'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module'; +import { AutomatedTriggerWorkspaceService } from 'src/modules/workflow/workflow-trigger/automated-trigger/automated-trigger.workspace-service'; import { CronTriggerCronCommand } from 'src/modules/workflow/workflow-trigger/automated-trigger/crons/commands/cron-trigger.cron.command'; import { CronTriggerCronJob } from 'src/modules/workflow/workflow-trigger/automated-trigger/crons/jobs/cron-trigger.cron.job'; -import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module'; +import { DatabaseEventTriggerListener } from 'src/modules/workflow/workflow-trigger/automated-trigger/listeners/database-event-trigger.listener'; @Module({ imports: [ - FeatureFlagModule, TypeOrmModule.forFeature([Workspace], 'core'), WorkflowCommonModule, ], diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/listeners/__tests__/database-event-trigger.listener.spec.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/listeners/__tests__/database-event-trigger.listener.spec.ts index ba06f8227..994cd19e3 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/listeners/__tests__/database-event-trigger.listener.spec.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/listeners/__tests__/database-event-trigger.listener.spec.ts @@ -1,6 +1,5 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { AutomatedTriggerType } from 'src/modules/workflow/common/standard-objects/workflow-automated-trigger.workspace-entity'; @@ -12,7 +11,6 @@ describe('DatabaseEventTriggerListener', () => { let listener: DatabaseEventTriggerListener; let twentyORMGlobalManager: jest.Mocked; let messageQueueService: jest.Mocked; - let featureFlagService: jest.Mocked; const mockRepository = { find: jest.fn(), @@ -27,10 +25,6 @@ describe('DatabaseEventTriggerListener', () => { add: jest.fn(), } as any; - featureFlagService = { - isFeatureEnabled: jest.fn().mockResolvedValue(true), - } as any; - const module: TestingModule = await Test.createTestingModule({ providers: [ DatabaseEventTriggerListener, @@ -42,10 +36,6 @@ describe('DatabaseEventTriggerListener', () => { provide: MessageQueueService, useValue: messageQueueService, }, - { - provide: FeatureFlagService, - useValue: featureFlagService, - }, { provide: 'MESSAGE_QUEUE_workflow-queue', useValue: messageQueueService, @@ -303,14 +293,6 @@ describe('DatabaseEventTriggerListener', () => { ); }); - it('should ignore events when feature flag is disabled', async () => { - featureFlagService.isFeatureEnabled.mockResolvedValueOnce(false); - - await listener.handleObjectRecordUpdateEvent(mockPayload); - - expect(messageQueueService.add).not.toHaveBeenCalled(); - }); - it('should handle multiple events in a batch', async () => { const batchPayload = { ...mockPayload, diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/listeners/database-event-trigger.listener.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/listeners/database-event-trigger.listener.ts index be4055aeb..315529682 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/listeners/database-event-trigger.listener.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/listeners/database-event-trigger.listener.ts @@ -10,8 +10,6 @@ import { ObjectRecordDeleteEvent } from 'src/engine/core-modules/event-emitter/t import { ObjectRecordDestroyEvent } from 'src/engine/core-modules/event-emitter/types/object-record-destroy.event'; import { ObjectRecordNonDestructiveEvent } from 'src/engine/core-modules/event-emitter/types/object-record-non-destructive-event'; import { ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-update.event'; -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 { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; @@ -36,7 +34,6 @@ export class DatabaseEventTriggerListener { private readonly twentyORMGlobalManager: TwentyORMGlobalManager, @InjectMessageQueue(MessageQueue.workflowQueue) private readonly messageQueueService: MessageQueueService, - private readonly isFeatureFlagEnabledService: FeatureFlagService, private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService, ) {} @@ -231,13 +228,7 @@ export class DatabaseEventTriggerListener { return true; } - const isWorkflowEnabled = - await this.isFeatureFlagEnabledService.isFeatureEnabled( - FeatureFlagKey.IS_WORKFLOW_ENABLED, - workspaceId, - ); - - return !isWorkflowEnabled; + return false; } private async handleEvent({