Start using next step ids (#11683)
- update workflow executor - update next step ids on step creation/deletion - use these in workflow run - use these in variables
This commit is contained in:
+1
@@ -9,4 +9,5 @@ export class WorkflowStepExecutorException extends CustomException {
|
||||
export enum WorkflowStepExecutorExceptionCode {
|
||||
SCOPED_WORKSPACE_NOT_FOUND = 'SCOPED_WORKSPACE_NOT_FOUND',
|
||||
INVALID_STEP_TYPE = 'INVALID_STEP_TYPE',
|
||||
STEP_NOT_FOUND = 'STEP_NOT_FOUND',
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||
|
||||
export type WorkflowExecutorInput = {
|
||||
currentStepIndex: number;
|
||||
currentStepId: string;
|
||||
steps: WorkflowAction[];
|
||||
context: Record<string, unknown>;
|
||||
workflowRunId: string;
|
||||
|
||||
+9
-2
@@ -22,11 +22,18 @@ export class CodeWorkflowAction implements WorkflowExecutor {
|
||||
) {}
|
||||
|
||||
async execute({
|
||||
currentStepIndex,
|
||||
currentStepId,
|
||||
steps,
|
||||
context,
|
||||
}: WorkflowExecutorInput): Promise<WorkflowExecutorOutput> {
|
||||
const step = steps[currentStepIndex];
|
||||
const step = steps.find((step) => step.id === currentStepId);
|
||||
|
||||
if (!step) {
|
||||
throw new WorkflowStepExecutorException(
|
||||
'Step not found',
|
||||
WorkflowStepExecutorExceptionCode.STEP_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isWorkflowCodeAction(step)) {
|
||||
throw new WorkflowStepExecutorException(
|
||||
|
||||
+9
-2
@@ -13,10 +13,17 @@ import { isWorkflowFormAction } from 'src/modules/workflow/workflow-executor/wor
|
||||
@Injectable()
|
||||
export class FormWorkflowAction implements WorkflowExecutor {
|
||||
async execute({
|
||||
currentStepIndex,
|
||||
currentStepId,
|
||||
steps,
|
||||
}: WorkflowExecutorInput): Promise<WorkflowExecutorOutput> {
|
||||
const step = steps[currentStepIndex];
|
||||
const step = steps.find((step) => step.id === currentStepId);
|
||||
|
||||
if (!step) {
|
||||
throw new WorkflowStepExecutorException(
|
||||
'Step not found',
|
||||
WorkflowStepExecutorExceptionCode.STEP_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isWorkflowFormAction(step)) {
|
||||
throw new WorkflowStepExecutorException(
|
||||
|
||||
+9
-2
@@ -75,11 +75,18 @@ export class SendEmailWorkflowAction implements WorkflowExecutor {
|
||||
}
|
||||
|
||||
async execute({
|
||||
currentStepIndex,
|
||||
currentStepId,
|
||||
steps,
|
||||
context,
|
||||
}: WorkflowExecutorInput): Promise<WorkflowExecutorOutput> {
|
||||
const step = steps[currentStepIndex];
|
||||
const step = steps.find((step) => step.id === currentStepId);
|
||||
|
||||
if (!step) {
|
||||
throw new WorkflowStepExecutorException(
|
||||
'Step not found',
|
||||
WorkflowStepExecutorExceptionCode.STEP_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isWorkflowSendEmailAction(step)) {
|
||||
throw new WorkflowStepExecutorException(
|
||||
|
||||
+8
-2
@@ -42,12 +42,18 @@ export class CreateRecordWorkflowAction implements WorkflowExecutor {
|
||||
) {}
|
||||
|
||||
async execute({
|
||||
currentStepIndex,
|
||||
currentStepId,
|
||||
steps,
|
||||
context,
|
||||
}: WorkflowExecutorInput): Promise<WorkflowExecutorOutput> {
|
||||
const step = steps[currentStepIndex];
|
||||
const step = steps.find((step) => step.id === currentStepId);
|
||||
|
||||
if (!step) {
|
||||
throw new WorkflowStepExecutorException(
|
||||
'Step not found',
|
||||
WorkflowStepExecutorExceptionCode.STEP_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
if (!isWorkflowCreateRecordAction(step)) {
|
||||
throw new WorkflowStepExecutorException(
|
||||
'Step is not a create record action',
|
||||
|
||||
+8
-2
@@ -35,12 +35,18 @@ export class DeleteRecordWorkflowAction implements WorkflowExecutor {
|
||||
) {}
|
||||
|
||||
async execute({
|
||||
currentStepIndex,
|
||||
currentStepId,
|
||||
steps,
|
||||
context,
|
||||
}: WorkflowExecutorInput): Promise<WorkflowExecutorOutput> {
|
||||
const step = steps[currentStepIndex];
|
||||
const step = steps.find((step) => step.id === currentStepId);
|
||||
|
||||
if (!step) {
|
||||
throw new WorkflowStepExecutorException(
|
||||
'Step not found',
|
||||
WorkflowStepExecutorExceptionCode.STEP_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
if (!isWorkflowDeleteRecordAction(step)) {
|
||||
throw new WorkflowStepExecutorException(
|
||||
'Step is not a delete record action',
|
||||
|
||||
+9
-2
@@ -45,11 +45,18 @@ export class FindRecordsWorkflowAction implements WorkflowExecutor {
|
||||
) {}
|
||||
|
||||
async execute({
|
||||
currentStepIndex,
|
||||
currentStepId,
|
||||
steps,
|
||||
context,
|
||||
}: WorkflowExecutorInput): Promise<WorkflowExecutorOutput> {
|
||||
const step = steps[currentStepIndex];
|
||||
const step = steps.find((step) => step.id === currentStepId);
|
||||
|
||||
if (!step) {
|
||||
throw new WorkflowStepExecutorException(
|
||||
'Step not found',
|
||||
WorkflowStepExecutorExceptionCode.STEP_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isWorkflowFindRecordsAction(step)) {
|
||||
throw new WorkflowStepExecutorException(
|
||||
|
||||
+9
-2
@@ -42,11 +42,18 @@ export class UpdateRecordWorkflowAction implements WorkflowExecutor {
|
||||
) {}
|
||||
|
||||
async execute({
|
||||
currentStepIndex,
|
||||
currentStepId,
|
||||
steps,
|
||||
context,
|
||||
}: WorkflowExecutorInput): Promise<WorkflowExecutorOutput> {
|
||||
const step = steps[currentStepIndex];
|
||||
const step = steps.find((step) => step.id === currentStepId);
|
||||
|
||||
if (!step) {
|
||||
throw new WorkflowStepExecutorException(
|
||||
'Step not found',
|
||||
WorkflowStepExecutorExceptionCode.STEP_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isWorkflowUpdateRecordAction(step)) {
|
||||
throw new WorkflowStepExecutorException(
|
||||
|
||||
+12
-9
@@ -107,6 +107,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
||||
retryOnFailure: { value: false },
|
||||
},
|
||||
},
|
||||
nextStepIds: ['step-2'],
|
||||
},
|
||||
{
|
||||
id: 'step-2',
|
||||
@@ -117,6 +118,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
||||
retryOnFailure: { value: false },
|
||||
},
|
||||
},
|
||||
nextStepIds: [],
|
||||
},
|
||||
] as WorkflowAction[];
|
||||
|
||||
@@ -124,7 +126,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
||||
// No steps to execute
|
||||
const result = await service.execute({
|
||||
workflowRunId: mockWorkflowRunId,
|
||||
currentStepIndex: 2,
|
||||
currentStepId: 'step-2',
|
||||
steps: mockSteps,
|
||||
context: mockContext,
|
||||
});
|
||||
@@ -145,7 +147,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
||||
|
||||
const result = await service.execute({
|
||||
workflowRunId: mockWorkflowRunId,
|
||||
currentStepIndex: 0,
|
||||
currentStepId: 'step-1',
|
||||
steps: mockSteps,
|
||||
context: mockContext,
|
||||
});
|
||||
@@ -156,7 +158,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
||||
);
|
||||
expect(mockWorkflowExecutor.execute).toHaveBeenCalledWith({
|
||||
workflowRunId: mockWorkflowRunId,
|
||||
currentStepIndex: 0,
|
||||
currentStepId: 'step-1',
|
||||
steps: mockSteps,
|
||||
context: mockContext,
|
||||
attemptCount: 1,
|
||||
@@ -199,7 +201,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
||||
|
||||
const result = await service.execute({
|
||||
workflowRunId: mockWorkflowRunId,
|
||||
currentStepIndex: 0,
|
||||
currentStepId: 'step-1',
|
||||
steps: mockSteps,
|
||||
context: mockContext,
|
||||
});
|
||||
@@ -231,7 +233,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
||||
|
||||
const result = await service.execute({
|
||||
workflowRunId: mockWorkflowRunId,
|
||||
currentStepIndex: 0,
|
||||
currentStepId: 'step-1',
|
||||
steps: mockSteps,
|
||||
context: mockContext,
|
||||
});
|
||||
@@ -265,6 +267,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
||||
retryOnFailure: { value: false },
|
||||
},
|
||||
},
|
||||
nextStepIds: ['step-2'],
|
||||
},
|
||||
{
|
||||
id: 'step-2',
|
||||
@@ -284,7 +287,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
||||
|
||||
const result = await service.execute({
|
||||
workflowRunId: mockWorkflowRunId,
|
||||
currentStepIndex: 0,
|
||||
currentStepId: 'step-1',
|
||||
steps: stepsWithContinueOnFailure,
|
||||
context: mockContext,
|
||||
});
|
||||
@@ -330,7 +333,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
||||
|
||||
await service.execute({
|
||||
workflowRunId: mockWorkflowRunId,
|
||||
currentStepIndex: 0,
|
||||
currentStepId: 'step-1',
|
||||
steps: stepsWithRetryOnFailure,
|
||||
context: mockContext,
|
||||
});
|
||||
@@ -367,7 +370,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
||||
|
||||
const result = await service.execute({
|
||||
workflowRunId: mockWorkflowRunId,
|
||||
currentStepIndex: 0,
|
||||
currentStepId: 'step-1',
|
||||
steps: stepsWithRetryOnFailure,
|
||||
context: mockContext,
|
||||
attemptCount: 3, // MAX_RETRIES_ON_FAILURE is 3
|
||||
@@ -394,7 +397,7 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
||||
|
||||
const result = await service.execute({
|
||||
workflowRunId: mockWorkflowRunId,
|
||||
currentStepIndex: 0,
|
||||
currentStepId: 'step-1',
|
||||
steps: mockSteps,
|
||||
context: mockContext,
|
||||
});
|
||||
|
||||
+26
-30
@@ -1,5 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { WorkflowExecutor } from 'src/modules/workflow/workflow-executor/interfaces/workflow-executor.interface';
|
||||
|
||||
import { BILLING_FEATURE_USED } from 'src/engine/core-modules/billing/constants/billing-feature-used.constant';
|
||||
@@ -38,22 +40,20 @@ export class WorkflowExecutorWorkspaceService implements WorkflowExecutor {
|
||||
) {}
|
||||
|
||||
async execute({
|
||||
currentStepIndex,
|
||||
currentStepId,
|
||||
steps,
|
||||
context,
|
||||
attemptCount = 1,
|
||||
workflowRunId,
|
||||
}: WorkflowExecutorInput): Promise<WorkflowExecutorOutput> {
|
||||
if (currentStepIndex >= steps.length) {
|
||||
const step = steps.find((step) => step.id === currentStepId);
|
||||
|
||||
if (!step) {
|
||||
return {
|
||||
result: {
|
||||
success: true,
|
||||
},
|
||||
error: 'Step not found',
|
||||
};
|
||||
}
|
||||
|
||||
const step = steps[currentStepIndex];
|
||||
|
||||
const workflowExecutor = this.workflowExecutorFactory.get(step.type);
|
||||
|
||||
let actionOutput: WorkflowExecutorOutput;
|
||||
@@ -80,7 +80,7 @@ export class WorkflowExecutorWorkspaceService implements WorkflowExecutor {
|
||||
|
||||
try {
|
||||
actionOutput = await workflowExecutor.execute({
|
||||
currentStepIndex,
|
||||
currentStepId,
|
||||
steps,
|
||||
context,
|
||||
attemptCount,
|
||||
@@ -111,11 +111,17 @@ export class WorkflowExecutorWorkspaceService implements WorkflowExecutor {
|
||||
return actionOutput;
|
||||
}
|
||||
|
||||
if (actionOutput.result) {
|
||||
const updatedContext = {
|
||||
...context,
|
||||
[step.id]: actionOutput.result,
|
||||
};
|
||||
const shouldContinue =
|
||||
isDefined(actionOutput.result) ||
|
||||
step.settings.errorHandlingOptions.continueOnFailure.value;
|
||||
|
||||
if (shouldContinue) {
|
||||
const updatedContext = isDefined(actionOutput.result)
|
||||
? {
|
||||
...context,
|
||||
[step.id]: actionOutput.result,
|
||||
}
|
||||
: context;
|
||||
|
||||
await this.workflowRunWorkspaceService.saveWorkflowRunState({
|
||||
workflowRunId,
|
||||
@@ -123,36 +129,26 @@ export class WorkflowExecutorWorkspaceService implements WorkflowExecutor {
|
||||
context: updatedContext,
|
||||
});
|
||||
|
||||
if (!isDefined(step.nextStepIds?.[0])) {
|
||||
return actionOutput;
|
||||
}
|
||||
|
||||
// TODO: handle multiple next steps
|
||||
return await this.execute({
|
||||
workflowRunId,
|
||||
currentStepIndex: currentStepIndex + 1,
|
||||
currentStepId: step.nextStepIds[0],
|
||||
steps,
|
||||
context: updatedContext,
|
||||
});
|
||||
}
|
||||
|
||||
if (step.settings.errorHandlingOptions.continueOnFailure.value) {
|
||||
await this.workflowRunWorkspaceService.saveWorkflowRunState({
|
||||
workflowRunId,
|
||||
stepOutput,
|
||||
context,
|
||||
});
|
||||
|
||||
return await this.execute({
|
||||
workflowRunId,
|
||||
currentStepIndex: currentStepIndex + 1,
|
||||
steps,
|
||||
context,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
step.settings.errorHandlingOptions.retryOnFailure.value &&
|
||||
attemptCount < MAX_RETRIES_ON_FAILURE
|
||||
) {
|
||||
return await this.execute({
|
||||
workflowRunId,
|
||||
currentStepIndex,
|
||||
currentStepId,
|
||||
steps,
|
||||
context,
|
||||
attemptCount: attemptCount + 1,
|
||||
|
||||
Reference in New Issue
Block a user