fix: gracefully handle missing logic functions during workflow destroy (#21362)

## Summary
- Wraps `deleteOneWithSource` calls in `.catch()` during workflow/step
destruction so that a missing logic function (valid UUID but already
deleted) no longer crashes the entire destroy operation
- Adds a `Logger` to `WorkflowVersionStepOperationsWorkspaceService` for
the warning
- Fixes test mock to return a resolved Promise and use a valid UUID

## Context
When a CODE step references a `logicFunctionId` that is a valid UUID but
the logic function no longer exists (e.g. deleted by a previous
operation or orphaned), the destroy fails with "Logic function with id X
not found". This blocks users from cleaning up workflows.

## Test plan
- [x] Destroy a workflow with CODE steps whose logic functions already
exist → succeeds as before
- [ ] Destroy a workflow with CODE steps referencing a
deleted/non-existent logic function → succeeds with a warning log
instead of crashing
This commit is contained in:
Thomas Trompette
2026-06-10 10:54:04 +02:00
committed by GitHub
parent adba66caea
commit 71480c3888
3 changed files with 40 additions and 9 deletions
@@ -10,6 +10,10 @@ import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object
import { buildObjectIdByNameMaps } from 'src/engine/metadata-modules/flat-object-metadata/utils/build-object-id-by-name-maps.util';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { LogicFunctionFromSourceService } from 'src/engine/metadata-modules/logic-function/services/logic-function-from-source.service';
import {
LogicFunctionException,
LogicFunctionExceptionCode,
} from 'src/engine/metadata-modules/logic-function/logic-function.exception';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { type WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
@@ -387,10 +391,22 @@ export class WorkflowCommonWorkspaceService {
continue;
}
await this.logicFunctionFromSourceService.deleteOneWithSource({
id: logicFunctionId,
workspaceId,
});
await this.logicFunctionFromSourceService
.deleteOneWithSource({
id: logicFunctionId,
workspaceId,
})
.catch((error) => {
if (
error instanceof LogicFunctionException &&
error.code ===
LogicFunctionExceptionCode.LOGIC_FUNCTION_NOT_FOUND
) {
return;
}
throw error;
});
}
}
}
@@ -91,7 +91,7 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => {
} as unknown as jest.Mocked<CodeStepBuildService>;
logicFunctionFromSourceService = {
deleteOneWithSource: jest.fn(),
deleteOneWithSource: jest.fn().mockResolvedValue(undefined),
} as unknown as jest.Mocked<LogicFunctionFromSourceService>;
agentService = {
@@ -22,6 +22,10 @@ import { AiAgentRoleService } from 'src/engine/metadata-modules/ai/ai-agent-role
import { AgentService } from 'src/engine/metadata-modules/ai/ai-agent/agent.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { LogicFunctionFromSourceService } from 'src/engine/metadata-modules/logic-function/services/logic-function-from-source.service';
import {
LogicFunctionException,
LogicFunctionExceptionCode,
} from 'src/engine/metadata-modules/logic-function/logic-function.exception';
import { findFlatLogicFunctionOrThrow } from 'src/engine/metadata-modules/logic-function/utils/find-flat-logic-function-or-throw.util';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity';
@@ -95,10 +99,21 @@ export class WorkflowVersionStepOperationsWorkspaceService {
break;
}
await this.logicFunctionFromSourceService.deleteOneWithSource({
id: step.settings.input.logicFunctionId,
workspaceId,
});
await this.logicFunctionFromSourceService
.deleteOneWithSource({
id: step.settings.input.logicFunctionId,
workspaceId,
})
.catch((error) => {
if (
error instanceof LogicFunctionException &&
error.code === LogicFunctionExceptionCode.LOGIC_FUNCTION_NOT_FOUND
) {
return;
}
throw error;
});
break;
}
case WorkflowActionType.AI_AGENT: {