feat: mutualize CRUD tools between workflows and AI (#14996)

Closes [#1662](https://github.com/twentyhq/core-team-issues/issues/1662)
This commit is contained in:
Abdul Rahman
2025-10-10 00:55:34 +05:30
committed by GitHub
parent b508bd8f9d
commit 5adc9fe9b2
29 changed files with 899 additions and 1499 deletions
@@ -1,34 +1,25 @@
import { Injectable } from '@nestjs/common';
import { isDefined } from 'class-validator';
import { resolveInput } from 'twenty-shared/utils';
import { canObjectBeManagedByWorkflow } from 'twenty-shared/workflow';
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface';
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 {
RecordCrudException,
RecordCrudExceptionCode,
} from 'src/engine/core-modules/record-crud/exceptions/record-crud.exception';
import { CreateRecordService } from 'src/engine/core-modules/record-crud/services/create-record.service';
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
import { type WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input';
import { type WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type';
import { findStepOrThrow } from 'src/modules/workflow/workflow-executor/utils/find-step-or-throw.util';
import {
RecordCRUDActionException,
RecordCRUDActionExceptionCode,
} from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/exceptions/record-crud-action.exception';
import { type WorkflowCreateRecordActionInput } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/types/workflow-record-crud-action-input.type';
@Injectable()
export class CreateRecordWorkflowAction implements WorkflowAction {
constructor(
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
private readonly createRecordService: CreateRecordService,
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
private readonly recordPositionService: RecordPositionService,
private readonly recordInputTransformerService: RecordInputTransformerService,
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
) {}
async execute({
@@ -41,12 +32,12 @@ export class CreateRecordWorkflowAction implements WorkflowAction {
stepId: currentStepId,
});
const workspaceId = this.scopedWorkspaceContextFactory.create().workspaceId;
const { workspaceId } = this.scopedWorkspaceContextFactory.create();
if (!workspaceId) {
throw new RecordCRUDActionException(
throw new RecordCrudException(
'Failed to create: Workspace ID is required',
RecordCRUDActionExceptionCode.INVALID_REQUEST,
RecordCrudExceptionCode.INVALID_REQUEST,
);
}
@@ -55,62 +46,21 @@ export class CreateRecordWorkflowAction implements WorkflowAction {
context,
) as WorkflowCreateRecordActionInput;
const repository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
workspaceId,
workflowActionInput.objectName,
{ shouldBypassPermissionChecks: true },
);
const { objectMetadataItemWithFieldsMaps } =
await this.workflowCommonWorkspaceService.getObjectMetadataItemWithFieldsMaps(
workflowActionInput.objectName,
workspaceId,
);
if (
!canObjectBeManagedByWorkflow({
nameSingular: objectMetadataItemWithFieldsMaps.nameSingular,
isSystem: objectMetadataItemWithFieldsMaps.isSystem,
})
) {
throw new RecordCRUDActionException(
'Failed to create: Object cannot be created by workflow',
RecordCRUDActionExceptionCode.INVALID_REQUEST,
);
}
const position = await this.recordPositionService.buildRecordPosition({
value: 'first',
objectMetadata: objectMetadataItemWithFieldsMaps,
const toolOutput = await this.createRecordService.execute({
objectName: workflowActionInput.objectName,
objectRecord: workflowActionInput.objectRecord,
workspaceId,
});
const validObjectRecord = Object.fromEntries(
Object.entries(workflowActionInput.objectRecord).filter(([key]) =>
isDefined(objectMetadataItemWithFieldsMaps.fieldIdByName[key]),
),
);
const transformedObjectRecord =
await this.recordInputTransformerService.process({
recordInput: validObjectRecord,
objectMetadataMapItem: objectMetadataItemWithFieldsMaps,
});
const insertResult = await repository.insert({
...transformedObjectRecord,
position,
createdBy: {
source: FieldActorSource.WORKFLOW,
name: 'Workflow',
},
});
const [createdRecord] = insertResult.generatedMaps;
if (!toolOutput.success) {
throw new RecordCrudException(
toolOutput.error || toolOutput.message,
RecordCrudExceptionCode.RECORD_CREATION_FAILED,
);
}
return {
result: createdRecord,
result: toolOutput.result,
};
}
}
@@ -1,14 +1,15 @@
import { Injectable } from '@nestjs/common';
import { isDefined } from 'class-validator';
import { isValidUuid, resolveInput } from 'twenty-shared/utils';
import { canObjectBeManagedByWorkflow } from 'twenty-shared/workflow';
import { isDefined, isValidUuid, resolveInput } from 'twenty-shared/utils';
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface';
import {
RecordCrudException,
RecordCrudExceptionCode,
} from 'src/engine/core-modules/record-crud/exceptions/record-crud.exception';
import { DeleteRecordService } from 'src/engine/core-modules/record-crud/services/delete-record.service';
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
import {
WorkflowStepExecutorException,
WorkflowStepExecutorExceptionCode,
@@ -16,18 +17,13 @@ import {
import { type WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input';
import { type WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type';
import { findStepOrThrow } from 'src/modules/workflow/workflow-executor/utils/find-step-or-throw.util';
import {
RecordCRUDActionException,
RecordCRUDActionExceptionCode,
} from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/exceptions/record-crud-action.exception';
import { isWorkflowDeleteRecordAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/guards/is-workflow-delete-record-action.guard';
import { type WorkflowDeleteRecordActionInput } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/types/workflow-record-crud-action-input.type';
@Injectable()
export class DeleteRecordWorkflowAction implements WorkflowAction {
constructor(
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
private readonly deleteRecordService: DeleteRecordService,
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
) {}
@@ -58,69 +54,37 @@ export class DeleteRecordWorkflowAction implements WorkflowAction {
!isValidUuid(workflowActionInput.objectRecordId) ||
!isDefined(workflowActionInput.objectName)
) {
throw new RecordCRUDActionException(
'Failed to update: Object record ID and name are required',
RecordCRUDActionExceptionCode.INVALID_REQUEST,
throw new RecordCrudException(
'Failed to delete: Object record ID and name are required',
RecordCrudExceptionCode.INVALID_REQUEST,
);
}
const workspaceId = this.scopedWorkspaceContextFactory.create().workspaceId;
const { workspaceId } = this.scopedWorkspaceContextFactory.create();
if (!workspaceId) {
throw new RecordCRUDActionException(
throw new RecordCrudException(
'Failed to delete: Workspace ID is required',
RecordCRUDActionExceptionCode.INVALID_REQUEST,
RecordCrudExceptionCode.INVALID_REQUEST,
);
}
const repository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
workspaceId,
workflowActionInput.objectName,
{ shouldBypassPermissionChecks: true },
);
const { objectMetadataItemWithFieldsMaps } =
await this.workflowCommonWorkspaceService.getObjectMetadataItemWithFieldsMaps(
workflowActionInput.objectName,
workspaceId,
);
if (
!canObjectBeManagedByWorkflow({
nameSingular: objectMetadataItemWithFieldsMaps.nameSingular,
isSystem: objectMetadataItemWithFieldsMaps.isSystem,
})
) {
throw new RecordCRUDActionException(
'Failed to delete: Object cannot be deleted by workflow',
RecordCRUDActionExceptionCode.INVALID_REQUEST,
);
}
const objectRecord = await repository.findOne({
where: {
id: workflowActionInput.objectRecordId,
},
const toolOutput = await this.deleteRecordService.execute({
objectName: workflowActionInput.objectName,
objectRecordId: workflowActionInput.objectRecordId,
workspaceId,
soft: true,
});
if (!objectRecord) {
throw new RecordCRUDActionException(
`Failed to delete: Record ${workflowActionInput.objectName} with id ${workflowActionInput.objectRecordId} not found`,
RecordCRUDActionExceptionCode.RECORD_NOT_FOUND,
if (!toolOutput.success) {
throw new RecordCrudException(
toolOutput.error || toolOutput.message,
RecordCrudExceptionCode.RECORD_DELETION_FAILED,
);
}
const columnsToReturnForSoftDelete: string[] = [];
await repository.softDelete(
workflowActionInput.objectRecordId,
undefined,
columnsToReturnForSoftDelete,
);
return {
result: objectRecord,
result: toolOutput.result,
};
}
}
@@ -1,8 +0,0 @@
import { CustomException } from 'src/utils/custom-exception';
export class RecordCRUDActionException extends CustomException<RecordCRUDActionExceptionCode> {}
export enum RecordCRUDActionExceptionCode {
INVALID_REQUEST = 'INVALID_REQUEST',
RECORD_NOT_FOUND = 'RECORD_NOT_FOUND',
}
@@ -1,24 +1,15 @@
import { Injectable } from '@nestjs/common';
import { type Entity } from '@microsoft/microsoft-graph-types';
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
import { resolveInput } from 'twenty-shared/utils';
import { type ObjectLiteral } from 'typeorm';
import {
type ObjectRecordFilter,
type ObjectRecordOrderBy,
OrderByDirection,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface';
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { type ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
import {
RecordCrudException,
RecordCrudExceptionCode,
} from 'src/engine/core-modules/record-crud/exceptions/record-crud.exception';
import { FindRecordsService } from 'src/engine/core-modules/record-crud/services/find-records.service';
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
import { type WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
import {
WorkflowStepExecutorException,
WorkflowStepExecutorExceptionCode,
@@ -26,19 +17,14 @@ import {
import { type WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input';
import { type WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type';
import { findStepOrThrow } from 'src/modules/workflow/workflow-executor/utils/find-step-or-throw.util';
import {
RecordCRUDActionException,
RecordCRUDActionExceptionCode,
} from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/exceptions/record-crud-action.exception';
import { isWorkflowFindRecordsAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/guards/is-workflow-find-records-action.guard';
import { type WorkflowFindRecordsActionInput } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/types/workflow-record-crud-action-input.type';
@Injectable()
export class FindRecordsWorkflowAction implements WorkflowAction {
constructor(
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
private readonly findRecordsService: FindRecordsService,
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
) {}
async execute({
@@ -63,119 +49,39 @@ export class FindRecordsWorkflowAction implements WorkflowAction {
context,
) as WorkflowFindRecordsActionInput;
const workspaceId = this.scopedWorkspaceContextFactory.create().workspaceId;
const { workspaceId } = this.scopedWorkspaceContextFactory.create();
if (!workspaceId) {
throw new RecordCRUDActionException(
throw new RecordCrudException(
'Failed to read: Workspace ID is required',
RecordCRUDActionExceptionCode.INVALID_REQUEST,
RecordCrudExceptionCode.INVALID_REQUEST,
);
}
const repository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
workspaceId,
workflowActionInput.objectName,
{ shouldBypassPermissionChecks: true },
const toolOutput = await this.findRecordsService.execute({
objectName: workflowActionInput.objectName,
filter: workflowActionInput.filter?.gqlOperationFilter,
orderBy: workflowActionInput.orderBy,
limit: workflowActionInput.limit,
workspaceId,
});
if (!toolOutput.success) {
throw new RecordCrudException(
toolOutput.error || toolOutput.message,
RecordCrudExceptionCode.QUERY_FAILED,
);
}
const { objectMetadataItemWithFieldsMaps, objectMetadataMaps } =
await this.workflowCommonWorkspaceService.getObjectMetadataItemWithFieldsMaps(
workflowActionInput.objectName,
workspaceId,
);
const graphqlQueryParser = new GraphqlQueryParser(
objectMetadataItemWithFieldsMaps,
objectMetadataMaps,
);
const objectRecords = await this.getObjectRecords(
workflowActionInput,
objectMetadataItemWithFieldsMaps,
objectMetadataMaps,
repository,
graphqlQueryParser,
);
const totalCount = await this.getTotalCount(
workflowActionInput,
repository,
graphqlQueryParser,
);
const records = toolOutput.result?.records ?? [];
const totalCount = toolOutput.result?.count ?? 0;
return {
result: {
first: objectRecords[0],
all: objectRecords,
first: records[0],
all: records,
totalCount,
},
};
}
private async getObjectRecords<T extends ObjectLiteral>(
workflowActionInput: WorkflowFindRecordsActionInput,
_objectMetadataItemWithFieldsMaps: ObjectMetadataItemWithFieldMaps,
_objectMetadataMaps: ObjectMetadataMaps,
repository: WorkspaceRepository<T>,
graphqlQueryParser: GraphqlQueryParser,
) {
const queryBuilder = repository.createQueryBuilder(
workflowActionInput.objectName,
);
const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
queryBuilder,
workflowActionInput.objectName,
workflowActionInput.filter?.gqlOperationFilter ??
({} as ObjectRecordFilter),
);
const orderByWithIdCondition = [
...(workflowActionInput.orderBy ?? []),
{ id: OrderByDirection.AscNullsFirst },
] as ObjectRecordOrderBy;
const withOrderByQueryBuilder = graphqlQueryParser.applyOrderToBuilder(
withFilterQueryBuilder,
orderByWithIdCondition,
workflowActionInput.objectName,
false,
);
return withOrderByQueryBuilder
.take(
workflowActionInput.limit
? Math.min(workflowActionInput.limit, QUERY_MAX_RECORDS)
: QUERY_MAX_RECORDS,
)
.getMany();
}
private async getTotalCount(
workflowActionInput: WorkflowFindRecordsActionInput,
repository: WorkspaceRepository<Entity>,
graphqlQueryParser: GraphqlQueryParser,
) {
const countQueryBuilder = repository.createQueryBuilder(
workflowActionInput.objectName,
);
const withFilterCountQueryBuilder = graphqlQueryParser.applyFilterToBuilder(
countQueryBuilder,
workflowActionInput.objectName,
workflowActionInput.filter?.gqlOperationFilter ??
({} as ObjectRecordFilter),
);
const withDeletedCountQueryBuilder =
graphqlQueryParser.applyDeletedAtToBuilder(
withFilterCountQueryBuilder,
workflowActionInput.filter?.gqlOperationFilter
? workflowActionInput.filter.gqlOperationFilter
: ({} as ObjectRecordFilter),
);
return withDeletedCountQueryBuilder.getCount();
}
}
@@ -1,26 +1,14 @@
import { Module } from '@nestjs/common';
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
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 { RecordCrudModule } from 'src/engine/core-modules/record-crud/record-crud.module';
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';
import { UpdateRecordWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/update-record.workflow-action';
@Module({
imports: [
WorkspaceCacheStorageModule,
NestjsQueryTypeOrmModule.forFeature([ObjectMetadataEntity]),
RecordPositionModule,
RecordTransformerModule,
WorkflowCommonModule,
],
imports: [RecordCrudModule],
providers: [
ScopedWorkspaceContextFactory,
CreateRecordWorkflowAction,
@@ -3,17 +3,16 @@ import {
type ObjectRecordOrderBy,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type ObjectRecord = Record<string, any>;
import { type ObjectRecordProperties } from 'src/engine/core-modules/record-crud/types/object-record-properties.type';
export type WorkflowCreateRecordActionInput = {
objectName: string;
objectRecord: ObjectRecord;
objectRecord: ObjectRecordProperties;
};
export type WorkflowUpdateRecordActionInput = {
objectName: string;
objectRecord: ObjectRecord;
objectRecord: ObjectRecordProperties;
objectRecordId: string;
fieldsToUpdate: string[];
};
@@ -1,15 +1,15 @@
import { Injectable } from '@nestjs/common';
import deepEqual from 'deep-equal';
import { isDefined, isValidUuid, resolveInput } from 'twenty-shared/utils';
import { canObjectBeManagedByWorkflow } from 'twenty-shared/workflow';
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface';
import { RecordInputTransformerService } from 'src/engine/core-modules/record-transformer/services/record-input-transformer.service';
import {
RecordCrudException,
RecordCrudExceptionCode,
} from 'src/engine/core-modules/record-crud/exceptions/record-crud.exception';
import { UpdateRecordService } from 'src/engine/core-modules/record-crud/services/update-record.service';
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
import {
WorkflowStepExecutorException,
WorkflowStepExecutorExceptionCode,
@@ -17,20 +17,14 @@ import {
import { type WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input';
import { type WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type';
import { findStepOrThrow } from 'src/modules/workflow/workflow-executor/utils/find-step-or-throw.util';
import {
RecordCRUDActionException,
RecordCRUDActionExceptionCode,
} from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/exceptions/record-crud-action.exception';
import { isWorkflowUpdateRecordAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/guards/is-workflow-update-record-action.guard';
import { type WorkflowUpdateRecordActionInput } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/types/workflow-record-crud-action-input.type';
@Injectable()
export class UpdateRecordWorkflowAction implements WorkflowAction {
constructor(
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
private readonly updateRecordService: UpdateRecordService,
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
private readonly recordInputTransformerService: RecordInputTransformerService,
) {}
async execute({
@@ -60,97 +54,38 @@ export class UpdateRecordWorkflowAction implements WorkflowAction {
!isValidUuid(workflowActionInput.objectRecordId) ||
!isDefined(workflowActionInput.objectName)
) {
throw new RecordCRUDActionException(
throw new RecordCrudException(
'Failed to update: Object record ID and name are required',
RecordCRUDActionExceptionCode.INVALID_REQUEST,
RecordCrudExceptionCode.INVALID_REQUEST,
);
}
const workspaceId = this.scopedWorkspaceContextFactory.create().workspaceId;
const { workspaceId } = this.scopedWorkspaceContextFactory.create();
if (!workspaceId) {
throw new RecordCRUDActionException(
throw new RecordCrudException(
'Failed to update: Workspace ID is required',
RecordCRUDActionExceptionCode.INVALID_REQUEST,
RecordCrudExceptionCode.INVALID_REQUEST,
);
}
const repository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
workspaceId,
workflowActionInput.objectName,
{ shouldBypassPermissionChecks: true },
);
const previousObjectRecord = await repository.findOne({
where: {
id: workflowActionInput.objectRecordId,
},
const toolOutput = await this.updateRecordService.execute({
objectName: workflowActionInput.objectName,
objectRecordId: workflowActionInput.objectRecordId,
objectRecord: workflowActionInput.objectRecord,
fieldsToUpdate: workflowActionInput.fieldsToUpdate,
workspaceId,
});
if (!previousObjectRecord) {
throw new RecordCRUDActionException(
`Failed to update: Record ${workflowActionInput.objectName} with id ${workflowActionInput.objectRecordId} not found`,
RecordCRUDActionExceptionCode.RECORD_NOT_FOUND,
if (!toolOutput.success) {
throw new RecordCrudException(
toolOutput.error || toolOutput.message,
RecordCrudExceptionCode.RECORD_UPDATE_FAILED,
);
}
if (workflowActionInput.fieldsToUpdate.length === 0) {
return {
result: previousObjectRecord,
};
}
const { objectMetadataItemWithFieldsMaps } =
await this.workflowCommonWorkspaceService.getObjectMetadataItemWithFieldsMaps(
workflowActionInput.objectName,
workspaceId,
);
if (
!canObjectBeManagedByWorkflow({
nameSingular: objectMetadataItemWithFieldsMaps.nameSingular,
isSystem: objectMetadataItemWithFieldsMaps.isSystem,
})
) {
throw new RecordCRUDActionException(
'Failed to update: Object cannot be updated by workflow',
RecordCRUDActionExceptionCode.INVALID_REQUEST,
);
}
const objectRecordWithFilteredFields = Object.keys(
workflowActionInput.objectRecord,
).reduce((acc, key) => {
if (workflowActionInput.fieldsToUpdate.includes(key)) {
return {
...acc,
[key]: workflowActionInput.objectRecord[key],
};
}
return acc;
}, {});
const transformedObjectRecord =
await this.recordInputTransformerService.process({
recordInput: objectRecordWithFilteredFields,
objectMetadataMapItem: objectMetadataItemWithFieldsMaps,
});
const updatedObjectRecord = {
...previousObjectRecord,
...objectRecordWithFilteredFields,
};
if (!deepEqual(updatedObjectRecord, previousObjectRecord)) {
await repository.update(workflowActionInput.objectRecordId, {
...transformedObjectRecord,
});
}
return {
result: updatedObjectRecord,
result: toolOutput.result,
};
}
}