diff --git a/packages/twenty-server/src/modules/workflow/common/exceptions/workflow-common.exception.ts b/packages/twenty-server/src/modules/workflow/common/exceptions/workflow-common.exception.ts new file mode 100644 index 000000000..288c6ae59 --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/common/exceptions/workflow-common.exception.ts @@ -0,0 +1,12 @@ +import { CustomException } from 'src/utils/custom-exception'; + +export class WorkflowCommonException extends CustomException { + constructor(message: string, code: WorkflowCommonExceptionCode) { + super(message, code); + } +} + +export enum WorkflowCommonExceptionCode { + OBJECT_METADATA_NOT_FOUND = 'OBJECT_METADATA_NOT_FOUND', + INVALID_CACHE_VERSION = 'INVALID_CACHE_VERSION', +} diff --git a/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-query-hook.module.ts b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-query-hook.module.ts index d7d2fc7e9..338acabff 100644 --- a/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-query-hook.module.ts +++ b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-query-hook.module.ts @@ -5,6 +5,7 @@ import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm'; import { RecordPositionModule } from 'src/engine/core-modules/record-position/record-position.module'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless-function/serverless-function.module'; +import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module'; import { WorkflowCreateManyPostQueryHook } from 'src/modules/workflow/common/query-hooks/workflow-create-many.post-query.hook'; import { WorkflowCreateManyPreQueryHook } from 'src/modules/workflow/common/query-hooks/workflow-create-many.pre-query.hook'; import { WorkflowCreateOnePostQueryHook } from 'src/modules/workflow/common/query-hooks/workflow-create-one.post-query.hook'; @@ -33,6 +34,7 @@ import { WorkflowVersionValidationWorkspaceService } from 'src/modules/workflow/ NestjsQueryTypeOrmModule.forFeature([ObjectMetadataEntity], 'metadata'), ServerlessFunctionModule, RecordPositionModule, + WorkspaceCacheStorageModule, ], providers: [ WorkflowCreateOnePreQueryHook, diff --git a/packages/twenty-server/src/modules/workflow/common/workflow-common.module.ts b/packages/twenty-server/src/modules/workflow/common/workflow-common.module.ts index 0bce86bfe..55111383d 100644 --- a/packages/twenty-server/src/modules/workflow/common/workflow-common.module.ts +++ b/packages/twenty-server/src/modules/workflow/common/workflow-common.module.ts @@ -1,11 +1,16 @@ import { Module } from '@nestjs/common'; import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless-function/serverless-function.module'; +import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module'; import { WorkflowQueryHookModule } from 'src/modules/workflow/common/query-hooks/workflow-query-hook.module'; import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; @Module({ - imports: [WorkflowQueryHookModule, ServerlessFunctionModule], + imports: [ + WorkflowQueryHookModule, + ServerlessFunctionModule, + WorkspaceCacheStorageModule, + ], providers: [WorkflowCommonWorkspaceService], exports: [WorkflowCommonWorkspaceService], }) diff --git a/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-common.workspace-service.ts b/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-common.workspace-service.ts index f2419f15b..665e085a9 100644 --- a/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-common.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-common.workspace-service.ts @@ -1,8 +1,16 @@ import { Injectable } from '@nestjs/common'; import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service'; +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; +import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; +import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util'; import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; +import { + WorkflowCommonException, + WorkflowCommonExceptionCode, +} from 'src/modules/workflow/common/exceptions/workflow-common.exception'; import { WorkflowEventListenerWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-event-listener.workspace-entity'; import { WorkflowRunWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity'; import { WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; @@ -17,6 +25,7 @@ export class WorkflowCommonWorkspaceService { constructor( private readonly twentyORMManager: TwentyORMManager, private readonly serverlessFunctionService: ServerlessFunctionService, + private readonly workspaceCacheStorageService: WorkspaceCacheStorageService, ) {} async getWorkflowVersionOrFail( @@ -64,6 +73,55 @@ export class WorkflowCommonWorkspaceService { return { ...workflowVersion, trigger: workflowVersion.trigger }; } + async getObjectMetadataItemWithFieldsMaps( + objectNameSingular: string, + workspaceId: string, + ): Promise<{ + objectMetadataItemWithFieldsMaps: ObjectMetadataItemWithFieldMaps; + objectMetadataMaps: ObjectMetadataMaps; + }> { + const currentCacheVersion = + await this.workspaceCacheStorageService.getMetadataVersion(workspaceId); + + if (currentCacheVersion === undefined) { + throw new WorkflowCommonException( + 'Failed to read: Metadata cache version not found', + WorkflowCommonExceptionCode.INVALID_CACHE_VERSION, + ); + } + + const objectMetadataMaps = + await this.workspaceCacheStorageService.getObjectMetadataMaps( + workspaceId, + currentCacheVersion, + ); + + if (!objectMetadataMaps) { + throw new WorkflowCommonException( + 'Failed to read: Object metadata collection not found', + WorkflowCommonExceptionCode.OBJECT_METADATA_NOT_FOUND, + ); + } + + const objectMetadataItemWithFieldsMaps = + getObjectMetadataMapItemByNameSingular( + objectMetadataMaps, + objectNameSingular, + ); + + if (!objectMetadataItemWithFieldsMaps) { + throw new WorkflowCommonException( + `Failed to read: Object ${objectNameSingular} not found`, + WorkflowCommonExceptionCode.OBJECT_METADATA_NOT_FOUND, + ); + } + + return { + objectMetadataItemWithFieldsMaps, + objectMetadataMaps, + }; + } + async cleanWorkflowsSubEntities( workflowIds: string[], workspaceId: string, diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action.ts index f010bf23d..17794d17a 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action.ts @@ -7,11 +7,13 @@ import { WorkflowExecutor } from 'src/modules/workflow/workflow-executor/interfa import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service'; +import { RecordInputTransformerService } from 'src/engine/core-modules/record-transformer/services/record-input-transformer.service'; import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; +import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; import { WorkflowStepExecutorException, WorkflowStepExecutorExceptionCode, @@ -35,6 +37,8 @@ export class CreateRecordWorkflowAction implements WorkflowExecutor { private readonly workspaceEventEmitter: WorkspaceEventEmitter, private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory, private readonly recordPositionService: RecordPositionService, + private readonly recordInputTransformerService: RecordInputTransformerService, + private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService, ) {} async execute({ @@ -88,8 +92,20 @@ export class CreateRecordWorkflowAction implements WorkflowExecutor { workspaceId, }); + const { objectMetadataItemWithFieldsMaps } = + await this.workflowCommonWorkspaceService.getObjectMetadataItemWithFieldsMaps( + workflowActionInput.objectName, + workspaceId, + ); + + const transformedObjectRecord = + await this.recordInputTransformerService.process({ + recordInput: workflowActionInput.objectRecord, + objectMetadataMapItem: objectMetadataItemWithFieldsMaps, + }); + const objectRecord = await repository.save({ - ...workflowActionInput.objectRecord, + ...transformedObjectRecord, position, createdBy: { source: FieldActorSource.WORKFLOW, diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/find-records.workflow-action.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/find-records.workflow-action.ts index bb460cb4f..d51f9c42a 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/find-records.workflow-action.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/find-records.workflow-action.ts @@ -15,12 +15,11 @@ import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/ import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; -import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util'; import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; -import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; +import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; import { WorkflowStepExecutorException, WorkflowStepExecutorExceptionCode, @@ -39,9 +38,9 @@ import { WorkflowFindRecordsActionInput } from 'src/modules/workflow/workflow-ex export class FindRecordsWorkflowAction implements WorkflowExecutor { constructor( private readonly twentyORMManager: TwentyORMManager, - private readonly workspaceCacheStorageService: WorkspaceCacheStorageService, private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory, private readonly featureFlagService: FeatureFlagService, + private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService, ) {} async execute({ @@ -76,42 +75,12 @@ export class FindRecordsWorkflowAction implements WorkflowExecutor { ); } - const currentCacheVersion = - await this.workspaceCacheStorageService.getMetadataVersion(workspaceId); - - if (currentCacheVersion === undefined) { - throw new RecordCRUDActionException( - 'Failed to read: Metadata cache version not found', - RecordCRUDActionExceptionCode.INVALID_REQUEST, - ); - } - - const objectMetadataMaps = - await this.workspaceCacheStorageService.getObjectMetadataMaps( - workspaceId, - currentCacheVersion, - ); - - if (!objectMetadataMaps) { - throw new RecordCRUDActionException( - 'Failed to read: Object metadata collection not found', - RecordCRUDActionExceptionCode.INVALID_REQUEST, - ); - } - - const objectMetadataItemWithFieldsMaps = - getObjectMetadataMapItemByNameSingular( - objectMetadataMaps, + const { objectMetadataItemWithFieldsMaps, objectMetadataMaps } = + await this.workflowCommonWorkspaceService.getObjectMetadataItemWithFieldsMaps( workflowActionInput.objectName, + workspaceId, ); - if (!objectMetadataItemWithFieldsMaps) { - throw new RecordCRUDActionException( - `Failed to read: Object ${workflowActionInput.objectName} not found`, - RecordCRUDActionExceptionCode.INVALID_REQUEST, - ); - } - const featureFlagMaps = await this.featureFlagService.getWorkspaceFeatureFlagsMap(workspaceId); diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/record-crud-action.module.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/record-crud-action.module.ts index 0ba218ba1..ff002d3ce 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/record-crud-action.module.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/record-crud-action.module.ts @@ -4,9 +4,11 @@ import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { RecordPositionModule } from 'src/engine/core-modules/record-position/record-position.module'; +import { RecordTransformerModule } from 'src/engine/core-modules/record-transformer/record-transformer.module'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module'; +import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module'; import { CreateRecordWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action'; import { DeleteRecordWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/delete-record.workflow-action'; import { FindRecordsWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/find-records.workflow-action'; @@ -18,6 +20,8 @@ import { UpdateRecordWorkflowAction } from 'src/modules/workflow/workflow-execut NestjsQueryTypeOrmModule.forFeature([ObjectMetadataEntity], 'metadata'), FeatureFlagModule, RecordPositionModule, + RecordTransformerModule, + WorkflowCommonModule, ], providers: [ ScopedWorkspaceContextFactory, diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/update-record.workflow-action.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/update-record.workflow-action.ts index b69d9ade4..22551238c 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/update-record.workflow-action.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/update-record.workflow-action.ts @@ -1,19 +1,19 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; import deepEqual from 'deep-equal'; +import { Repository } from 'typeorm'; import { WorkflowExecutor } from 'src/modules/workflow/workflow-executor/interfaces/workflow-executor.interface'; import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; +import { RecordInputTransformerService } from 'src/engine/core-modules/record-transformer/services/record-input-transformer.service'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util'; import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; -import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; +import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; import { WorkflowStepExecutorException, WorkflowStepExecutorExceptionCode, @@ -32,11 +32,12 @@ import { WorkflowUpdateRecordActionInput } from 'src/modules/workflow/workflow-e export class UpdateRecordWorkflowAction implements WorkflowExecutor { constructor( private readonly twentyORMManager: TwentyORMManager, - private readonly workspaceCacheStorageService: WorkspaceCacheStorageService, private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory, @InjectRepository(ObjectMetadataEntity, 'metadata') private readonly objectMetadataRepository: Repository, private readonly workspaceEventEmitter: WorkspaceEventEmitter, + private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService, + private readonly recordInputTransformerService: RecordInputTransformerService, ) {} async execute({ @@ -97,48 +98,18 @@ export class UpdateRecordWorkflowAction implements WorkflowExecutor { ); } - const currentCacheVersion = - await this.workspaceCacheStorageService.getMetadataVersion(workspaceId); - - if (currentCacheVersion === undefined) { - throw new RecordCRUDActionException( - 'Failed to read: Metadata cache version not found', - RecordCRUDActionExceptionCode.INVALID_REQUEST, - ); - } - - const objectMetadataMaps = - await this.workspaceCacheStorageService.getObjectMetadataMaps( - workspaceId, - currentCacheVersion, - ); - - if (!objectMetadataMaps) { - throw new RecordCRUDActionException( - 'Failed to read: Object metadata collection not found', - RecordCRUDActionExceptionCode.INVALID_REQUEST, - ); - } - - const objectMetadataItemWithFieldsMaps = - getObjectMetadataMapItemByNameSingular( - objectMetadataMaps, - workflowActionInput.objectName, - ); - - if (!objectMetadataItemWithFieldsMaps) { - throw new RecordCRUDActionException( - `Failed to read: Object ${workflowActionInput.objectName} not found`, - RecordCRUDActionExceptionCode.INVALID_REQUEST, - ); - } - if (workflowActionInput.fieldsToUpdate.length === 0) { return { result: previousObjectRecord, }; } + const { objectMetadataItemWithFieldsMaps } = + await this.workflowCommonWorkspaceService.getObjectMetadataItemWithFieldsMaps( + workflowActionInput.objectName, + workspaceId, + ); + const objectRecordWithFilteredFields = Object.keys( workflowActionInput.objectRecord, ).reduce((acc, key) => { @@ -152,8 +123,14 @@ export class UpdateRecordWorkflowAction implements WorkflowExecutor { return acc; }, {}); + const transformedObjectRecord = + await this.recordInputTransformerService.process({ + recordInput: objectRecordWithFilteredFields, + objectMetadataMapItem: objectMetadataItemWithFieldsMaps, + }); + const objectRecordFormatted = formatData( - objectRecordWithFilteredFields, + transformedObjectRecord, objectMetadataItemWithFieldsMaps, );