Logic function refactorization (#17861)

As title
This commit is contained in:
martmull
2026-02-12 11:40:49 +01:00
committed by GitHub
parent b456f79167
commit a4ed043d43
122 changed files with 1441 additions and 1897 deletions
@@ -310,7 +310,7 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => {
runtime: LogicFunctionRuntime.NODE22,
timeoutSeconds: 30,
sourceHandlerPath: 'src/index.ts',
builtHandlerPath: 'index.mjs',
builtHandlerPath: 'src/index.mjs',
handlerName: 'main',
checksum: null,
toolInputSchema: null,
@@ -321,6 +321,7 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => {
cronTriggerSettings: null,
databaseEventTriggerSettings: null,
httpRouteTriggerSettings: null,
isBuildUpToDate: false,
};
logicFunctionMetadataService.createOne.mockResolvedValue(
@@ -394,6 +395,7 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => {
cronTriggerSettings: null,
databaseEventTriggerSettings: null,
httpRouteTriggerSettings: null,
isBuildUpToDate: true,
};
const mockNewFlatLogicFunction: FlatLogicFunction = {
@@ -407,7 +409,7 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => {
runtime: LogicFunctionRuntime.NODE22,
timeoutSeconds: 30,
sourceHandlerPath: 'src/index.ts',
builtHandlerPath: 'index.mjs',
builtHandlerPath: 'src/index.mjs',
handlerName: 'main',
checksum: null,
toolInputSchema: null,
@@ -418,6 +420,7 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => {
cronTriggerSettings: null,
databaseEventTriggerSettings: null,
httpRouteTriggerSettings: null,
isBuildUpToDate: false,
};
const emptyMaps = createEmptyAllFlatEntityMaps();
@@ -14,30 +14,25 @@ import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/
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-input-to-flat-logic-function.util';
import { fromCreateLogicFunctionInputToFlatLogicFunction } from 'src/engine/metadata-modules/logic-function/utils/from-create-logic-function-from-source-input-to-flat-logic-function.util';
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 { SEED_LOGIC_FUNCTION_INPUT_SCHEMA } from 'src/engine/core-modules/logic-function/logic-function-resource/constants/seed-logic-function-input-schema';
import type { JsonbProperty } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type';
const WORKFLOW_BASE_FOLDER_PREFIX = 'workflow';
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,
) {}
private getSourceSubfolderForCodeStep(logicFunctionId: string) {
return `${WORKFLOW_BASE_FOLDER_PREFIX}/${logicFunctionId}`;
}
async createCodeStepLogicFunction({
logicFunctionId,
workspaceId,
@@ -45,33 +40,13 @@ export class CodeStepBuildService {
logicFunctionId: string;
workspaceId: string;
}) {
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const { sourceHandlerPath, builtHandlerPath, handlerName, checksum } =
await this.logicFunctionResourceService.seedSourceFiles({
sourceSubfolder: this.getSourceSubfolderForCodeStep(logicFunctionId),
workspaceId,
applicationUniversalIdentifier:
workspaceCustomFlatApplication.universalIdentifier,
});
return await this.logicFunctionMetadataService.createOne({
return await this.logicFunctionFromSourceService.createOne({
input: {
id: logicFunctionId,
name: 'A Logic Function Code Workflow Step',
name: 'A Code Step',
description: '',
sourceHandlerPath,
builtHandlerPath,
handlerName,
checksum,
toolInputSchema: SEED_LOGIC_FUNCTION_INPUT_SCHEMA,
},
workspaceId,
ownerFlatApplication: workspaceCustomFlatApplication,
});
}
@@ -114,7 +89,6 @@ export class CodeStepBuildService {
existingLogicFunction.id,
newId,
);
const toBuiltHandlerPath = builtHandlerPath.replace(
existingLogicFunction.id,
newId,
@@ -122,9 +96,9 @@ export class CodeStepBuildService {
await this.logicFunctionResourceService.copyResources({
fromSourceHandlerPath: sourceHandlerPath,
fromBuiltHandlerPath: builtHandlerPath,
toSourceHandlerPath,
toBuiltHandlerPath,
fromBuiltHandlerPath: builtHandlerPath,
toBuiltHandlerPath: toBuiltHandlerPath,
workspaceId,
applicationUniversalIdentifier,
});
@@ -136,8 +110,8 @@ export class CodeStepBuildService {
id: newId,
universalIdentifier: newUniversalIdentifier,
description: existingLogicFunction.description ?? undefined,
builtHandlerPath: toBuiltHandlerPath,
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
@@ -230,7 +204,7 @@ export class CodeStepBuildService {
if (
!isDefined(flatLogicFunction) ||
flatLogicFunction.deletedAt ||
!this.isWorkflowCodeStepLogicFunction(flatLogicFunction)
flatLogicFunction.isBuildUpToDate
) {
continue;
}
@@ -246,63 +220,10 @@ export class CodeStepBuildService {
continue;
}
const { checksum } =
await this.logicFunctionResourceService.buildFromSource({
sourceHandlerPath: flatLogicFunction.sourceHandlerPath,
builtHandlerPath: flatLogicFunction.builtHandlerPath,
workspaceId,
applicationUniversalIdentifier,
});
await this.logicFunctionMetadataService.updateChecksum({
id: flatLogicFunction.id,
checksum,
await this.logicFunctionFromSourceService.buildOneFromSource({
workspaceId,
id: logicFunctionId,
});
}
}
isWorkflowCodeStepLogicFunction(
flatLogicFunction: FlatLogicFunction,
): boolean {
return (
flatLogicFunction.sourceHandlerPath.startsWith(
`${WORKFLOW_BASE_FOLDER_PREFIX}/`,
) ||
flatLogicFunction.builtHandlerPath.startsWith(
`${WORKFLOW_BASE_FOLDER_PREFIX}/`,
)
);
}
async getFlatLogicFunctionForCodeStepOrNull({
logicFunctionId,
workspaceId,
}: {
logicFunctionId: string;
workspaceId: string;
}): Promise<FlatLogicFunction | null> {
const { flatLogicFunctionMaps } =
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatLogicFunctionMaps'],
},
);
const flatLogicFunction = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: logicFunctionId,
flatEntityMaps: flatLogicFunctionMaps,
});
if (
!isDefined(flatLogicFunction) ||
flatLogicFunction.deletedAt ||
!this.isWorkflowCodeStepLogicFunction(flatLogicFunction)
) {
return null;
}
return flatLogicFunction;
}
}
@@ -3,8 +3,8 @@ import { Injectable } from '@nestjs/common';
import { type ToolSet } from 'ai';
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
import { LogicFunctionFromSourceService } from 'src/engine/metadata-modules/logic-function/services/logic-function-from-source.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { LogicFunctionService } from 'src/engine/metadata-modules/logic-function/services/logic-function.service';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config';
import { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service';
@@ -43,7 +43,7 @@ export class WorkflowToolWorkspaceService {
workflowSchemaService: WorkflowSchemaWorkspaceService,
globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
recordPositionService: RecordPositionService,
logicFunctionService: LogicFunctionService,
logicFunctionFromSourceService: LogicFunctionFromSourceService,
flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
) {
this.deps = {
@@ -55,7 +55,7 @@ export class WorkflowToolWorkspaceService {
workflowSchemaService,
globalWorkspaceOrmManager,
recordPositionService,
logicFunctionService,
logicFunctionFromSourceService,
flatEntityMapsCacheService,
};
}
@@ -13,24 +13,14 @@ const updateLogicFunctionSourceSchema = z.object({
'The ID of the logic function to update (from the code step settings.input.logicFunctionId)',
),
code: z
.object({
src: z
.object({
'index.ts': z
.string()
.describe(
'The TypeScript source code for the logic function. Must export a main function.',
),
})
.describe('Source folder containing the index.ts file'),
})
.string()
.describe(
'The source code structure. Use { src: { "index.ts": "your code here" } }',
'The TypeScript source code for the logic function. Must export a main function.',
),
});
export const createUpdateLogicFunctionSourceTool = (
deps: Pick<WorkflowToolDependencies, 'logicFunctionService'>,
deps: Pick<WorkflowToolDependencies, 'logicFunctionFromSourceService'>,
context: WorkflowToolContext,
) => ({
name: 'update_logic_function_source' as const,
@@ -55,17 +45,16 @@ export const main = async (params: { url: string }) => {
To find the logicFunctionId, look at the code step's settings.input.logicFunctionId field.`,
inputSchema: updateLogicFunctionSourceSchema,
execute: async (parameters: {
logicFunctionId: string;
code: { src: { 'index.ts': string } };
}) => {
execute: async (parameters: { logicFunctionId: string; code: string }) => {
try {
const { logicFunctionId, code } = parameters;
const { workspaceId } = context;
await deps.logicFunctionService.updateLogicFunctionSource({
await deps.logicFunctionFromSourceService.updateOne({
id: logicFunctionId,
code,
update: {
sourceHandlerCode: code,
},
workspaceId,
});
@@ -1,6 +1,6 @@
import type { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
import type { LogicFunctionFromSourceService } from 'src/engine/metadata-modules/logic-function/services/logic-function-from-source.service';
import type { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import type { LogicFunctionService } from 'src/engine/metadata-modules/logic-function/services/logic-function.service';
import type { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import type { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service';
import type { WorkflowVersionEdgeWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.workspace-service';
@@ -18,7 +18,7 @@ export type WorkflowToolDependencies = {
workflowSchemaService: WorkflowSchemaWorkspaceService;
globalWorkspaceOrmManager: GlobalWorkspaceOrmManager;
recordPositionService: RecordPositionService;
logicFunctionService: LogicFunctionService;
logicFunctionFromSourceService: LogicFunctionFromSourceService;
flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService;
};