diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts index ff51c318e..955031ba4 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids.ts @@ -425,6 +425,7 @@ export const WORKFLOW_VERSION_STANDARD_FIELD_IDS = { workflow: '20202020-afa3-46c3-91b0-0631ca6aa1c8', trigger: '20202020-4eae-43e7-86e0-212b41a30b48', runs: '20202020-1d08-46df-901a-85045f18099a', + steps: '20202020-5988-4a64-b94a-1f9b7b989039', }; export const WORKSPACE_MEMBER_STANDARD_FIELD_IDS = { diff --git a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-version.workspace-entity.ts b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-version.workspace-entity.ts index ec8a8206b..d8b0be0c5 100644 --- a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-version.workspace-entity.ts +++ b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-version.workspace-entity.ts @@ -21,6 +21,7 @@ import { import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; import { WorkflowRunWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity'; import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity'; +import { WorkflowStep } from 'src/modules/workflow/common/types/workflow-step.type'; import { WorkflowTrigger } from 'src/modules/workflow/common/types/workflow-trigger.type'; @WorkspaceEntity({ @@ -51,11 +52,19 @@ export class WorkflowVersionWorkspaceEntity extends BaseWorkspaceEntity { type: FieldMetadataType.RAW_JSON, label: 'Version trigger', description: 'Json object to provide trigger', - icon: 'IconPlayerPlay', }) @WorkspaceIsNullable() trigger: WorkflowTrigger | null; + @WorkspaceField({ + standardId: WORKFLOW_VERSION_STANDARD_FIELD_IDS.steps, + type: FieldMetadataType.RAW_JSON, + label: 'Version steps', + description: 'Json object to provide steps', + }) + @WorkspaceIsNullable() + steps: WorkflowStep[] | null; + // Relations @WorkspaceRelation({ standardId: WORKFLOW_VERSION_STANDARD_FIELD_IDS.workflow, diff --git a/packages/twenty-server/src/modules/workflow/common/types/workflow-action.type.ts b/packages/twenty-server/src/modules/workflow/common/types/workflow-action.type.ts deleted file mode 100644 index 3c380a490..000000000 --- a/packages/twenty-server/src/modules/workflow/common/types/workflow-action.type.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { WorkflowCodeSettingsType } from 'src/modules/workflow/common/types/workflow-settings.type'; - -export enum WorkflowActionType { - CODE = 'CODE', -} - -type CommonWorkflowAction = { - name: string; - displayName: string; - valid: boolean; -}; - -type WorkflowCodeAction = CommonWorkflowAction & { - type: WorkflowActionType.CODE; - settings: WorkflowCodeSettingsType; -}; - -export type WorkflowAction = WorkflowCodeAction & { - nextAction: WorkflowAction; -}; diff --git a/packages/twenty-server/src/modules/workflow/common/types/workflow-settings.type.ts b/packages/twenty-server/src/modules/workflow/common/types/workflow-settings.type.ts index 55c4d5957..dde3025df 100644 --- a/packages/twenty-server/src/modules/workflow/common/types/workflow-settings.type.ts +++ b/packages/twenty-server/src/modules/workflow/common/types/workflow-settings.type.ts @@ -1,4 +1,4 @@ -type WorkflowBaseSettingsType = { +type BaseWorkflowSettings = { errorHandlingOptions: { retryOnFailure: { value: boolean; @@ -9,6 +9,6 @@ type WorkflowBaseSettingsType = { }; }; -export type WorkflowCodeSettingsType = WorkflowBaseSettingsType & { +export type WorkflowCodeSettings = BaseWorkflowSettings & { serverlessFunctionId: string; }; diff --git a/packages/twenty-server/src/modules/workflow/common/types/workflow-step.type.ts b/packages/twenty-server/src/modules/workflow/common/types/workflow-step.type.ts new file mode 100644 index 000000000..ef3c70696 --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/common/types/workflow-step.type.ts @@ -0,0 +1,18 @@ +import { WorkflowCodeSettings } from 'src/modules/workflow/common/types/workflow-settings.type'; + +export enum WorkflowStepType { + CODE_ACTION = 'CODE_ACTION', +} + +type BaseWorkflowStep = { + id: string; + name: string; + valid: boolean; +}; + +export type WorkflowCodeStep = BaseWorkflowStep & { + type: WorkflowStepType.CODE_ACTION; + settings: WorkflowCodeSettings; +}; + +export type WorkflowStep = WorkflowCodeStep; diff --git a/packages/twenty-server/src/modules/workflow/common/types/workflow-trigger.type.ts b/packages/twenty-server/src/modules/workflow/common/types/workflow-trigger.type.ts index 66f981cf8..b2ca87bc9 100644 --- a/packages/twenty-server/src/modules/workflow/common/types/workflow-trigger.type.ts +++ b/packages/twenty-server/src/modules/workflow/common/types/workflow-trigger.type.ts @@ -1,20 +1,17 @@ -import { WorkflowAction } from 'src/modules/workflow/common/types/workflow-action.type'; - export enum WorkflowTriggerType { DATABASE_EVENT = 'DATABASE_EVENT', } type BaseTrigger = { + name: string; type: WorkflowTriggerType; input?: object; - nextAction?: WorkflowAction; }; export type WorkflowDatabaseEventTrigger = BaseTrigger & { type: WorkflowTriggerType.DATABASE_EVENT; settings: { eventName: string; - triggerName: string; }; }; diff --git a/packages/twenty-server/src/modules/workflow/workflow-action-executor/workflow-action-executor.exception.ts b/packages/twenty-server/src/modules/workflow/workflow-action-executor/workflow-action-executor.exception.ts deleted file mode 100644 index 83d615058..000000000 --- a/packages/twenty-server/src/modules/workflow/workflow-action-executor/workflow-action-executor.exception.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { CustomException } from 'src/utils/custom-exception'; - -export class WorkflowActionExecutorException extends CustomException { - code: WorkflowActionExecutorExceptionCode; - constructor(message: string, code: WorkflowActionExecutorExceptionCode) { - super(message, code); - } -} - -export enum WorkflowActionExecutorExceptionCode { - SCOPED_WORKSPACE_NOT_FOUND = 'SCOPED_WORKSPACE_NOT_FOUND', -} diff --git a/packages/twenty-server/src/modules/workflow/workflow-action-executor/workflow-action-executor.factory.ts b/packages/twenty-server/src/modules/workflow/workflow-action-executor/workflow-action-executor.factory.ts deleted file mode 100644 index 485bb8133..000000000 --- a/packages/twenty-server/src/modules/workflow/workflow-action-executor/workflow-action-executor.factory.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { WorkflowActionType } from 'src/modules/workflow/common/types/workflow-action.type'; -import { WorkflowActionExecutor } from 'src/modules/workflow/workflow-action-executor/workflow-action-executor.interface'; -import { CodeWorkflowActionExecutor } from 'src/modules/workflow/workflow-action-executor/workflow-action-executors/code-workflow-action-executor'; - -@Injectable() -export class WorkflowActionExecutorFactory { - constructor( - private readonly codeWorkflowActionExecutor: CodeWorkflowActionExecutor, - ) {} - - get(actionType: WorkflowActionType): WorkflowActionExecutor { - switch (actionType) { - case WorkflowActionType.CODE: - return this.codeWorkflowActionExecutor; - default: - throw new Error( - `Workflow action executor not found for action type '${actionType}'`, - ); - } - } -} diff --git a/packages/twenty-server/src/modules/workflow/workflow-action-executor/workflow-action-executor.module.ts b/packages/twenty-server/src/modules/workflow/workflow-action-executor/workflow-action-executor.module.ts deleted file mode 100644 index c827d1a05..000000000 --- a/packages/twenty-server/src/modules/workflow/workflow-action-executor/workflow-action-executor.module.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Module } from '@nestjs/common'; - -import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless-function/serverless-function.module'; -import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; -import { WorkflowActionExecutorFactory } from 'src/modules/workflow/workflow-action-executor/workflow-action-executor.factory'; -import { CodeWorkflowActionExecutor } from 'src/modules/workflow/workflow-action-executor/workflow-action-executors/code-workflow-action-executor'; - -@Module({ - imports: [ServerlessFunctionModule], - providers: [ - WorkflowActionExecutorFactory, - CodeWorkflowActionExecutor, - ScopedWorkspaceContextFactory, - ], - exports: [WorkflowActionExecutorFactory], -}) -export class WorkflowActionExecutorModule {} diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-executor.module.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-executor.module.ts index 43d9a6ad6..d01b7aa11 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-executor.module.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-executor.module.ts @@ -1,11 +1,11 @@ import { Module } from '@nestjs/common'; import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module'; -import { WorkflowActionExecutorModule } from 'src/modules/workflow/workflow-action-executor/workflow-action-executor.module'; import { WorkflowExecutorWorkspaceService } from 'src/modules/workflow/workflow-executor/workflow-executor.workspace-service'; +import { WorkflowStepExecutorModule } from 'src/modules/workflow/workflow-step-executor/workflow-step-executor.module'; @Module({ - imports: [WorkflowCommonModule, WorkflowActionExecutorModule], + imports: [WorkflowCommonModule, WorkflowStepExecutorModule], providers: [WorkflowExecutorWorkspaceService], exports: [WorkflowExecutorWorkspaceService], }) diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-executor.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-executor.workspace-service.ts index 040805686..b157a8d14 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-executor.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-executor.workspace-service.ts @@ -1,11 +1,11 @@ import { Injectable } from '@nestjs/common'; -import { WorkflowAction } from 'src/modules/workflow/common/types/workflow-action.type'; -import { WorkflowActionExecutorFactory } from 'src/modules/workflow/workflow-action-executor/workflow-action-executor.factory'; +import { WorkflowStep } from 'src/modules/workflow/common/types/workflow-step.type'; import { WorkflowExecutorException, WorkflowExecutorExceptionCode, } from 'src/modules/workflow/workflow-executor/workflow-executor.exception'; +import { WorkflowStepExecutorFactory } from 'src/modules/workflow/workflow-step-executor/workflow-step-executor.factory'; const MAX_RETRIES_ON_FAILURE = 3; @@ -17,36 +17,41 @@ export type WorkflowExecutionOutput = { @Injectable() export class WorkflowExecutorWorkspaceService { constructor( - private readonly workflowActionExecutorFactory: WorkflowActionExecutorFactory, + private readonly workflowStepExecutorFactory: WorkflowStepExecutorFactory, ) {} async execute({ - action, + currentStepIndex, + steps, payload, attemptCount = 1, }: { - action?: WorkflowAction; + currentStepIndex: number; + steps: WorkflowStep[]; payload?: object; attemptCount?: number; }): Promise { - if (!action) { + if (currentStepIndex >= steps.length) { return { data: payload, }; } - const workflowActionExecutor = this.workflowActionExecutorFactory.get( - action.type, + const step = steps[currentStepIndex]; + + const workflowStepExecutor = this.workflowStepExecutorFactory.get( + step.type, ); - const result = await workflowActionExecutor.execute({ - action, + const result = await workflowStepExecutor.execute({ + step, payload, }); if (result.data) { return await this.execute({ - action: action.nextAction, + currentStepIndex: currentStepIndex + 1, + steps, payload: result.data, }); } @@ -58,19 +63,21 @@ export class WorkflowExecutorWorkspaceService { ); } - if (action.settings.errorHandlingOptions.continueOnFailure.value) { + if (step.settings.errorHandlingOptions.continueOnFailure.value) { return await this.execute({ - action: action.nextAction, + currentStepIndex: currentStepIndex + 1, + steps, payload, }); } if ( - action.settings.errorHandlingOptions.retryOnFailure.value && + step.settings.errorHandlingOptions.retryOnFailure.value && attemptCount < MAX_RETRIES_ON_FAILURE ) { return await this.execute({ - action, + currentStepIndex, + steps, payload, attemptCount: attemptCount + 1, }); 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 619d4babf..4762a6be7 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 @@ -38,7 +38,8 @@ export class RunWorkflowJob { try { await this.workflowExecutorWorkspaceService.execute({ - action: workflowVersion.trigger.nextAction, + currentStepIndex: 0, + steps: workflowVersion.steps || [], payload, }); diff --git a/packages/twenty-server/src/modules/workflow/workflow-step-executor/workflow-step-executor.exception.ts b/packages/twenty-server/src/modules/workflow/workflow-step-executor/workflow-step-executor.exception.ts new file mode 100644 index 000000000..327a472f1 --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-step-executor/workflow-step-executor.exception.ts @@ -0,0 +1,13 @@ +import { CustomException } from 'src/utils/custom-exception'; + +export class WorkflowStepExecutorException extends CustomException { + code: WorkflowStepExecutorExceptionCode; + constructor(message: string, code: WorkflowStepExecutorExceptionCode) { + super(message, code); + } +} + +export enum WorkflowStepExecutorExceptionCode { + SCOPED_WORKSPACE_NOT_FOUND = 'SCOPED_WORKSPACE_NOT_FOUND', + INVALID_STEP_TYPE = 'INVALID_STEP_TYPE', +} diff --git a/packages/twenty-server/src/modules/workflow/workflow-step-executor/workflow-step-executor.factory.ts b/packages/twenty-server/src/modules/workflow/workflow-step-executor/workflow-step-executor.factory.ts new file mode 100644 index 000000000..72f46451e --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-step-executor/workflow-step-executor.factory.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@nestjs/common'; + +import { WorkflowStepType } from 'src/modules/workflow/common/types/workflow-step.type'; +import { + WorkflowStepExecutorException, + WorkflowStepExecutorExceptionCode, +} from 'src/modules/workflow/workflow-step-executor/workflow-step-executor.exception'; +import { WorkflowStepExecutor } from 'src/modules/workflow/workflow-step-executor/workflow-step-executor.interface'; +import { CodeActionExecutor } from 'src/modules/workflow/workflow-step-executor/workflow-step-executors/code-action-executor'; + +@Injectable() +export class WorkflowStepExecutorFactory { + constructor(private readonly codeActionExecutor: CodeActionExecutor) {} + + get(stepType: WorkflowStepType): WorkflowStepExecutor { + switch (stepType) { + case WorkflowStepType.CODE_ACTION: + return this.codeActionExecutor; + default: + throw new WorkflowStepExecutorException( + `Workflow step executor not found for step type '${stepType}'`, + WorkflowStepExecutorExceptionCode.INVALID_STEP_TYPE, + ); + } + } +} diff --git a/packages/twenty-server/src/modules/workflow/workflow-action-executor/workflow-action-executor.interface.ts b/packages/twenty-server/src/modules/workflow/workflow-step-executor/workflow-step-executor.interface.ts similarity index 50% rename from packages/twenty-server/src/modules/workflow/workflow-action-executor/workflow-action-executor.interface.ts rename to packages/twenty-server/src/modules/workflow/workflow-step-executor/workflow-step-executor.interface.ts index ea2adfdcf..169ebc2ca 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-action-executor/workflow-action-executor.interface.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-step-executor/workflow-step-executor.interface.ts @@ -1,12 +1,12 @@ -import { WorkflowAction } from 'src/modules/workflow/common/types/workflow-action.type'; import { WorkflowResult } from 'src/modules/workflow/common/types/workflow-result.type'; +import { WorkflowStep } from 'src/modules/workflow/common/types/workflow-step.type'; -export interface WorkflowActionExecutor { +export interface WorkflowStepExecutor { execute({ - action, + step, payload, }: { - action: WorkflowAction; + step: WorkflowStep; payload?: object; }): Promise; } diff --git a/packages/twenty-server/src/modules/workflow/workflow-step-executor/workflow-step-executor.module.ts b/packages/twenty-server/src/modules/workflow/workflow-step-executor/workflow-step-executor.module.ts new file mode 100644 index 000000000..293a3614b --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-step-executor/workflow-step-executor.module.ts @@ -0,0 +1,17 @@ +import { Module } from '@nestjs/common'; + +import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless-function/serverless-function.module'; +import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; +import { WorkflowStepExecutorFactory } from 'src/modules/workflow/workflow-step-executor/workflow-step-executor.factory'; +import { CodeActionExecutor } from 'src/modules/workflow/workflow-step-executor/workflow-step-executors/code-action-executor'; + +@Module({ + imports: [ServerlessFunctionModule], + providers: [ + WorkflowStepExecutorFactory, + CodeActionExecutor, + ScopedWorkspaceContextFactory, + ], + exports: [WorkflowStepExecutorFactory], +}) +export class WorkflowStepExecutorModule {} diff --git a/packages/twenty-server/src/modules/workflow/workflow-action-executor/workflow-action-executors/code-workflow-action-executor.ts b/packages/twenty-server/src/modules/workflow/workflow-step-executor/workflow-step-executors/code-action-executor.ts similarity index 60% rename from packages/twenty-server/src/modules/workflow/workflow-action-executor/workflow-action-executors/code-workflow-action-executor.ts rename to packages/twenty-server/src/modules/workflow/workflow-step-executor/workflow-step-executors/code-action-executor.ts index cd11dedbf..ae8b85754 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-action-executor/workflow-action-executors/code-workflow-action-executor.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-step-executor/workflow-step-executors/code-action-executor.ts @@ -2,39 +2,39 @@ import { Injectable } from '@nestjs/common'; import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service'; import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; -import { WorkflowAction } from 'src/modules/workflow/common/types/workflow-action.type'; import { WorkflowResult } from 'src/modules/workflow/common/types/workflow-result.type'; +import { WorkflowCodeStep } from 'src/modules/workflow/common/types/workflow-step.type'; import { - WorkflowActionExecutorException, - WorkflowActionExecutorExceptionCode, -} from 'src/modules/workflow/workflow-action-executor/workflow-action-executor.exception'; -import { WorkflowActionExecutor } from 'src/modules/workflow/workflow-action-executor/workflow-action-executor.interface'; + WorkflowStepExecutorException, + WorkflowStepExecutorExceptionCode, +} from 'src/modules/workflow/workflow-step-executor/workflow-step-executor.exception'; +import { WorkflowStepExecutor } from 'src/modules/workflow/workflow-step-executor/workflow-step-executor.interface'; @Injectable() -export class CodeWorkflowActionExecutor implements WorkflowActionExecutor { +export class CodeActionExecutor implements WorkflowStepExecutor { constructor( private readonly serverlessFunctionService: ServerlessFunctionService, private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory, ) {} async execute({ - action, + step, payload, }: { - action: WorkflowAction; + step: WorkflowCodeStep; payload?: object; }): Promise { const { workspaceId } = this.scopedWorkspaceContextFactory.create(); if (!workspaceId) { - throw new WorkflowActionExecutorException( + throw new WorkflowStepExecutorException( 'Scoped workspace not found', - WorkflowActionExecutorExceptionCode.SCOPED_WORKSPACE_NOT_FOUND, + WorkflowStepExecutorExceptionCode.SCOPED_WORKSPACE_NOT_FOUND, ); } const result = await this.serverlessFunctionService.executeOne( - action.settings.serverlessFunctionId, + step.settings.serverlessFunctionId, workspaceId, payload, ); diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-workflow-version-is-valid.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-workflow-version-is-valid.ts index d38cc8034..d1b7ea977 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-workflow-version-is-valid.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/utils/assert-workflow-version-is-valid.ts @@ -27,9 +27,9 @@ export function assertWorkflowVersionIsValid( ); } - if (!workflowVersion.trigger.nextAction) { + if (!workflowVersion.steps || workflowVersion.steps.length === 0) { throw new WorkflowTriggerException( - 'No next action provided in trigger', + 'No steps provided in workflow version', WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER, ); }