Workflow - Avoid billing skipped steps (#19547)

As title
This commit is contained in:
Thomas Trompette
2026-04-10 14:49:14 +02:00
committed by GitHub
parent 217957f2a1
commit 31baf52528
3 changed files with 57 additions and 2 deletions
@@ -14,6 +14,8 @@ import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service'
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
import { WorkflowActionFactory } from 'src/modules/workflow/workflow-executor/factories/workflow-action.factory';
import { shouldExecuteStep } from 'src/modules/workflow/workflow-executor/utils/should-execute-step.util';
import { shouldFailSafely } from 'src/modules/workflow/workflow-executor/utils/should-fail-safely.util';
import { shouldSkipStepExecution } from 'src/modules/workflow/workflow-executor/utils/should-skip-step-execution.util';
import {
type WorkflowAction,
WorkflowActionType,
@@ -30,11 +32,25 @@ jest.mock(
return {
...actual,
shouldExecuteStep: jest.fn().mockReturnValue(true), // default behavior
shouldExecuteStep: jest.fn().mockReturnValue(true),
};
},
);
jest.mock(
'src/modules/workflow/workflow-executor/utils/should-fail-safely.util',
() => ({
shouldFailSafely: jest.fn().mockReturnValue(false),
}),
);
jest.mock(
'src/modules/workflow/workflow-executor/utils/should-skip-step-execution.util',
() => ({
shouldSkipStepExecution: jest.fn().mockReturnValue(false),
}),
);
describe('WorkflowExecutorWorkspaceService', () => {
let service: WorkflowExecutorWorkspaceService;
let workflowActionFactory: WorkflowActionFactory;
@@ -358,6 +374,40 @@ describe('WorkflowExecutorWorkspaceService', () => {
});
});
it('should not emit billing event for skipped steps', async () => {
(shouldExecuteStep as jest.Mock).mockReturnValue(false);
(shouldSkipStepExecution as jest.Mock).mockReturnValue(true);
await service.executeFromSteps({
workflowRunId: mockWorkflowRunId,
stepIds: ['step-1'],
workspaceId: mockWorkspaceId,
});
expect(workflowActionFactory.get).not.toHaveBeenCalled();
expect(workspaceEventEmitter.emitCustomBatchEvent).not.toHaveBeenCalled();
(shouldExecuteStep as jest.Mock).mockReturnValue(true);
(shouldSkipStepExecution as jest.Mock).mockReturnValue(false);
});
it('should not emit billing event for fail-safely steps', async () => {
(shouldExecuteStep as jest.Mock).mockReturnValue(false);
(shouldFailSafely as jest.Mock).mockReturnValue(true);
await service.executeFromSteps({
workflowRunId: mockWorkflowRunId,
stepIds: ['step-1'],
workspaceId: mockWorkspaceId,
});
expect(workflowActionFactory.get).not.toHaveBeenCalled();
expect(workspaceEventEmitter.emitCustomBatchEvent).not.toHaveBeenCalled();
(shouldExecuteStep as jest.Mock).mockReturnValue(true);
(shouldFailSafely as jest.Mock).mockReturnValue(false);
});
it('should return if step should not be executed', async () => {
(shouldExecuteStep as jest.Mock).mockReturnValueOnce(false);
@@ -177,7 +177,11 @@ export class WorkflowExecutorWorkspaceService {
const isError =
isDefined(actionOutput.error) && !actionOutput.shouldFailSafely;
if (!isError && !actionOutput.shouldFailSafely) {
if (
!isError &&
!actionOutput.shouldFailSafely &&
!actionOutput.shouldSkipStepExecution
) {
this.sendWorkflowNodeRunEvent(workspaceId, workflowRun.workflowId);
}