From 43295017433470ebb3ca10bb0c258eb108375fff Mon Sep 17 00:00:00 2001 From: Thomas Trompette Date: Mon, 29 Sep 2025 15:45:51 +0200 Subject: [PATCH] Workflow run over a list of records + backfill availability on manual triggers (#14761) - Allow to run workflow on a list of records - Use new availability in manual trigger when iterator feature flag is enabled - Build a command to backfill availability in workflow trigger https://github.com/user-attachments/assets/d685c01f-4059-4647-92a1-f5f529b560cf --- .../hooks/useRunWorkflowRecordActions.tsx | 47 ++++++-- .../utils/isBulkRecordsManualTrigger.ts | 8 ++ .../utils/isGlobalManualTrigger.ts | 17 +++ ...ActiveWorkflowVersionsWithManualTrigger.ts | 29 +++-- .../components/WorkflowEditTriggerManual.tsx | 1 + .../WorkflowEditTriggerManualDeprecated.tsx | 4 + ...alTriggerDefaultSettingsDeprecated.test.ts | 112 ++++++++++-------- .../getTriggerDefaultDefinition.test.ts | 4 + .../utils/getManualTriggerDefaultSettings.ts | 3 + ...tManualTriggerDefaultSettingsDeprecated.ts | 9 ++ ...low-manual-trigger-availability.command.ts | 85 +++++++++++++ .../1-7/1-7-upgrade-version-command.module.ts | 11 +- .../resolvers/workflow-trigger.resolver.ts | 6 +- .../workflow-schema/workflow-schema.module.ts | 5 +- .../workflow-schema.workspace-service.ts | 25 ++-- .../workflow-runner/jobs/run-workflow.job.ts | 2 - .../workflow-runner/workflow-runner.module.ts | 2 - 17 files changed, 285 insertions(+), 85 deletions(-) create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/utils/isBulkRecordsManualTrigger.ts create mode 100644 packages/twenty-front/src/modules/action-menu/actions/record-actions/utils/isGlobalManualTrigger.ts create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-7/1-7-backfill-workflow-manual-trigger-availability.command.ts diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/run-workflow-actions/hooks/useRunWorkflowRecordActions.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/run-workflow-actions/hooks/useRunWorkflowRecordActions.tsx index 85c22e4b79..010110e58c 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/run-workflow-actions/hooks/useRunWorkflowRecordActions.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/run-workflow-actions/hooks/useRunWorkflowRecordActions.tsx @@ -1,4 +1,5 @@ import { Action } from '@/action-menu/actions/components/Action'; +import { isBulkRecordsManualTrigger } from '@/action-menu/actions/record-actions/utils/isBulkRecordsManualTrigger'; import { ActionScope } from '@/action-menu/actions/types/ActionScope'; import { ActionType } from '@/action-menu/actions/types/ActionType'; import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; @@ -10,9 +11,11 @@ import { useRunWorkflowVersion } from '@/workflow/hooks/useRunWorkflowVersion'; import { type WorkflowVersion } from '@/workflow/types/Workflow'; import { COMMAND_MENU_DEFAULT_ICON } from '@/workflow/workflow-trigger/constants/CommandMenuDefaultIcon'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useRecoilCallback } from 'recoil'; import { capitalize, isDefined } from 'twenty-shared/utils'; import { useIcons } from 'twenty-ui/display'; +import { FeatureFlagKey } from '~/generated/graphql'; export const useRunWorkflowRecordActions = ({ objectMetadataItem, @@ -22,6 +25,9 @@ export const useRunWorkflowRecordActions = ({ skip?: boolean; }) => { const { getIcon } = useIcons(); + const isIteratorEnabled = useIsFeatureEnabled( + FeatureFlagKey.IS_WORKFLOW_ITERATOR_ENABLED, + ); const contextStoreTargetedRecordsRule = useRecoilComponentValue( contextStoreTargetedRecordsRuleComponentState, ); @@ -45,23 +51,44 @@ export const useRunWorkflowRecordActions = ({ selectedRecordIds: string[], activeWorkflowVersion: WorkflowVersion, ) => { - for (const selectedRecordId of selectedRecordIds) { - const selectedRecord = snapshot - .getLoadable(recordStoreFamilyState(selectedRecordId)) - .getValue(); - - if (!isDefined(selectedRecord)) { - continue; - } + if ( + isIteratorEnabled && + isDefined(activeWorkflowVersion?.trigger) && + isBulkRecordsManualTrigger(activeWorkflowVersion.trigger) + ) { + const objectNamePlural = objectMetadataItem.namePlural; + const selectedRecords = selectedRecordIds + .map((recordId) => + snapshot.getLoadable(recordStoreFamilyState(recordId)).getValue(), + ) + .filter(isDefined); await runWorkflowVersion({ workflowId: activeWorkflowVersion.workflowId, workflowVersionId: activeWorkflowVersion.id, - payload: selectedRecord, + payload: { + [objectNamePlural]: selectedRecords, + }, }); + } else { + for (const selectedRecordId of selectedRecordIds) { + const selectedRecord = snapshot + .getLoadable(recordStoreFamilyState(selectedRecordId)) + .getValue(); + + if (!isDefined(selectedRecord)) { + continue; + } + + await runWorkflowVersion({ + workflowId: activeWorkflowVersion.workflowId, + workflowVersionId: activeWorkflowVersion.id, + payload: selectedRecord, + }); + } } }, - [runWorkflowVersion], + [runWorkflowVersion, isIteratorEnabled, objectMetadataItem], ); return activeWorkflowVersions diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/utils/isBulkRecordsManualTrigger.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/utils/isBulkRecordsManualTrigger.ts new file mode 100644 index 0000000000..4febce98d5 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/utils/isBulkRecordsManualTrigger.ts @@ -0,0 +1,8 @@ +import { type WorkflowTrigger } from '@/workflow/types/Workflow'; + +export const isBulkRecordsManualTrigger = (trigger: WorkflowTrigger) => { + return ( + trigger.type === 'MANUAL' && + trigger?.settings?.availability?.type === 'BULK_RECORDS' + ); +}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/utils/isGlobalManualTrigger.ts b/packages/twenty-front/src/modules/action-menu/actions/record-actions/utils/isGlobalManualTrigger.ts new file mode 100644 index 0000000000..f9d087f4df --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/utils/isGlobalManualTrigger.ts @@ -0,0 +1,17 @@ +import { type WorkflowTrigger } from '@/workflow/types/Workflow'; +import { isDefined } from 'twenty-shared/utils'; + +export const isGlobalManualTrigger = ( + trigger: WorkflowTrigger, + isIteratorEnabled: boolean, +) => { + if (trigger.type !== 'MANUAL') { + return false; + } + + if (isIteratorEnabled && isDefined(trigger.settings?.availability)) { + return trigger.settings.availability.type === 'GLOBAL'; + } + + return !isDefined(trigger.settings.objectType); +}; diff --git a/packages/twenty-front/src/modules/workflow/hooks/useActiveWorkflowVersionsWithManualTrigger.ts b/packages/twenty-front/src/modules/workflow/hooks/useActiveWorkflowVersionsWithManualTrigger.ts index 2607665a30..63b98a8342 100644 --- a/packages/twenty-front/src/modules/workflow/hooks/useActiveWorkflowVersionsWithManualTrigger.ts +++ b/packages/twenty-front/src/modules/workflow/hooks/useActiveWorkflowVersionsWithManualTrigger.ts @@ -1,3 +1,4 @@ +import { isGlobalManualTrigger } from '@/action-menu/actions/record-actions/utils/isGlobalManualTrigger'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; @@ -7,7 +8,9 @@ import { type ManualTriggerWorkflowVersion, type Workflow, } from '@/workflow/types/Workflow'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { isDefined } from 'twenty-shared/utils'; +import { FeatureFlagKey } from '~/generated/graphql'; export const useActiveWorkflowVersionsWithManualTrigger = ({ objectMetadataItem, @@ -16,6 +19,10 @@ export const useActiveWorkflowVersionsWithManualTrigger = ({ objectMetadataItem?: ObjectMetadataItem; skip?: boolean; }) => { + const isIteratorEnabled = useIsFeatureEnabled( + FeatureFlagKey.IS_WORKFLOW_ITERATOR_ENABLED, + ); + const filters = [ { status: { @@ -29,12 +36,20 @@ export const useActiveWorkflowVersionsWithManualTrigger = ({ }, ]; + const objectTypeFilter = isIteratorEnabled + ? { + trigger: { + like: `%"objectNameSingular": "${objectMetadataItem?.nameSingular}"%`, + }, + } + : { + trigger: { + like: `%"objectType": "${objectMetadataItem?.nameSingular}"%`, + }, + }; + if (isDefined(objectMetadataItem)) { - filters.push({ - trigger: { - like: `%"objectType": "${objectMetadataItem.nameSingular}"%`, - }, - }); + filters.push(objectTypeFilter); } const { objectMetadataItem: workflowVersionObjectMetadataItem } = @@ -64,8 +79,8 @@ export const useActiveWorkflowVersionsWithManualTrigger = ({ records: records.filter( (record) => record.status === 'ACTIVE' && - record.trigger?.type === 'MANUAL' && - !isDefined(record.trigger?.settings.objectType), + isDefined(record.trigger) && + isGlobalManualTrigger(record.trigger, isIteratorEnabled), ), }; } diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerManual.tsx b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerManual.tsx index 800ef0aaa2..5d21ffe5ff 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerManual.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerManual.tsx @@ -158,6 +158,7 @@ export const WorkflowEditTriggerManual = ({ type: availability.type, objectNameSingular, }, + objectType: objectNameSingular, outputSchema: {}, }, }); diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerManualDeprecated.tsx b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerManualDeprecated.tsx index 4d5d0fbdf1..446db6a32b 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerManualDeprecated.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerManualDeprecated.tsx @@ -197,6 +197,10 @@ export const WorkflowEditTriggerManualDeprecated = ({ ...trigger, settings: { ...trigger.settings, + availability: { + objectNameSingular: updatedObject, + type: 'SINGLE_RECORD', + }, objectType: updatedObject, outputSchema: {}, }, diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/__tests__/getManualTriggerDefaultSettingsDeprecated.test.ts b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/__tests__/getManualTriggerDefaultSettingsDeprecated.test.ts index b4833a29d3..209222da74 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/__tests__/getManualTriggerDefaultSettingsDeprecated.test.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/__tests__/getManualTriggerDefaultSettingsDeprecated.test.ts @@ -3,57 +3,71 @@ import { COMMAND_MENU_DEFAULT_ICON } from '@/workflow/workflow-trigger/constants import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems'; import { getManualTriggerDefaultSettingsDeprecated } from '../getManualTriggerDefaultSettingsDeprecated'; -it('returns settings for a manual trigger that can be activated from any where', () => { - expect( - getManualTriggerDefaultSettingsDeprecated({ - availability: 'EVERYWHERE', - activeNonSystemObjectMetadataItems: generatedMockObjectMetadataItems, - }), - ).toStrictEqual({ - objectType: undefined, - outputSchema: {}, - icon: COMMAND_MENU_DEFAULT_ICON, - isPinned: false, +describe('getManualTriggerDefaultSettingsDeprecated', () => { + it('returns settings for a manual trigger that can be activated from any where', () => { + expect( + getManualTriggerDefaultSettingsDeprecated({ + availability: 'EVERYWHERE', + activeNonSystemObjectMetadataItems: generatedMockObjectMetadataItems, + }), + ).toStrictEqual({ + objectType: undefined, + outputSchema: {}, + icon: COMMAND_MENU_DEFAULT_ICON, + isPinned: false, + availability: { + type: 'GLOBAL', + locations: [], + }, + }); }); -}); -it('returns settings for a manual trigger that can be activated from any where', () => { - expect( - getManualTriggerDefaultSettingsDeprecated({ - availability: 'WHEN_RECORD_SELECTED', - activeNonSystemObjectMetadataItems: generatedMockObjectMetadataItems, + it('returns settings for a manual trigger that can be activated from any where', () => { + expect( + getManualTriggerDefaultSettingsDeprecated({ + availability: 'WHEN_RECORD_SELECTED', + activeNonSystemObjectMetadataItems: generatedMockObjectMetadataItems, + icon: 'IconTest', + }), + ).toStrictEqual({ + objectType: generatedMockObjectMetadataItems[0].nameSingular, + outputSchema: {}, icon: 'IconTest', - }), - ).toStrictEqual({ - objectType: generatedMockObjectMetadataItems[0].nameSingular, - outputSchema: {}, - icon: 'IconTest', - isPinned: false, + isPinned: false, + availability: { + type: 'SINGLE_RECORD', + objectNameSingular: generatedMockObjectMetadataItems[0].nameSingular, + }, + }); + }); + + it('returns settings for WHEN_RECORD_SELECTED with default icon when no custom icon provided', () => { + expect( + getManualTriggerDefaultSettingsDeprecated({ + availability: 'WHEN_RECORD_SELECTED', + activeNonSystemObjectMetadataItems: generatedMockObjectMetadataItems, + }), + ).toStrictEqual({ + objectType: generatedMockObjectMetadataItems[0].nameSingular, + outputSchema: {}, + icon: COMMAND_MENU_DEFAULT_ICON, + isPinned: false, + availability: { + type: 'SINGLE_RECORD', + objectNameSingular: generatedMockObjectMetadataItems[0].nameSingular, + }, + }); + }); + + it('throws error for unsupported availability type', () => { + const invalidAvailability = + 'INVALID_AVAILABILITY' as WorkflowManualTriggerAvailability; + + expect(() => + getManualTriggerDefaultSettingsDeprecated({ + availability: invalidAvailability, + activeNonSystemObjectMetadataItems: generatedMockObjectMetadataItems, + }), + ).toThrow("Didn't expect to get here."); }); }); - -it('returns settings for WHEN_RECORD_SELECTED with default icon when no custom icon provided', () => { - expect( - getManualTriggerDefaultSettingsDeprecated({ - availability: 'WHEN_RECORD_SELECTED', - activeNonSystemObjectMetadataItems: generatedMockObjectMetadataItems, - }), - ).toStrictEqual({ - objectType: generatedMockObjectMetadataItems[0].nameSingular, - outputSchema: {}, - icon: COMMAND_MENU_DEFAULT_ICON, - isPinned: false, - }); -}); - -it('throws error for unsupported availability type', () => { - const invalidAvailability = - 'INVALID_AVAILABILITY' as WorkflowManualTriggerAvailability; - - expect(() => - getManualTriggerDefaultSettingsDeprecated({ - availability: invalidAvailability, - activeNonSystemObjectMetadataItems: generatedMockObjectMetadataItems, - }), - ).toThrow("Didn't expect to get here."); -}); diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/__tests__/getTriggerDefaultDefinition.test.ts b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/__tests__/getTriggerDefaultDefinition.test.ts index 518eef7a84..f75e4fe4dc 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/__tests__/getTriggerDefaultDefinition.test.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/__tests__/getTriggerDefaultDefinition.test.ts @@ -116,6 +116,10 @@ describe('getTriggerDefaultDefinition', () => { name: 'Launch manually', settings: { objectType: generatedMockObjectMetadataItems[0].nameSingular, + availability: { + objectNameSingular: generatedMockObjectMetadataItems[0].nameSingular, + type: 'SINGLE_RECORD', + }, outputSchema: {}, icon: COMMAND_MENU_DEFAULT_ICON, isPinned: false, diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getManualTriggerDefaultSettings.ts b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getManualTriggerDefaultSettings.ts index e329dc0a11..58228a47ea 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getManualTriggerDefaultSettings.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getManualTriggerDefaultSettings.ts @@ -17,6 +17,7 @@ export const getManualTriggerDefaultSettings = ({ switch (availabilityType) { case 'GLOBAL': { return { + objectType: undefined, availability: { type: 'GLOBAL', locations: undefined, @@ -28,6 +29,7 @@ export const getManualTriggerDefaultSettings = ({ } case 'SINGLE_RECORD': { return { + objectType: activeNonSystemObjectMetadataItems[0].nameSingular, availability: { type: 'SINGLE_RECORD', objectNameSingular: @@ -40,6 +42,7 @@ export const getManualTriggerDefaultSettings = ({ } case 'BULK_RECORDS': { return { + objectType: activeNonSystemObjectMetadataItems[0].nameSingular, availability: { type: 'BULK_RECORDS', objectNameSingular: diff --git a/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getManualTriggerDefaultSettingsDeprecated.ts b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getManualTriggerDefaultSettingsDeprecated.ts index 2a5cfe1ee9..fdc33727f2 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getManualTriggerDefaultSettingsDeprecated.ts +++ b/packages/twenty-front/src/modules/workflow/workflow-trigger/utils/getManualTriggerDefaultSettingsDeprecated.ts @@ -24,6 +24,10 @@ export const getManualTriggerDefaultSettingsDeprecated = ({ outputSchema: {}, icon: icon || COMMAND_MENU_DEFAULT_ICON, isPinned: isPinned || false, + availability: { + type: 'GLOBAL', + locations: [], + }, }; } case 'WHEN_RECORD_SELECTED': { @@ -32,6 +36,11 @@ export const getManualTriggerDefaultSettingsDeprecated = ({ outputSchema: {}, icon: icon || COMMAND_MENU_DEFAULT_ICON, isPinned: isPinned || false, + availability: { + type: 'SINGLE_RECORD', + objectNameSingular: + activeNonSystemObjectMetadataItems[0].nameSingular, + }, }; } } diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-7/1-7-backfill-workflow-manual-trigger-availability.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-7/1-7-backfill-workflow-manual-trigger-availability.command.ts new file mode 100644 index 0000000000..dcd478d7b1 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-7/1-7-backfill-workflow-manual-trigger-availability.command.ts @@ -0,0 +1,85 @@ +import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; + +import { Command } from 'nest-commander'; +import { isDefined } from 'twenty-shared/utils'; +import { DataSource, Repository } from 'typeorm'; + +import { + ActiveOrSuspendedWorkspacesMigrationCommandRunner, + type RunOnWorkspaceArgs, +} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util'; +import { WorkflowTriggerType } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type'; + +@Command({ + name: 'upgrade:1-7:backfill-workflow-manual-trigger-availability', + description: + 'Backfill workflow manual trigger availability based on objectType', +}) +export class BackfillWorkflowManualTriggerAvailabilityCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { + constructor( + @InjectRepository(Workspace) + protected readonly workspaceRepository: Repository, + protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, + @InjectDataSource() + private readonly coreDataSource: DataSource, + ) { + super(workspaceRepository, twentyORMGlobalManager); + } + + override async runOnWorkspace({ + workspaceId, + }: RunOnWorkspaceArgs): Promise { + const schemaName = getWorkspaceSchemaName(workspaceId); + + const workflowVersions = await this.coreDataSource.query( + `SELECT * FROM ${schemaName}."workflowVersion"`, + ); + + for (const workflowVersion of workflowVersions) { + const { trigger } = workflowVersion; + + if (trigger.type !== WorkflowTriggerType.MANUAL) { + continue; + } + + const availability = trigger.settings.availability; + const objectType = trigger.settings.objectType; + + if (isDefined(availability)) { + continue; + } + + const newAvailability = objectType + ? { + type: 'SINGLE_RECORD', + objectNameSingular: objectType, + } + : { + type: 'GLOBAL', + locations: [], + }; + + const updatedTrigger = { + ...trigger, + settings: { + ...trigger.settings, + availability: newAvailability, + }, + }; + + this.logger.log( + `Updating workflow version ${workflowVersion.id} with new availability ${JSON.stringify( + newAvailability, + )}`, + ); + + await this.coreDataSource.query( + `UPDATE ${schemaName}."workflowVersion" SET trigger = $1 WHERE id = $2`, + [updatedTrigger, workflowVersion.id], + ); + } + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-7/1-7-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-7/1-7-upgrade-version-command.module.ts index 87c5cb2237..b85842b572 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-7/1-7-upgrade-version-command.module.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-7/1-7-upgrade-version-command.module.ts @@ -1,13 +1,20 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { BackfillWorkflowManualTriggerAvailabilityCommand } from 'src/database/commands/upgrade-version-command/1-7/1-7-backfill-workflow-manual-trigger-availability.command'; import { RegeneratePersonSearchVectorWithPhonesCommand } from 'src/database/commands/upgrade-version-command/1-7/1-7-regenerate-person-search-vector-with-phones.command'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; @Module({ imports: [TypeOrmModule.forFeature([Workspace]), WorkspaceDataSourceModule], - providers: [RegeneratePersonSearchVectorWithPhonesCommand], - exports: [RegeneratePersonSearchVectorWithPhonesCommand], + providers: [ + RegeneratePersonSearchVectorWithPhonesCommand, + BackfillWorkflowManualTriggerAvailabilityCommand, + ], + exports: [ + RegeneratePersonSearchVectorWithPhonesCommand, + BackfillWorkflowManualTriggerAvailabilityCommand, + ], }) export class V1_7_UpgradeVersionCommandModule {} diff --git a/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-trigger.resolver.ts b/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-trigger.resolver.ts index e007a3d975..8f46be1a6f 100644 --- a/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-trigger.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-trigger.resolver.ts @@ -44,7 +44,7 @@ export class WorkflowTriggerResolver { @Args('workflowVersionId', { type: () => UUIDScalarType }) workflowVersionId: string, ) { - return await this.workflowTriggerWorkspaceService.activateWorkflowVersion( + return this.workflowTriggerWorkspaceService.activateWorkflowVersion( workflowVersionId, ); } @@ -54,7 +54,7 @@ export class WorkflowTriggerResolver { @Args('workflowVersionId', { type: () => UUIDScalarType }) workflowVersionId: string, ) { - return await this.workflowTriggerWorkspaceService.deactivateWorkflowVersion( + return this.workflowTriggerWorkspaceService.deactivateWorkflowVersion( workflowVersionId, ); } @@ -78,7 +78,7 @@ export class WorkflowTriggerResolver { }, }); - return await this.workflowTriggerWorkspaceService.runWorkflowVersion({ + return this.workflowTriggerWorkspaceService.runWorkflowVersion({ workflowVersionId, workflowRunId: workflowRunId ?? undefined, payload: payload ?? {}, diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.module.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.module.ts index a2ceb12a3e..57ab2aa72c 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.module.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.module.ts @@ -1,10 +1,11 @@ import { Module } from '@nestjs/common'; -import { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service'; +import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module'; +import { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service'; @Module({ - imports: [WorkflowCommonModule], + imports: [WorkflowCommonModule, FeatureFlagModule], providers: [WorkflowSchemaWorkspaceService], exports: [WorkflowSchemaWorkspaceService], }) diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service.ts index 1197c9bd9f..bebac2992d 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service.ts @@ -12,6 +12,8 @@ import { import { type DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { checkStringIsDatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/utils/check-string-is-database-event-action'; +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 { generateFakeValue } from 'src/engine/utils/generate-fake-value'; import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; import { DEFAULT_ITERATOR_CURRENT_ITEM } from 'src/modules/workflow/workflow-builder/workflow-schema/constants/default-iterator-current-item.const'; @@ -38,6 +40,7 @@ import { export class WorkflowSchemaWorkspaceService { constructor( private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService, + private readonly featureFlagService: FeatureFlagService, ) {} async computeStepOutputSchema({ @@ -59,8 +62,21 @@ export class WorkflowSchemaWorkspaceService { }); } case WorkflowTriggerType.MANUAL: { + const isIteratorEnabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IS_WORKFLOW_ITERATOR_ENABLED, + workspaceId, + ); + const { objectType, availability } = step.settings; + if (isDefined(availability) && isIteratorEnabled) { + return this.computeTriggerOutputSchemaFromAvailability({ + availability, + workspaceId, + }); + } + // TODO: to be deprecated once all triggers are migrated to the new availability type if (isDefined(objectType)) { return this.computeRecordOutputSchema({ @@ -69,13 +85,6 @@ export class WorkflowSchemaWorkspaceService { }); } - if (isDefined(availability)) { - return this.computeTriggerOutputSchemaFromAvailability({ - availability, - workspaceId, - }); - } - return {}; } case WorkflowTriggerType.WEBHOOK: @@ -291,7 +300,7 @@ export class WorkflowSchemaWorkspaceService { ); return { - [availability.objectNameSingular]: { + [objectMetadataInfo.objectMetadataItemWithFieldsMaps.namePlural]: { label: objectMetadataInfo.objectMetadataItemWithFieldsMaps.labelPlural, isLeaf: true, diff --git a/packages/twenty-server/src/modules/workflow/workflow-runner/jobs/run-workflow.job.ts b/packages/twenty-server/src/modules/workflow/workflow-runner/jobs/run-workflow.job.ts index 721aa2cd2a..26778836bd 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-runner/jobs/run-workflow.job.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-runner/jobs/run-workflow.job.ts @@ -2,7 +2,6 @@ import { Scope } from '@nestjs/common'; import { isDefined } from 'twenty-shared/utils'; -import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; @@ -37,7 +36,6 @@ export class RunWorkflowJob { private readonly twentyConfigService: TwentyConfigService, private readonly metricsService: MetricsService, private readonly workflowRunQueueWorkspaceService: WorkflowRunQueueWorkspaceService, - private readonly featureFlagService: FeatureFlagService, ) {} @Process(RunWorkflowJob.name) diff --git a/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-runner.module.ts b/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-runner.module.ts index 5ec1a5a700..7d23f4292c 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-runner.module.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-runner.module.ts @@ -1,7 +1,6 @@ import { Module } from '@nestjs/common'; import { BillingModule } from 'src/engine/core-modules/billing/billing.module'; -import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module'; import { ThrottlerModule } from 'src/engine/core-modules/throttler/throttler.module'; import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module'; @@ -21,7 +20,6 @@ import { WorkflowRunnerWorkspaceService } from 'src/modules/workflow/workflow-ru WorkflowRunModule, MetricsModule, WorkflowRunQueueModule, - FeatureFlagModule, WorkflowVersionStepModule, ], providers: [WorkflowRunnerWorkspaceService, RunWorkflowJob],