From 5a46cf7bd32c6bf355d6d04e367b1869d932e32c Mon Sep 17 00:00:00 2001 From: Paul Rastoin <45004772+prastoin@users.noreply.github.com> Date: Thu, 19 Feb 2026 13:18:42 +0100 Subject: [PATCH] Refactor message backfill command (#18078) # Introduction Atomically create the field and object to be created And avoid synchronizing unrelated non up to date object and fields Followup https://github.com/twentyhq/twenty/pull/17398 --- ...sage-association-message-folder.command.ts | 138 +++++++----------- 1 file changed, 53 insertions(+), 85 deletions(-) diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-19/1-19-backfill-message-channel-message-association-message-folder.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-19/1-19-backfill-message-channel-message-association-message-folder.command.ts index 8d70154ab4..1781dba9ba 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-19/1-19-backfill-message-channel-message-association-message-folder.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-19/1-19-backfill-message-channel-message-association-message-folder.command.ts @@ -10,18 +10,22 @@ import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/worksp import { ApplicationService } from 'src/engine/core-modules/application/services/application.service'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; -import { type SyncableFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type'; -import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; -import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util'; +import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util'; import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util'; -import { getSubFlatEntityMapsByApplicationIdsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/get-sub-flat-entity-maps-by-application-ids-or-throw.util'; -import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; -import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { findManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-universal-identifier-in-universal-flat-entity-maps-or-throw.util'; +import { isMorphOrRelationUniversalFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-morph-or-relation-flat-field-metadata.util'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; import { computeTwentyStandardApplicationAllFlatEntityMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant'; import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service'; +const OBJECT_UNIVERSAL_IDENTIFIER_TO_CREATE = + STANDARD_OBJECTS.messageChannelMessageAssociationMessageFolder + .universalIdentifier; +const FIELD_UNIVERSAL_IDENTIFIERS_TO_CREATE = Object.values( + STANDARD_OBJECTS.messageChannelMessageAssociationMessageFolder.fields, +).map((el) => el.universalIdentifier); + @Command({ name: 'upgrade:1-19:backfill-message-channel-message-association-message-folder', description: @@ -40,34 +44,6 @@ export class BackfillMessageChannelMessageAssociationMessageFolderCommand extend super(workspaceRepository, twentyORMGlobalManager, dataSourceService); } - private addNewEntitiesToFlatEntityMaps({ - fromMaps, - standardBuilderMaps, - }: { - fromMaps: FlatEntityMaps; - standardBuilderMaps: FlatEntityMaps; - }): FlatEntityMaps { - let toMaps = fromMaps; - - for (const [universalIdentifier, entity] of Object.entries( - standardBuilderMaps.byUniversalIdentifier, - )) { - if ( - !isDefined(entity) || - isDefined(fromMaps.byUniversalIdentifier[universalIdentifier]) - ) { - continue; - } - - toMaps = addFlatEntityToFlatEntityMapsOrThrow({ - flatEntity: entity, - flatEntityMaps: toMaps, - }); - } - - return toMaps; - } - override async runOnWorkspace({ workspaceId, options, @@ -78,18 +54,14 @@ export class BackfillMessageChannelMessageAssociationMessageFolderCommand extend `${isDryRun ? '[DRY RUN] ' : ''}Starting backfill of messageChannelMessageAssociationMessageFolder for workspace ${workspaceId}`, ); - const { flatObjectMetadataMaps, flatFieldMetadataMaps, featureFlagsMap } = + const { flatObjectMetadataMaps } = await this.workspaceCacheService.getOrRecompute(workspaceId, [ 'flatObjectMetadataMaps', - 'flatFieldMetadataMaps', - 'featureFlagsMap', ]); const existingObject = findFlatEntityByUniversalIdentifier({ flatEntityMaps: flatObjectMetadataMaps, - universalIdentifier: - STANDARD_OBJECTS.messageChannelMessageAssociationMessageFolder - .universalIdentifier, + universalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER_TO_CREATE, }); if (existingObject) { @@ -113,67 +85,63 @@ export class BackfillMessageChannelMessageAssociationMessageFolderCommand extend { workspaceId }, ); - const fromFlatObjectMetadataMaps = - getSubFlatEntityMapsByApplicationIdsOrThrow({ - applicationIds: [twentyStandardFlatApplication.id], - flatEntityMaps: flatObjectMetadataMaps, + const { allFlatEntityMaps: standardAllFlatEntityMaps } = + computeTwentyStandardApplicationAllFlatEntityMaps({ + now: new Date().toISOString(), + workspaceId, + twentyStandardApplicationId: twentyStandardFlatApplication.id, }); - const fromFlatFieldMetadataMaps = - getSubFlatEntityMapsByApplicationIdsOrThrow({ - applicationIds: [twentyStandardFlatApplication.id], - flatEntityMaps: flatFieldMetadataMaps, + const flatObjectMetadataToCreate = + findFlatEntityByUniversalIdentifierOrThrow({ + flatEntityMaps: standardAllFlatEntityMaps.flatObjectMetadataMaps, + universalIdentifier: OBJECT_UNIVERSAL_IDENTIFIER_TO_CREATE, }); - const { - allFlatEntityMaps: standardAllFlatEntityMaps, - idByUniversalIdentifierByMetadataName, - } = computeTwentyStandardApplicationAllFlatEntityMaps({ - now: new Date().toISOString(), - workspaceId, - twentyStandardApplicationId: twentyStandardFlatApplication.id, - }); - - const toFlatObjectMetadataMaps = - this.addNewEntitiesToFlatEntityMaps({ - fromMaps: fromFlatObjectMetadataMaps, - standardBuilderMaps: standardAllFlatEntityMaps.flatObjectMetadataMaps, + const flatFieldMetadataToCreateOnObject = + findManyFlatEntityByUniversalIdentifierInUniversalFlatEntityMapsOrThrow({ + flatEntityMaps: standardAllFlatEntityMaps.flatFieldMetadataMaps, + universalIdentifiers: FIELD_UNIVERSAL_IDENTIFIERS_TO_CREATE, }); + const relatedFlatFieldMetadataToCreate = flatFieldMetadataToCreateOnObject + .map((flatFieldMetadata) => { + if (!isMorphOrRelationUniversalFlatFieldMetadata(flatFieldMetadata)) { + return undefined; + } - const toFlatFieldMetadataMaps = - this.addNewEntitiesToFlatEntityMaps({ - fromMaps: fromFlatFieldMetadataMaps, - standardBuilderMaps: standardAllFlatEntityMaps.flatFieldMetadataMaps, - }); + return findFlatEntityByUniversalIdentifierOrThrow({ + flatEntityMaps: standardAllFlatEntityMaps.flatFieldMetadataMaps, + universalIdentifier: + flatFieldMetadata.relationTargetFieldMetadataUniversalIdentifier, + }); + }) + .filter(isDefined); const validateAndBuildResult = - await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigrationFromTo( + await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( { - buildOptions: { - isSystemBuild: true, - applicationUniversalIdentifier: - twentyStandardFlatApplication.universalIdentifier, - }, - fromToAllFlatEntityMaps: { - flatObjectMetadataMaps: { - from: fromFlatObjectMetadataMaps, - to: toFlatObjectMetadataMaps, + allFlatEntityOperationByMetadataName: { + objectMetadata: { + flatEntityToCreate: [flatObjectMetadataToCreate], + flatEntityToDelete: [], + flatEntityToUpdate: [], }, - flatFieldMetadataMaps: { - from: fromFlatFieldMetadataMaps, - to: toFlatFieldMetadataMaps, + fieldMetadata: { + flatEntityToCreate: [ + ...flatFieldMetadataToCreateOnObject, + ...relatedFlatFieldMetadataToCreate, + ], + flatEntityToDelete: [], + flatEntityToUpdate: [], }, }, workspaceId, - additionalCacheDataMaps: { - featureFlagsMap, - }, - - idByUniversalIdentifierByMetadataName, + applicationUniversalIdentifier: + twentyStandardFlatApplication.universalIdentifier, }, ); - if (isDefined(validateAndBuildResult)) { + if (validateAndBuildResult.status === 'fail') { this.logger.error( `Failed to create messageChannelMessageAssociationMessageFolder:\n${JSON.stringify(validateAndBuildResult, null, 2)}`, );