Rework types for logic function (#18074)
## Summary - **Consolidate logic function services**: Remove `LogicFunctionMetadataService` and consolidate all logic function CRUD operations into `LogicFunctionFromSourceService`, with a new `LogicFunctionFromSourceHelperService` for shared validation/migration logic - **Introduce typed conversion utils following the skill pattern**: Add `fromCreateLogicFunctionFromSourceInputToUniversalFlatLogicFunctionToCreate` and `fromUpdateLogicFunctionFromSourceInputToFlatLogicFunctionToUpdate` that convert DTO inputs directly to flat entities (`UniversalFlatLogicFunction` / `FlatLogicFunction`), replacing the previous intermediate `UpdateLogicFunctionMetadataParams` indirection - **Simplify `CodeStepBuildService`**: Remove ~100 lines of manual duplication logic by delegating to `LogicFunctionFromSourceService.duplicateOneWithSource` - **Remove completed 1-17 migration**: Delete `MigrateWorkflowCodeStepsCommand` and associated utils that migrated workflow code steps from serverless functions to logic functions
This commit is contained in:
+12
-114
@@ -6,8 +6,7 @@ import { AgentService } from 'src/engine/metadata-modules/ai/ai-agent/agent.serv
|
||||
import { createEmptyAllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-all-flat-entity-maps.constant';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { LogicFunctionRuntime } from 'src/engine/metadata-modules/logic-function/logic-function.entity';
|
||||
import { LogicFunctionMetadataService } from 'src/engine/metadata-modules/logic-function/services/logic-function-metadata.service';
|
||||
import { type FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type';
|
||||
import { LogicFunctionFromSourceService } from 'src/engine/metadata-modules/logic-function/services/logic-function-from-source.service';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
@@ -25,7 +24,7 @@ const mockWorkspaceId = 'workspace-id';
|
||||
describe('WorkflowVersionStepOperationsWorkspaceService', () => {
|
||||
let service: WorkflowVersionStepOperationsWorkspaceService;
|
||||
let globalWorkspaceOrmManager: jest.Mocked<GlobalWorkspaceOrmManager>;
|
||||
let logicFunctionMetadataService: jest.Mocked<LogicFunctionMetadataService>;
|
||||
let logicFunctionFromSourceService: jest.Mocked<LogicFunctionFromSourceService>;
|
||||
let codeStepBuildService: jest.Mocked<CodeStepBuildService>;
|
||||
let agentService: jest.Mocked<AgentService>;
|
||||
let roleTargetRepository: jest.Mocked<any>;
|
||||
@@ -33,7 +32,6 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => {
|
||||
let workflowCommonWorkspaceService: jest.Mocked<WorkflowCommonWorkspaceService>;
|
||||
let aiAgentRoleService: jest.Mocked<AiAgentRoleService>;
|
||||
let workspaceCacheService: jest.Mocked<WorkspaceCacheService>;
|
||||
let flatEntityMapsCacheService: jest.Mocked<WorkspaceManyOrAllFlatEntityMapsCacheService>;
|
||||
|
||||
beforeEach(async () => {
|
||||
codeStepBuildService = {
|
||||
@@ -89,10 +87,9 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => {
|
||||
}),
|
||||
} as unknown as jest.Mocked<CodeStepBuildService>;
|
||||
|
||||
logicFunctionMetadataService = {
|
||||
createOne: jest.fn(),
|
||||
destroyOne: jest.fn(),
|
||||
} as unknown as jest.Mocked<LogicFunctionMetadataService>;
|
||||
logicFunctionFromSourceService = {
|
||||
deleteOneWithSource: jest.fn(),
|
||||
} as unknown as jest.Mocked<LogicFunctionFromSourceService>;
|
||||
|
||||
agentService = {
|
||||
deleteManyAgents: jest.fn().mockResolvedValue([]),
|
||||
@@ -132,8 +129,8 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => {
|
||||
useValue: globalWorkspaceOrmManager,
|
||||
},
|
||||
{
|
||||
provide: LogicFunctionMetadataService,
|
||||
useValue: logicFunctionMetadataService,
|
||||
provide: LogicFunctionFromSourceService,
|
||||
useValue: logicFunctionFromSourceService,
|
||||
},
|
||||
{
|
||||
provide: CodeStepBuildService,
|
||||
@@ -165,12 +162,12 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => {
|
||||
},
|
||||
{
|
||||
provide: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
useValue: (flatEntityMapsCacheService = {
|
||||
useValue: {
|
||||
flushFlatEntityMaps: jest.fn(),
|
||||
getOrRecomputeManyOrAllFlatEntityMaps: jest
|
||||
.fn()
|
||||
.mockResolvedValue(createEmptyAllFlatEntityMaps()),
|
||||
} as unknown as jest.Mocked<WorkspaceManyOrAllFlatEntityMapsCacheService>),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
@@ -203,7 +200,9 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => {
|
||||
workspaceId: mockWorkspaceId,
|
||||
});
|
||||
|
||||
expect(logicFunctionMetadataService.destroyOne).toHaveBeenCalledWith({
|
||||
expect(
|
||||
logicFunctionFromSourceService.deleteOneWithSource,
|
||||
).toHaveBeenCalledWith({
|
||||
id: 'function-id',
|
||||
workspaceId: mockWorkspaceId,
|
||||
});
|
||||
@@ -286,35 +285,6 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => {
|
||||
|
||||
describe('runStepCreationSideEffectsAndBuildStep', () => {
|
||||
it('should create code step with logic function', async () => {
|
||||
const mockFlatLogicFunction: FlatLogicFunction = {
|
||||
id: 'new-function-id',
|
||||
name: 'Test Function',
|
||||
description: 'Test Description',
|
||||
workspaceId: mockWorkspaceId,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
deletedAt: null,
|
||||
runtime: LogicFunctionRuntime.NODE22,
|
||||
timeoutSeconds: 30,
|
||||
sourceHandlerPath: 'src/index.ts',
|
||||
builtHandlerPath: 'src/index.mjs',
|
||||
handlerName: 'main',
|
||||
checksum: null,
|
||||
toolInputSchema: null,
|
||||
isTool: false,
|
||||
universalIdentifier: 'universal-id',
|
||||
applicationId: 'application-id',
|
||||
applicationUniversalIdentifier: 'application-universal-id',
|
||||
cronTriggerSettings: null,
|
||||
databaseEventTriggerSettings: null,
|
||||
httpRouteTriggerSettings: null,
|
||||
isBuildUpToDate: false,
|
||||
};
|
||||
|
||||
logicFunctionMetadataService.createOne.mockResolvedValue(
|
||||
mockFlatLogicFunction,
|
||||
);
|
||||
|
||||
const result = await service.runStepCreationSideEffectsAndBuildStep({
|
||||
type: WorkflowActionType.CODE,
|
||||
workspaceId: mockWorkspaceId,
|
||||
@@ -360,78 +330,6 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => {
|
||||
nextStepIds: ['next-step'],
|
||||
} as unknown as WorkflowAction;
|
||||
|
||||
const mockExistingFlatLogicFunction: FlatLogicFunction = {
|
||||
id: 'function-id',
|
||||
name: 'Existing Function',
|
||||
description: 'Existing Description',
|
||||
workspaceId: mockWorkspaceId,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
deletedAt: null,
|
||||
runtime: LogicFunctionRuntime.NODE22,
|
||||
timeoutSeconds: 30,
|
||||
sourceHandlerPath: 'workflow/function-id/src/index.ts',
|
||||
builtHandlerPath: 'workflow/function-id/src/index.mjs',
|
||||
handlerName: 'main',
|
||||
checksum: 'existing-checksum',
|
||||
toolInputSchema: null,
|
||||
isTool: false,
|
||||
universalIdentifier: 'existing-universal-id',
|
||||
applicationId: 'application-id',
|
||||
applicationUniversalIdentifier: 'application-universal-id',
|
||||
cronTriggerSettings: null,
|
||||
databaseEventTriggerSettings: null,
|
||||
httpRouteTriggerSettings: null,
|
||||
isBuildUpToDate: true,
|
||||
};
|
||||
|
||||
const mockNewFlatLogicFunction: FlatLogicFunction = {
|
||||
id: 'new-function-id',
|
||||
name: 'Test Function',
|
||||
description: 'Test Description',
|
||||
workspaceId: mockWorkspaceId,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
deletedAt: null,
|
||||
runtime: LogicFunctionRuntime.NODE22,
|
||||
timeoutSeconds: 30,
|
||||
sourceHandlerPath: 'src/index.ts',
|
||||
builtHandlerPath: 'src/index.mjs',
|
||||
handlerName: 'main',
|
||||
checksum: null,
|
||||
toolInputSchema: null,
|
||||
isTool: false,
|
||||
universalIdentifier: 'universal-id',
|
||||
applicationId: 'application-id',
|
||||
applicationUniversalIdentifier: 'application-universal-id',
|
||||
cronTriggerSettings: null,
|
||||
databaseEventTriggerSettings: null,
|
||||
httpRouteTriggerSettings: null,
|
||||
isBuildUpToDate: false,
|
||||
};
|
||||
|
||||
const emptyMaps = createEmptyAllFlatEntityMaps();
|
||||
const flatLogicFunctionMapsKey = 'flatLogicFunctionMaps' as const;
|
||||
|
||||
flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps.mockResolvedValueOnce(
|
||||
{
|
||||
...emptyMaps,
|
||||
[flatLogicFunctionMapsKey]: {
|
||||
byUniversalIdentifier: {
|
||||
'existing-universal-id': mockExistingFlatLogicFunction,
|
||||
},
|
||||
universalIdentifierById: {
|
||||
'function-id': 'existing-universal-id',
|
||||
},
|
||||
universalIdentifiersByApplicationId: {},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
logicFunctionMetadataService.createOne.mockResolvedValue(
|
||||
mockNewFlatLogicFunction,
|
||||
);
|
||||
|
||||
const clonedStep = await service.cloneStep({
|
||||
step: originalStep,
|
||||
workspaceId: mockWorkspaceId,
|
||||
|
||||
+5
-119
@@ -1,36 +1,20 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
import {
|
||||
CronTriggerSettings,
|
||||
DatabaseEventTriggerSettings,
|
||||
HttpRouteTriggerSettings,
|
||||
} from 'twenty-shared/application';
|
||||
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { LogicFunctionMetadataService } from 'src/engine/metadata-modules/logic-function/services/logic-function-metadata.service';
|
||||
import { type FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type';
|
||||
import { findFlatLogicFunctionOrThrow } from 'src/engine/metadata-modules/logic-function/utils/find-flat-logic-function-or-throw.util';
|
||||
import { fromCreateLogicFunctionInputToFlatLogicFunction } from 'src/engine/metadata-modules/logic-function/utils/from-create-logic-function-from-source-input-to-flat-logic-function.util';
|
||||
import { LogicFunctionFromSourceService } from 'src/engine/metadata-modules/logic-function/services/logic-function-from-source.service';
|
||||
import {
|
||||
WorkflowActionType,
|
||||
type WorkflowAction,
|
||||
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||
import { LogicFunctionResourceService } from 'src/engine/core-modules/logic-function/logic-function-resource/logic-function-resource.service';
|
||||
import type { JsonbProperty } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type';
|
||||
import { LogicFunctionFromSourceService } from 'src/engine/metadata-modules/logic-function/services/logic-function-from-source.service';
|
||||
|
||||
@Injectable()
|
||||
export class CodeStepBuildService {
|
||||
constructor(
|
||||
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
private readonly logicFunctionMetadataService: LogicFunctionMetadataService,
|
||||
private readonly logicFunctionFromSourceService: LogicFunctionFromSourceService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly logicFunctionResourceService: LogicFunctionResourceService,
|
||||
) {}
|
||||
|
||||
async createCodeStepLogicFunction({
|
||||
@@ -40,7 +24,7 @@ export class CodeStepBuildService {
|
||||
logicFunctionId: string;
|
||||
workspaceId: string;
|
||||
}) {
|
||||
return await this.logicFunctionFromSourceService.createOne({
|
||||
return await this.logicFunctionFromSourceService.createOneFromSource({
|
||||
input: {
|
||||
id: logicFunctionId,
|
||||
name: 'A Code Step',
|
||||
@@ -56,109 +40,11 @@ export class CodeStepBuildService {
|
||||
}: {
|
||||
existingLogicFunctionId: string;
|
||||
workspaceId: string;
|
||||
}): Promise<FlatLogicFunction> {
|
||||
const { flatLogicFunctionMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatLogicFunctionMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const existingLogicFunction = findFlatLogicFunctionOrThrow({
|
||||
id: existingLogicFunctionId,
|
||||
flatLogicFunctionMaps,
|
||||
});
|
||||
|
||||
const resolvedOwnerFlatApplication = (
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{ workspaceId },
|
||||
)
|
||||
).workspaceCustomFlatApplication;
|
||||
|
||||
const applicationUniversalIdentifier =
|
||||
resolvedOwnerFlatApplication.universalIdentifier;
|
||||
|
||||
const newId = v4();
|
||||
|
||||
const newUniversalIdentifier = v4();
|
||||
|
||||
const { sourceHandlerPath, builtHandlerPath } = existingLogicFunction;
|
||||
|
||||
const toSourceHandlerPath = sourceHandlerPath.replace(
|
||||
existingLogicFunction.id,
|
||||
newId,
|
||||
);
|
||||
const toBuiltHandlerPath = builtHandlerPath.replace(
|
||||
existingLogicFunction.id,
|
||||
newId,
|
||||
);
|
||||
|
||||
await this.logicFunctionResourceService.copyResources({
|
||||
fromSourceHandlerPath: sourceHandlerPath,
|
||||
toSourceHandlerPath,
|
||||
fromBuiltHandlerPath: builtHandlerPath,
|
||||
toBuiltHandlerPath: toBuiltHandlerPath,
|
||||
}): Promise<{ id: string }> {
|
||||
return this.logicFunctionFromSourceService.duplicateOneWithSource({
|
||||
existingLogicFunctionId,
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
});
|
||||
|
||||
const newFlatLogicFunction =
|
||||
fromCreateLogicFunctionInputToFlatLogicFunction({
|
||||
createLogicFunctionInput: {
|
||||
...existingLogicFunction,
|
||||
id: newId,
|
||||
universalIdentifier: newUniversalIdentifier,
|
||||
description: existingLogicFunction.description ?? undefined,
|
||||
sourceHandlerPath: toSourceHandlerPath,
|
||||
builtHandlerPath: toBuiltHandlerPath,
|
||||
toolInputSchema: existingLogicFunction.toolInputSchema ?? {},
|
||||
checksum: existingLogicFunction.checksum ?? '[default-checksum]', // TODO: checksum should never be null, update column in logicFunction entity to set it non nullable
|
||||
cronTriggerSettings: existingLogicFunction.cronTriggerSettings as
|
||||
| JsonbProperty<CronTriggerSettings>
|
||||
| undefined,
|
||||
databaseEventTriggerSettings:
|
||||
existingLogicFunction.databaseEventTriggerSettings as
|
||||
| JsonbProperty<DatabaseEventTriggerSettings>
|
||||
| undefined,
|
||||
httpRouteTriggerSettings:
|
||||
existingLogicFunction.httpRouteTriggerSettings as
|
||||
| JsonbProperty<HttpRouteTriggerSettings>
|
||||
| undefined,
|
||||
},
|
||||
workspaceId,
|
||||
ownerFlatApplication: resolvedOwnerFlatApplication,
|
||||
});
|
||||
|
||||
const created = await this.logicFunctionMetadataService.createOne({
|
||||
input: {
|
||||
...newFlatLogicFunction,
|
||||
description: newFlatLogicFunction.description ?? undefined,
|
||||
checksum: newFlatLogicFunction.checksum ?? '[default-checksum]',
|
||||
toolInputSchema: newFlatLogicFunction.toolInputSchema ?? {},
|
||||
cronTriggerSettings: newFlatLogicFunction.cronTriggerSettings as
|
||||
| JsonbProperty<CronTriggerSettings>
|
||||
| undefined,
|
||||
databaseEventTriggerSettings:
|
||||
newFlatLogicFunction.databaseEventTriggerSettings as
|
||||
| JsonbProperty<DatabaseEventTriggerSettings>
|
||||
| undefined,
|
||||
httpRouteTriggerSettings:
|
||||
newFlatLogicFunction.httpRouteTriggerSettings as
|
||||
| JsonbProperty<HttpRouteTriggerSettings>
|
||||
| undefined,
|
||||
},
|
||||
workspaceId,
|
||||
ownerFlatApplication: resolvedOwnerFlatApplication,
|
||||
});
|
||||
|
||||
if (!isDefined(created)) {
|
||||
throw new Error(
|
||||
'Failed to create logic function when duplicating code step',
|
||||
);
|
||||
}
|
||||
|
||||
return created;
|
||||
}
|
||||
|
||||
async buildCodeStepsFromSourceForSteps({
|
||||
|
||||
+3
-3
@@ -21,7 +21,7 @@ import { AiAgentRoleService } from 'src/engine/metadata-modules/ai/ai-agent-role
|
||||
import { AgentService } from 'src/engine/metadata-modules/ai/ai-agent/agent.service';
|
||||
import { DEFAULT_SMART_MODEL } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { LogicFunctionMetadataService } from 'src/engine/metadata-modules/logic-function/services/logic-function-metadata.service';
|
||||
import { LogicFunctionFromSourceService } from 'src/engine/metadata-modules/logic-function/services/logic-function-from-source.service';
|
||||
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';
|
||||
@@ -65,7 +65,7 @@ const ITERATOR_EMPTY_STEP_POSITION_OFFSET = {
|
||||
export class WorkflowVersionStepOperationsWorkspaceService {
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly logicFunctionMetadataService: LogicFunctionMetadataService,
|
||||
private readonly logicFunctionFromSourceService: LogicFunctionFromSourceService,
|
||||
private readonly codeStepBuildService: CodeStepBuildService,
|
||||
private readonly agentService: AgentService,
|
||||
@InjectRepository(RoleTargetEntity)
|
||||
@@ -87,7 +87,7 @@ export class WorkflowVersionStepOperationsWorkspaceService {
|
||||
}) {
|
||||
switch (step.type) {
|
||||
case WorkflowActionType.CODE: {
|
||||
await this.logicFunctionMetadataService.destroyOne({
|
||||
await this.logicFunctionFromSourceService.deleteOneWithSource({
|
||||
id: step.settings.input.logicFunctionId,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user