Refactor workflow to use new functions (#17552)
Removes the versioning system for logic functions (`latestVersion`,
`publishedVersions`) and simplifies the file storage structure.
### Changes
- Remove `publishOneLogicFunctionOrFail` and publishing logic from
workflow status updates
- Add `createLogicFunctionFromExistingLogicFunction` to duplicate logic
functions when creating draft workflow versions
- Update `createDraftStep` to create a new logic function copy instead
of referencing the same one
- Migrate file storage to v2 endpoints with
`applicationUniversalIdentifier`
- Unify path structure: source files at `source/workflow/{id}/`, built
files at `built-logic-function/workflow/{id}/`
- Store full paths in `sourceHandlerPath` and `builtHandlerPath` entity
fields
This commit is contained in:
+108
-23
@@ -1,15 +1,26 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { join } from 'path';
|
||||
import { promises as fs } from 'fs';
|
||||
import { dirname, join } from 'path';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
import { isObject } from '@sniptt/guards';
|
||||
import { FileFolder, type Sources } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { getSeedProjectFiles } from 'src/engine/core-modules/logic-function-executor/drivers/utils/get-seed-project-files';
|
||||
import { getLogicFunctionFolderOrThrow } from 'src/engine/core-modules/logic-function-executor/utils/get-logic-function-folder-or-throw.utils';
|
||||
import { LambdaBuildDirectoryManager } from 'src/engine/core-modules/logic-function-executor/drivers/utils/lambda-build-directory-manager';
|
||||
import { getLogicFunctionBaseFolderPath } from 'src/engine/metadata-modules/logic-function/utils/get-logic-function-base-folder-path.util';
|
||||
import { LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity';
|
||||
import {
|
||||
LogicFunctionException,
|
||||
LogicFunctionExceptionCode,
|
||||
} from 'src/engine/metadata-modules/logic-function/logic-function.exception';
|
||||
import { CreateLogicFunctionAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/logic-function/types/workspace-migration-logic-function-action.type';
|
||||
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
|
||||
import { FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type';
|
||||
@@ -23,6 +34,8 @@ export class CreateLogicFunctionActionHandlerService extends WorkspaceMigrationR
|
||||
constructor(
|
||||
private readonly fileStorageService: FileStorageService,
|
||||
private readonly functionBuildService: FunctionBuildService,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -33,7 +46,13 @@ export class CreateLogicFunctionActionHandlerService extends WorkspaceMigrationR
|
||||
const { action, queryRunner, workspaceId } = context;
|
||||
const { flatEntity: logicFunction } = action;
|
||||
|
||||
await this.buildAndSaveLogicFunction(logicFunction);
|
||||
const applicationUniversalIdentifier =
|
||||
await this.getApplicationUniversalIdentifier(logicFunction.applicationId);
|
||||
|
||||
await this.buildAndSaveLogicFunction(
|
||||
logicFunction,
|
||||
applicationUniversalIdentifier,
|
||||
);
|
||||
|
||||
const logicFunctionRepository =
|
||||
queryRunner.manager.getRepository<LogicFunctionEntity>(
|
||||
@@ -46,28 +65,84 @@ export class CreateLogicFunctionActionHandlerService extends WorkspaceMigrationR
|
||||
});
|
||||
}
|
||||
|
||||
private async buildAndSaveLogicFunction(logicFunction: FlatLogicFunction) {
|
||||
const draftFileFolder = getLogicFunctionFolderOrThrow({
|
||||
flatLogicFunction: logicFunction,
|
||||
private async getApplicationUniversalIdentifier(
|
||||
applicationId: string,
|
||||
): Promise<string> {
|
||||
const application = await this.applicationRepository.findOne({
|
||||
where: { id: applicationId },
|
||||
select: ['universalIdentifier'],
|
||||
});
|
||||
|
||||
if (isDefined(logicFunction?.code)) {
|
||||
await this.fileStorageService.writeFolder(
|
||||
logicFunction.code,
|
||||
draftFileFolder,
|
||||
if (!isDefined(application)) {
|
||||
throw new LogicFunctionException(
|
||||
`Application with id ${applicationId} not found`,
|
||||
LogicFunctionExceptionCode.LOGIC_FUNCTION_NOT_FOUND,
|
||||
);
|
||||
} else {
|
||||
for (const file of await getSeedProjectFiles) {
|
||||
await this.fileStorageService.write({
|
||||
file: file.content,
|
||||
name: file.name,
|
||||
mimeType: 'application/typescript',
|
||||
folder: join(draftFileFolder, file.path),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return application.universalIdentifier;
|
||||
}
|
||||
|
||||
private async writeSourcesToLocalFolder(
|
||||
sources: Sources,
|
||||
localPath: string,
|
||||
): Promise<void> {
|
||||
for (const key of Object.keys(sources)) {
|
||||
const filePath = join(localPath, key);
|
||||
const value = sources[key];
|
||||
|
||||
if (isObject(value)) {
|
||||
await this.writeSourcesToLocalFolder(value as Sources, filePath);
|
||||
continue;
|
||||
}
|
||||
await fs.mkdir(dirname(filePath), { recursive: true });
|
||||
await fs.writeFile(filePath, value);
|
||||
}
|
||||
}
|
||||
|
||||
private async buildAndSaveLogicFunction(
|
||||
logicFunction: FlatLogicFunction,
|
||||
applicationUniversalIdentifier: string,
|
||||
) {
|
||||
const lambdaBuildDirectoryManager = new LambdaBuildDirectoryManager();
|
||||
|
||||
try {
|
||||
const { sourceTemporaryDir } = await lambdaBuildDirectoryManager.init();
|
||||
|
||||
if (isDefined(logicFunction?.code)) {
|
||||
await this.writeSourcesToLocalFolder(
|
||||
logicFunction.code,
|
||||
sourceTemporaryDir,
|
||||
);
|
||||
} else {
|
||||
for (const file of await getSeedProjectFiles) {
|
||||
const filePath = join(sourceTemporaryDir, file.path, file.name);
|
||||
|
||||
await fs.mkdir(join(sourceTemporaryDir, file.path), {
|
||||
recursive: true,
|
||||
});
|
||||
await fs.writeFile(filePath, file.content);
|
||||
}
|
||||
}
|
||||
|
||||
const baseFolderPath = getLogicFunctionBaseFolderPath(
|
||||
logicFunction.sourceHandlerPath,
|
||||
);
|
||||
|
||||
await this.fileStorageService.uploadFolder_v2({
|
||||
workspaceId: logicFunction.workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.Source,
|
||||
resourcePath: baseFolderPath,
|
||||
localPath: sourceTemporaryDir,
|
||||
});
|
||||
} finally {
|
||||
await lambdaBuildDirectoryManager.clean();
|
||||
}
|
||||
|
||||
await this.functionBuildService.buildAndUpload({
|
||||
flatLogicFunction: logicFunction,
|
||||
applicationUniversalIdentifier,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -76,10 +151,20 @@ export class CreateLogicFunctionActionHandlerService extends WorkspaceMigrationR
|
||||
): Promise<void> {
|
||||
const { action } = context;
|
||||
|
||||
await this.fileStorageService.delete({
|
||||
folderPath: getLogicFunctionFolderOrThrow({
|
||||
flatLogicFunction: action.flatEntity,
|
||||
}),
|
||||
const applicationUniversalIdentifier =
|
||||
await this.getApplicationUniversalIdentifier(
|
||||
action.flatEntity.applicationId,
|
||||
);
|
||||
|
||||
const baseFolderPath = getLogicFunctionBaseFolderPath(
|
||||
action.flatEntity.sourceHandlerPath,
|
||||
);
|
||||
|
||||
await this.fileStorageService.delete_v2({
|
||||
workspaceId: action.flatEntity.workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.Source,
|
||||
resourcePath: baseFolderPath,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+13
-52
@@ -5,9 +5,9 @@ import { FileFolder } from 'twenty-shared/types';
|
||||
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { getLogicFunctionFolderOrThrow } from 'src/engine/core-modules/logic-function-executor/utils/get-logic-function-folder-or-throw.utils';
|
||||
import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util';
|
||||
import { LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity';
|
||||
import { getLogicFunctionBaseFolderPath } from 'src/engine/metadata-modules/logic-function/utils/get-logic-function-base-folder-path.util';
|
||||
import { DeleteLogicFunctionAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/logic-function/types/workspace-migration-logic-function-action.type';
|
||||
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
|
||||
|
||||
@@ -41,58 +41,19 @@ export class DeleteLogicFunctionActionHandlerService extends WorkspaceMigrationR
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
// TODO: Should implement a cron task or a job to delete the files after a certain period of time
|
||||
await this.fileStorageService.move({
|
||||
from: {
|
||||
folderPath: getLogicFunctionFolderOrThrow({
|
||||
flatLogicFunction,
|
||||
fileFolder: FileFolder.LogicFunction,
|
||||
}),
|
||||
},
|
||||
to: {
|
||||
folderPath: getLogicFunctionFolderOrThrow({
|
||||
flatLogicFunction,
|
||||
fileFolder: FileFolder.LogicFunctionToDelete,
|
||||
}),
|
||||
},
|
||||
});
|
||||
const sourceBaseFolderPath = getLogicFunctionBaseFolderPath(
|
||||
flatLogicFunction.sourceHandlerPath,
|
||||
);
|
||||
const builtBaseFolderPath = getLogicFunctionBaseFolderPath(
|
||||
flatLogicFunction.builtHandlerPath,
|
||||
);
|
||||
|
||||
// We can delete built code as it can be computed from source code if rollback occurs
|
||||
await this.fileStorageService.delete({
|
||||
folderPath: getLogicFunctionFolderOrThrow({
|
||||
flatLogicFunction,
|
||||
fileFolder: FileFolder.BuiltFunction,
|
||||
}),
|
||||
});
|
||||
const sourceFolderPath = `workspace-${workspaceId}/${FileFolder.Source}/${sourceBaseFolderPath}`;
|
||||
const builtFolderPath = `workspace-${workspaceId}/${FileFolder.BuiltLogicFunction}/${builtBaseFolderPath}`;
|
||||
|
||||
await this.fileStorageService.delete({ folderPath: sourceFolderPath });
|
||||
await this.fileStorageService.delete({ folderPath: builtFolderPath });
|
||||
}
|
||||
|
||||
async rollbackForMetadata(
|
||||
context: Omit<
|
||||
WorkspaceMigrationActionRunnerArgs<DeleteLogicFunctionAction>,
|
||||
'queryRunner'
|
||||
>,
|
||||
): Promise<void> {
|
||||
const { action, allFlatEntityMaps } = context;
|
||||
const { universalIdentifier } = action;
|
||||
|
||||
const flatLogicFunction = findFlatEntityByUniversalIdentifierOrThrow({
|
||||
flatEntityMaps: allFlatEntityMaps.flatLogicFunctionMaps,
|
||||
universalIdentifier,
|
||||
});
|
||||
|
||||
await this.fileStorageService.move({
|
||||
from: {
|
||||
folderPath: getLogicFunctionFolderOrThrow({
|
||||
flatLogicFunction,
|
||||
fileFolder: FileFolder.LogicFunctionToDelete,
|
||||
}),
|
||||
},
|
||||
to: {
|
||||
folderPath: getLogicFunctionFolderOrThrow({
|
||||
flatLogicFunction,
|
||||
fileFolder: FileFolder.LogicFunction,
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
async rollbackForMetadata(): Promise<void> {}
|
||||
}
|
||||
|
||||
+83
-7
@@ -1,20 +1,32 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { type Sources } from 'twenty-shared/types';
|
||||
import { promises as fs } from 'fs';
|
||||
import { dirname, join } from 'path';
|
||||
|
||||
import { isObject } from '@sniptt/guards';
|
||||
import { FileFolder, type Sources } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { LambdaBuildDirectoryManager } from 'src/engine/core-modules/logic-function-executor/drivers/utils/lambda-build-directory-manager';
|
||||
import { LogicFunctionExecutorService } from 'src/engine/core-modules/logic-function-executor/logic-function-executor.service';
|
||||
import { getLogicFunctionFolderOrThrow } from 'src/engine/core-modules/logic-function-executor/utils/get-logic-function-folder-or-throw.utils';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { FunctionBuildService } from 'src/engine/metadata-modules/function-build/function-build.service';
|
||||
import { LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity';
|
||||
import {
|
||||
LogicFunctionException,
|
||||
LogicFunctionExceptionCode,
|
||||
} from 'src/engine/metadata-modules/logic-function/logic-function.exception';
|
||||
import { FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type';
|
||||
import { getLogicFunctionBaseFolderPath } from 'src/engine/metadata-modules/logic-function/utils/get-logic-function-base-folder-path.util';
|
||||
import { UpdateLogicFunctionAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/logic-function/types/workspace-migration-logic-function-action.type';
|
||||
import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
|
||||
import { fromFlatEntityPropertiesUpdatesToPartialFlatEntity } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/utils/from-flat-entity-properties-updates-to-partial-flat-entity';
|
||||
import { FunctionBuildService } from 'src/engine/metadata-modules/function-build/function-build.service';
|
||||
|
||||
@Injectable()
|
||||
export class UpdateLogicFunctionActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||
@@ -25,6 +37,8 @@ export class UpdateLogicFunctionActionHandlerService extends WorkspaceMigrationR
|
||||
private readonly fileStorageService: FileStorageService,
|
||||
private readonly logicFunctionExecutorService: LogicFunctionExecutorService,
|
||||
private readonly functionBuildService: FunctionBuildService,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -73,6 +87,41 @@ export class UpdateLogicFunctionActionHandlerService extends WorkspaceMigrationR
|
||||
this.logicFunctionExecutorService.delete(flatLogicFunction);
|
||||
}
|
||||
|
||||
private async getApplicationUniversalIdentifier(
|
||||
applicationId: string,
|
||||
): Promise<string> {
|
||||
const application = await this.applicationRepository.findOne({
|
||||
where: { id: applicationId },
|
||||
select: ['universalIdentifier'],
|
||||
});
|
||||
|
||||
if (!isDefined(application)) {
|
||||
throw new LogicFunctionException(
|
||||
`Application with id ${applicationId} not found`,
|
||||
LogicFunctionExceptionCode.LOGIC_FUNCTION_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return application.universalIdentifier;
|
||||
}
|
||||
|
||||
private async writeSourcesToLocalFolder(
|
||||
sources: Sources,
|
||||
localPath: string,
|
||||
): Promise<void> {
|
||||
for (const key of Object.keys(sources)) {
|
||||
const filePath = join(localPath, key);
|
||||
const value = sources[key];
|
||||
|
||||
if (isObject(value)) {
|
||||
await this.writeSourcesToLocalFolder(value as Sources, filePath);
|
||||
continue;
|
||||
}
|
||||
await fs.mkdir(dirname(filePath), { recursive: true });
|
||||
await fs.writeFile(filePath, value);
|
||||
}
|
||||
}
|
||||
|
||||
async handleChecksumUpdate({
|
||||
flatLogicFunction,
|
||||
code,
|
||||
@@ -80,14 +129,41 @@ export class UpdateLogicFunctionActionHandlerService extends WorkspaceMigrationR
|
||||
flatLogicFunction: FlatLogicFunction;
|
||||
code: Sources;
|
||||
}) {
|
||||
const fileFolder = getLogicFunctionFolderOrThrow({
|
||||
flatLogicFunction,
|
||||
});
|
||||
const applicationUniversalIdentifier =
|
||||
await this.getApplicationUniversalIdentifier(
|
||||
flatLogicFunction.applicationId,
|
||||
);
|
||||
|
||||
await this.fileStorageService.writeFolder(code, fileFolder);
|
||||
const lambdaBuildDirectoryManager = new LambdaBuildDirectoryManager();
|
||||
|
||||
try {
|
||||
const { sourceTemporaryDir } = await lambdaBuildDirectoryManager.init();
|
||||
|
||||
await this.writeSourcesToLocalFolder(code, sourceTemporaryDir);
|
||||
|
||||
const baseFolderPath = getLogicFunctionBaseFolderPath(
|
||||
flatLogicFunction.sourceHandlerPath,
|
||||
);
|
||||
|
||||
await this.fileStorageService.uploadFolder_v2({
|
||||
workspaceId: flatLogicFunction.workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.Source,
|
||||
resourcePath: baseFolderPath,
|
||||
localPath: sourceTemporaryDir,
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.log(
|
||||
'workspace-migration-runner',
|
||||
`Error updating logic function ${flatLogicFunction.id}: ${error.message}`,
|
||||
);
|
||||
} finally {
|
||||
await lambdaBuildDirectoryManager.clean();
|
||||
}
|
||||
|
||||
await this.functionBuildService.buildAndUpload({
|
||||
flatLogicFunction,
|
||||
applicationUniversalIdentifier,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+7
-1
@@ -1,5 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { WorkspaceSchemaManagerModule } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.module';
|
||||
import { CreateAgentActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/agent/services/create-agent-action-handler.service';
|
||||
import { DeleteAgentActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/agent/services/delete-agent-action-handler.service';
|
||||
@@ -70,7 +72,11 @@ import { UpdateViewActionHandlerService } from 'src/engine/workspace-manager/wor
|
||||
import { FunctionBuildModule } from 'src/engine/metadata-modules/function-build/function-build.module';
|
||||
|
||||
@Module({
|
||||
imports: [WorkspaceSchemaManagerModule, FunctionBuildModule],
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([ApplicationEntity]),
|
||||
WorkspaceSchemaManagerModule,
|
||||
FunctionBuildModule,
|
||||
],
|
||||
providers: [
|
||||
CreateFieldActionHandlerService,
|
||||
UpdateFieldActionHandlerService,
|
||||
|
||||
Reference in New Issue
Block a user