From b70542535812292d01fc476e6be6be0ce901781c Mon Sep 17 00:00:00 2001 From: Thomas Trompette Date: Wed, 26 Feb 2025 15:15:39 +0100 Subject: [PATCH] Create Workflow Form action (#10509) - create form action - add `pendingEvent` to executor output - when receiving pendingEvent, exit workflow execution - let the workflow in running status for now before taking a decision --- ...workflow-version-step.workspace-service.ts | 1 - .../factories/workflow-executor.factory.ts | 4 +++ .../types/workflow-executor-output.type.ts | 1 + .../form/form-action.module.ts | 9 ++++++ .../form/form.workflow-action.ts | 32 +++++++++++++++++++ .../guards/is-workflow-form-action.guard.ts | 9 ++++++ .../workflow-form-action-settings.type.ts | 12 +++++++ .../types/workflow-action-settings.type.ts | 5 +-- .../types/workflow-action.type.ts | 10 +++++- .../workflow-executor.module.ts | 2 ++ .../workflow-executor.workspace-service.ts | 10 ++++++ .../workflow-runner/jobs/run-workflow.job.ts | 17 ++++++---- 12 files changed, 102 insertions(+), 10 deletions(-) create mode 100644 packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/form/form-action.module.ts create mode 100644 packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/form/form.workflow-action.ts create mode 100644 packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/form/guards/is-workflow-form-action.guard.ts create mode 100644 packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type.ts diff --git a/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-version-step.workspace-service.ts b/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-version-step.workspace-service.ts index e572083f3..17afc7570 100644 --- a/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-version-step.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-version-step.workspace-service.ts @@ -33,7 +33,6 @@ import { const TRIGGER_STEP_ID = 'trigger'; const BASE_STEP_DEFINITION: BaseWorkflowActionSettings = { - input: {}, outputSchema: {}, errorHandlingOptions: { continueOnFailure: { diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/factories/workflow-executor.factory.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/factories/workflow-executor.factory.ts index 843e28823..b7acabfe4 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/factories/workflow-executor.factory.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/factories/workflow-executor.factory.ts @@ -7,6 +7,7 @@ import { WorkflowStepExecutorExceptionCode, } from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception'; import { CodeWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/code/code.workflow-action'; +import { FormWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/form/form.workflow-action'; import { SendEmailWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/send-email.workflow-action'; import { CreateRecordWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action'; import { DeleteRecordWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/delete-record.workflow-action'; @@ -23,6 +24,7 @@ export class WorkflowExecutorFactory { private readonly updateRecordWorkflowAction: UpdateRecordWorkflowAction, private readonly deleteRecordWorkflowAction: DeleteRecordWorkflowAction, private readonly findRecordsWorkflowAction: FindRecordsWorkflowAction, + private readonly formWorkflowAction: FormWorkflowAction, ) {} get(stepType: WorkflowActionType): WorkflowExecutor { @@ -39,6 +41,8 @@ export class WorkflowExecutorFactory { return this.deleteRecordWorkflowAction; case WorkflowActionType.FIND_RECORDS: return this.findRecordsWorkflowAction; + case WorkflowActionType.FORM: + return this.formWorkflowAction; default: throw new WorkflowStepExecutorException( `Workflow step executor not found for step type '${stepType}'`, diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/types/workflow-executor-output.type.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/types/workflow-executor-output.type.ts index 65ef4ad08..5f8ffc213 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/types/workflow-executor-output.type.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/types/workflow-executor-output.type.ts @@ -1,4 +1,5 @@ export type WorkflowExecutorOutput = { result?: object; error?: string; + pendingEvent?: boolean; }; diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/form/form-action.module.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/form/form-action.module.ts new file mode 100644 index 000000000..1600d235b --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/form/form-action.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; + +import { FormWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/form/form.workflow-action'; + +@Module({ + providers: [FormWorkflowAction], + exports: [FormWorkflowAction], +}) +export class FormActionModule {} diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/form/form.workflow-action.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/form/form.workflow-action.ts new file mode 100644 index 000000000..bf592a068 --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/form/form.workflow-action.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@nestjs/common'; + +import { WorkflowExecutor } from 'src/modules/workflow/workflow-executor/interfaces/workflow-executor.interface'; + +import { + WorkflowStepExecutorException, + WorkflowStepExecutorExceptionCode, +} from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception'; +import { WorkflowExecutorInput } from 'src/modules/workflow/workflow-executor/types/workflow-executor-input'; +import { WorkflowExecutorOutput } from 'src/modules/workflow/workflow-executor/types/workflow-executor-output.type'; +import { isWorkflowFormAction } from 'src/modules/workflow/workflow-executor/workflow-actions/form/guards/is-workflow-form-action.guard'; + +@Injectable() +export class FormWorkflowAction implements WorkflowExecutor { + async execute({ + currentStepIndex, + steps, + }: WorkflowExecutorInput): Promise { + const step = steps[currentStepIndex]; + + if (!isWorkflowFormAction(step)) { + throw new WorkflowStepExecutorException( + 'Step is not a form action', + WorkflowStepExecutorExceptionCode.INVALID_STEP_TYPE, + ); + } + + return { + pendingEvent: true, + }; + } +} diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/form/guards/is-workflow-form-action.guard.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/form/guards/is-workflow-form-action.guard.ts new file mode 100644 index 000000000..3a078c30f --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/form/guards/is-workflow-form-action.guard.ts @@ -0,0 +1,9 @@ +import { + WorkflowAction, + WorkflowActionType, + WorkflowFormAction, +} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type'; + +export const isWorkflowFormAction = ( + action: WorkflowAction, +): action is WorkflowFormAction => action.type === WorkflowActionType.FORM; diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type.ts new file mode 100644 index 000000000..306008bd5 --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type.ts @@ -0,0 +1,12 @@ +import { BaseWorkflowActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-settings.type'; + +export type FormFieldMetadata = { + label: string; + type: string; + placeholder?: string; + settings?: Record; +}; + +export type WorkflowFormActionSettings = BaseWorkflowActionSettings & { + input: FormFieldMetadata[]; +}; diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-settings.type.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-settings.type.ts index 2dfdf84c4..78c275847 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-settings.type.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-settings.type.ts @@ -1,5 +1,6 @@ import { OutputSchema } from 'src/modules/workflow/workflow-builder/types/output-schema.type'; import { WorkflowCodeActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/code/types/workflow-code-action-settings.type'; +import { WorkflowFormActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type'; import { WorkflowSendEmailActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/types/workflow-send-email-action-settings.type'; import { WorkflowCreateRecordActionSettings, @@ -9,7 +10,6 @@ import { } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/types/workflow-record-crud-action-settings.type'; export type BaseWorkflowActionSettings = { - input: object; outputSchema: OutputSchema; errorHandlingOptions: { retryOnFailure: { @@ -27,4 +27,5 @@ export type WorkflowActionSettings = | WorkflowCreateRecordActionSettings | WorkflowUpdateRecordActionSettings | WorkflowDeleteRecordActionSettings - | WorkflowFindRecordsActionSettings; + | WorkflowFindRecordsActionSettings + | WorkflowFormActionSettings; diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type.ts index 0f18596e5..0a1757ec8 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type.ts @@ -1,4 +1,5 @@ import { WorkflowCodeActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/code/types/workflow-code-action-settings.type'; +import { WorkflowFormActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type'; import { WorkflowSendEmailActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/types/workflow-send-email-action-settings.type'; import { WorkflowCreateRecordActionSettings, @@ -15,6 +16,7 @@ export enum WorkflowActionType { UPDATE_RECORD = 'UPDATE_RECORD', DELETE_RECORD = 'DELETE_RECORD', FIND_RECORDS = 'FIND_RECORDS', + FORM = 'FORM', } type BaseWorkflowAction = { @@ -55,10 +57,16 @@ export type WorkflowFindRecordsAction = BaseWorkflowAction & { settings: WorkflowFindRecordsActionSettings; }; +export type WorkflowFormAction = BaseWorkflowAction & { + type: WorkflowActionType.FORM; + settings: WorkflowFormActionSettings; +}; + export type WorkflowAction = | WorkflowCodeAction | WorkflowSendEmailAction | WorkflowCreateRecordAction | WorkflowUpdateRecordAction | WorkflowDeleteRecordAction - | WorkflowFindRecordsAction; + | WorkflowFindRecordsAction + | WorkflowFormAction; 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 c88f67162..318eab2bd 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 @@ -4,6 +4,7 @@ import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/s import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module'; import { WorkflowExecutorFactory } from 'src/modules/workflow/workflow-executor/factories/workflow-executor.factory'; import { CodeActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/code/code-action.module'; +import { FormActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/form/form-action.module'; import { SendEmailActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/send-email-action.module'; import { RecordCRUDActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/record-crud-action.module'; import { WorkflowExecutorWorkspaceService } from 'src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service'; @@ -15,6 +16,7 @@ import { WorkflowRunModule } from 'src/modules/workflow/workflow-runner/workflow CodeActionModule, SendEmailActionModule, RecordCRUDActionModule, + FormActionModule, WorkflowRunModule, ], providers: [ diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service.ts index d491ea9d0..3269df82a 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service.ts @@ -77,6 +77,16 @@ export class WorkflowExecutorWorkspaceService implements WorkflowExecutor { output: actionOutput, }; + if (actionOutput.pendingEvent) { + await this.workflowRunWorkspaceService.saveWorkflowRunState({ + workflowRunId, + stepOutput, + context, + }); + + return actionOutput; + } + if (actionOutput.result) { const updatedContext = { ...context, 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 960e1ae90..a44d8c74c 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 @@ -67,12 +67,17 @@ export class RunWorkflowJob { await this.throttleExecution(workflowVersion.workflowId); - const { error } = await this.workflowExecutorWorkspaceService.execute({ - workflowRunId, - currentStepIndex: 0, - steps: workflowVersion.steps, - context, - }); + const { error, pendingEvent } = + await this.workflowExecutorWorkspaceService.execute({ + workflowRunId, + currentStepIndex: 0, + steps: workflowVersion.steps, + context, + }); + + if (pendingEvent) { + return; + } await this.workflowRunWorkspaceService.endWorkflowRun({ workflowRunId,