chore(workflow): temporarily lift credit-cap gate on workflow steps (#19904)
## Summary - Removes the per-step `canBillMeteredProduct(WORKFLOW_NODE_EXECUTION)` gate in `WorkflowExecutorWorkspaceService.executeStep` so workflows keep running when a workspace reaches `hasReachedCurrentPeriodCap`. Previously every step failed with `BILLING_WORKFLOW_EXECUTION_ERROR_MESSAGE` (\"No remaining credits to execute workflow…\"). - Drops the now-unused `BillingService` injection, related imports, and the helper `canBillWorkflowNodeExecution`. Updates the spec to drop the corresponding billing-validation case and mock. - Leaves the constant file and `BillingService` itself in place, plus a TODO at the previous gate site, so the behavior can be re-enabled with a small, reviewable revert. ## Notes - Usage events are still emitted (`USAGE_RECORDED` / `UsageResourceType.WORKFLOW`), and `EnforceUsageCapJob` keeps computing the cap and flipping `hasReachedCurrentPeriodCap` — only the executor stops consulting that flag. - The runner-level `canFeatureBeUsed` check in `WorkflowRunnerWorkspaceService.run` was already log-only (subscription presence, not credits), so no change there. - AI chat (`agent-chat.resolver.ts`) keeps its own `BILLING_CREDITS_EXHAUSTED` gate; this PR does not touch it. ## Test plan - [x] `npx jest workflow-executor.workspace-service.spec.ts` (17/17 pass) - [ ] Manual: with billing enabled and the metered subscription item flagged `hasReachedCurrentPeriodCap = true`, trigger a workflow run and verify steps execute end-to-end instead of failing with the billing error. Made with [Cursor](https://cursor.com)
This commit is contained in:
-44
@@ -2,8 +2,6 @@ import { Test, type TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { getWorkflowRunContext, StepStatus } from 'twenty-shared/workflow';
|
||||
|
||||
import { BILLING_WORKFLOW_EXECUTION_ERROR_MESSAGE } from 'src/engine/core-modules/billing/constants/billing-workflow-execution-error-message.constant';
|
||||
import { BillingService } from 'src/engine/core-modules/billing/services/billing.service';
|
||||
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service';
|
||||
@@ -71,11 +69,6 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
||||
getWorkflowRunOrFail: jest.fn(),
|
||||
};
|
||||
|
||||
const mockBillingService = {
|
||||
isBillingEnabled: jest.fn().mockReturnValue(true),
|
||||
canBillMeteredProduct: jest.fn().mockReturnValue(true),
|
||||
};
|
||||
|
||||
const mockExceptionHandlerService = {
|
||||
captureExceptions: jest.fn(),
|
||||
};
|
||||
@@ -108,10 +101,6 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
||||
provide: WorkflowRunWorkspaceService,
|
||||
useValue: mockWorkflowRunWorkspaceService,
|
||||
},
|
||||
{
|
||||
provide: BillingService,
|
||||
useValue: mockBillingService,
|
||||
},
|
||||
{
|
||||
provide: ExceptionHandlerService,
|
||||
useValue: mockExceptionHandlerService,
|
||||
@@ -341,39 +330,6 @@ describe('WorkflowExecutorWorkspaceService', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should stop when billing validation fails', async () => {
|
||||
mockBillingService.isBillingEnabled.mockReturnValueOnce(true);
|
||||
mockBillingService.canBillMeteredProduct.mockReturnValueOnce(false);
|
||||
|
||||
await service.executeFromSteps({
|
||||
workflowRunId: mockWorkflowRunId,
|
||||
stepIds: ['step-1'],
|
||||
workspaceId: mockWorkspaceId,
|
||||
});
|
||||
|
||||
expect(workflowActionFactory.get).toHaveBeenCalledTimes(0);
|
||||
|
||||
expect(
|
||||
workflowRunWorkspaceService.updateWorkflowRunStepInfo,
|
||||
).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(workflowRunWorkspaceService.endWorkflowRun).toHaveBeenCalledTimes(
|
||||
1,
|
||||
);
|
||||
|
||||
expect(
|
||||
workflowRunWorkspaceService.updateWorkflowRunStepInfo,
|
||||
).toHaveBeenCalledWith({
|
||||
stepId: 'step-1',
|
||||
stepInfo: {
|
||||
error: BILLING_WORKFLOW_EXECUTION_ERROR_MESSAGE,
|
||||
status: StepStatus.FAILED,
|
||||
},
|
||||
workflowRunId: mockWorkflowRunId,
|
||||
workspaceId: 'workspace-id',
|
||||
});
|
||||
});
|
||||
|
||||
it('should not emit billing event for skipped steps', async () => {
|
||||
(shouldExecuteStep as jest.Mock).mockReturnValue(false);
|
||||
(shouldSkipStepExecution as jest.Mock).mockReturnValue(true);
|
||||
|
||||
+3
-22
@@ -9,9 +9,6 @@ import {
|
||||
WorkflowRunStepInfos,
|
||||
} from 'twenty-shared/workflow';
|
||||
|
||||
import { BILLING_WORKFLOW_EXECUTION_ERROR_MESSAGE } from 'src/engine/core-modules/billing/constants/billing-workflow-execution-error-message.constant';
|
||||
import { BillingProductKey } from 'src/engine/core-modules/billing/enums/billing-product-key.enum';
|
||||
import { BillingService } from 'src/engine/core-modules/billing/services/billing.service';
|
||||
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
@@ -59,7 +56,6 @@ export class WorkflowExecutorWorkspaceService {
|
||||
private readonly workflowActionFactory: WorkflowActionFactory,
|
||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||
private readonly workflowRunWorkspaceService: WorkflowRunWorkspaceService,
|
||||
private readonly billingService: BillingService,
|
||||
private readonly exceptionHandlerService: ExceptionHandlerService,
|
||||
private readonly metricsService: MetricsService,
|
||||
@InjectMessageQueue(MessageQueue.workflowQueue)
|
||||
@@ -376,16 +372,6 @@ export class WorkflowExecutorWorkspaceService {
|
||||
);
|
||||
}
|
||||
|
||||
private async canBillWorkflowNodeExecution(workspaceId: string) {
|
||||
return (
|
||||
!this.billingService.isBillingEnabled() ||
|
||||
(await this.billingService.canBillMeteredProduct(
|
||||
workspaceId,
|
||||
BillingProductKey.WORKFLOW_NODE_EXECUTION,
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
private async processStepExecutionResult({
|
||||
actionOutput,
|
||||
stepId,
|
||||
@@ -467,14 +453,9 @@ export class WorkflowExecutorWorkspaceService {
|
||||
workflowRunId: string;
|
||||
workspaceId: string;
|
||||
}) {
|
||||
const canBill = await this.canBillWorkflowNodeExecution(workspaceId);
|
||||
|
||||
if (!canBill) {
|
||||
return {
|
||||
error: BILLING_WORKFLOW_EXECUTION_ERROR_MESSAGE,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: re-enable workflow node execution credit cap once billing limits are revisited.
|
||||
// Previously gated on BillingService.canBillMeteredProduct(WORKFLOW_NODE_EXECUTION);
|
||||
// temporarily disabled so workflows keep running when the period cap is reached.
|
||||
const stepId = step.id;
|
||||
|
||||
const workflowAction = this.workflowActionFactory.get(step.type);
|
||||
|
||||
Reference in New Issue
Block a user