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
This commit is contained in:
Thomas Trompette
2025-02-26 15:15:39 +01:00
committed by GitHub
parent 1af25bf31d
commit b705425358
12 changed files with 102 additions and 10 deletions

View File

@ -33,7 +33,6 @@ import {
const TRIGGER_STEP_ID = 'trigger';
const BASE_STEP_DEFINITION: BaseWorkflowActionSettings = {
input: {},
outputSchema: {},
errorHandlingOptions: {
continueOnFailure: {

View File

@ -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}'`,

View File

@ -1,4 +1,5 @@
export type WorkflowExecutorOutput = {
result?: object;
error?: string;
pendingEvent?: boolean;
};

View File

@ -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 {}

View File

@ -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<WorkflowExecutorOutput> {
const step = steps[currentStepIndex];
if (!isWorkflowFormAction(step)) {
throw new WorkflowStepExecutorException(
'Step is not a form action',
WorkflowStepExecutorExceptionCode.INVALID_STEP_TYPE,
);
}
return {
pendingEvent: true,
};
}
}

View File

@ -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;

View File

@ -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<string, any>;
};
export type WorkflowFormActionSettings = BaseWorkflowActionSettings & {
input: FormFieldMetadata[];
};

View File

@ -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;

View File

@ -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;

View File

@ -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: [

View File

@ -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,

View File

@ -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,