diff --git a/packages/twenty-server/src/engine/api/common/common-query-runners/common-merge-many-query-runner.service.ts b/packages/twenty-server/src/engine/api/common/common-query-runners/common-merge-many-query-runner.service.ts index 869c9ce882..c713ac7c80 100644 --- a/packages/twenty-server/src/engine/api/common/common-query-runners/common-merge-many-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/common/common-query-runners/common-merge-many-query-runner.service.ts @@ -33,7 +33,6 @@ import { GraphqlQueryRunnerException, GraphqlQueryRunnerExceptionCode, } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; -import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper'; import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return'; import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select'; import { hasRecordFieldValue } from 'src/engine/api/graphql/graphql-query-runner/utils/has-record-field-value.util'; @@ -73,15 +72,11 @@ export class CommonMergeManyQueryRunnerService extends CommonBaseQueryRunnerServ recordsToMerge, priorityRecord.id, objectMetadataItemWithFieldMaps, + args.dryRun ?? false, ); if (args.dryRun) { - return this.createDryRunResponse( - priorityRecord, - mergedData, - objectMetadataItemWithFieldMaps, - objectMetadataMaps, - ); + return this.createDryRunResponse(priorityRecord, mergedData); } const idsToDelete = args.ids.filter((id) => id !== priorityRecord.id); @@ -139,6 +134,9 @@ export class CommonMergeManyQueryRunnerService extends CommonBaseQueryRunnerServ const recordsToMerge = await context.repository.find({ where: { id: In(args.ids) }, select: columnsToSelect, + ...(args.dryRun + ? { relations: args.selectedFieldsResult.relations } + : {}), }); if (recordsToMerge.length !== args.ids.length) { @@ -175,6 +173,7 @@ export class CommonMergeManyQueryRunnerService extends CommonBaseQueryRunnerServ recordsToMerge: ObjectRecord[], priorityRecordId: string, objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps, + isDryRun = false, ): Partial { const mergedResult: Partial = {}; @@ -217,10 +216,18 @@ export class CommonMergeManyQueryRunnerService extends CommonBaseQueryRunnerServ return; } + const relationType = + isDryRun && fieldMetadata.type === FieldMetadataType.RELATION + ? (fieldMetadata.settings as FieldMetadataRelationSettings) + ?.relationType + : undefined; + mergedResult[fieldName] = mergeFieldValues( fieldMetadata.type, recordsWithValues, priorityRecordId, + isDryRun, + relationType, ); } }); @@ -242,25 +249,15 @@ export class CommonMergeManyQueryRunnerService extends CommonBaseQueryRunnerServ private createDryRunResponse( priorityRecord: ObjectRecord, mergedData: Partial, - objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps, - objectMetadataMaps: ObjectMetadataMaps, ): ObjectRecord { - const dryRunRecord = { + const dryRunRecord: ObjectRecord = { ...priorityRecord, ...mergedData, id: uuidv4(), deletedAt: new Date().toISOString(), - } as ObjectRecord; + }; - const typeORMObjectRecordsParser = - new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps); - - return typeORMObjectRecordsParser.processRecord({ - objectRecord: dryRunRecord, - objectName: objectMetadataItemWithFieldMaps.nameSingular, - take: 1, - totalCount: 1, - }); + return dryRunRecord; } private async updatePriorityRecord( diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/merge-field-values.util.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/merge-field-values.util.ts index bccd850dbd..900c4f48d6 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/merge-field-values.util.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/merge-field-values.util.ts @@ -1,4 +1,4 @@ -import { FieldMetadataType } from 'twenty-shared/types'; +import { FieldMetadataType, type RelationType } from 'twenty-shared/types'; import { type EmailsMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/emails.composite-type'; import { type LinksMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/links.composite-type'; @@ -8,18 +8,32 @@ import { mergeArrayFieldValues } from './merge-array-field-values.util'; import { mergeEmailsFieldValues } from './merge-emails-field-values.util'; import { mergeLinksFieldValues } from './merge-links-field-values.util'; import { mergePhonesFieldValues } from './merge-phones-field-values.util'; +import { mergeRelationFieldValuesForDryRunRecord } from './merge-relation-field-values-for-dry-run-record.util'; import { selectPriorityFieldValue } from './select-priority-field-value.util'; export const mergeFieldValues = ( fieldType: FieldMetadataType, recordsWithValues: { value: unknown; recordId: string }[], priorityRecordId: string, + isDryRun = false, + relationType?: RelationType, ): unknown => { switch (fieldType) { case FieldMetadataType.ARRAY: case FieldMetadataType.MULTI_SELECT: return mergeArrayFieldValues(recordsWithValues); + case FieldMetadataType.RELATION: + if (isDryRun) { + return mergeRelationFieldValuesForDryRunRecord( + recordsWithValues, + relationType, + priorityRecordId, + ); + } + + return selectPriorityFieldValue(recordsWithValues, priorityRecordId); + case FieldMetadataType.EMAILS: return mergeEmailsFieldValues( recordsWithValues as { value: EmailsMetadata; recordId: string }[], diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/merge-relation-field-values-for-dry-run-record.util.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/merge-relation-field-values-for-dry-run-record.util.ts new file mode 100644 index 0000000000..586bb6f28c --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/merge-relation-field-values-for-dry-run-record.util.ts @@ -0,0 +1,37 @@ +import { type ObjectRecord, RelationType } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; + +import { selectPriorityFieldValue } from 'src/engine/api/graphql/graphql-query-runner/utils/select-priority-field-value.util'; + +export const mergeRelationFieldValuesForDryRunRecord = ( + recordsWithValues: { value: unknown; recordId: string }[], + relationType: RelationType | undefined, + priorityRecordId: string, +): ObjectRecord | ObjectRecord[] | null => { + if (relationType === RelationType.ONE_TO_MANY) { + return mergeOneToManyRelationArrays(recordsWithValues); + } + + return selectPriorityFieldValue( + recordsWithValues as { value: ObjectRecord | null; recordId: string }[], + priorityRecordId, + ); +}; + +const mergeOneToManyRelationArrays = ( + recordsWithValues: { value: unknown; recordId: string }[], +): ObjectRecord[] => { + const uniqueRelationsMap = new Map(); + + recordsWithValues.forEach(({ value }) => { + if (Array.isArray(value)) { + value.forEach((relation: ObjectRecord) => { + if (isDefined(relation?.id)) { + uniqueRelationsMap.set(relation.id, relation); + } + }); + } + }); + + return Array.from(uniqueRelationsMap.values()); +};