From 70915614895d6e91cf34c312c05ee24c42c4bd89 Mon Sep 17 00:00:00 2001 From: Paul Rastoin <45004772+prastoin@users.noreply.github.com> Date: Mon, 13 Apr 2026 11:19:57 +0200 Subject: [PATCH] Fix command `FixMessageThreadViewAndLabelIdentifierCommand` (#19622) ## Introduction View standard backfill seed has been introduced during a partial patch, a window exists where some workspaces got created without the view and wasn't included in the upgrade process The fix isn't covering all the view that has been missed during that time, only the one required for the command to pass --- ...hread-view-and-label-identifier.command.ts | 56 ++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-21/1-21-workspace-command-1775500014000-fix-message-thread-view-and-label-identifier.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-21/1-21-workspace-command-1775500014000-fix-message-thread-view-and-label-identifier.command.ts index dba76ce1a7..36b283df65 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-21/1-21-workspace-command-1775500014000-fix-message-thread-view-and-label-identifier.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-21/1-21-workspace-command-1775500014000-fix-message-thread-view-and-label-identifier.command.ts @@ -9,10 +9,12 @@ import { ApplicationService } from 'src/engine/core-modules/application/applicat import { RegisteredWorkspaceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-workspace-command.decorator'; import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util'; import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { type FlatView } from 'src/engine/metadata-modules/flat-view/types/flat-view.type'; 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'; import { type UniversalFlatObjectMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-object-metadata.type'; +import { type UniversalFlatView } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view.type'; import { type UniversalFlatViewField } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-field.type'; const MESSAGE_THREAD_OBJECT_UNIVERSAL_IDENTIFIER = @@ -25,6 +27,7 @@ const ALL_MESSAGE_THREADS_VIEW_UNIVERSAL_IDENTIFIER = type WorkspaceFlatEntityMaps = Awaited< ReturnType >; +type FlatViewMaps = WorkspaceFlatEntityMaps['flatViewMaps']; type FlatViewFieldMaps = WorkspaceFlatEntityMaps['flatViewFieldMaps']; type FlatFieldMetadataMaps = WorkspaceFlatEntityMaps['flatFieldMetadataMaps']; @@ -79,10 +82,12 @@ export class FixMessageThreadViewAndLabelIdentifierCommand extends ActiveOrSuspe const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps, flatFieldMetadataMaps: existingFlatFieldMetadataMaps, + flatViewMaps: existingFlatViewMaps, flatViewFieldMaps: existingFlatViewFieldMaps, } = await this.workspaceCacheService.getOrRecompute(workspaceId, [ 'flatObjectMetadataMaps', 'flatFieldMetadataMaps', + 'flatViewMaps', 'flatViewFieldMaps', ]); @@ -100,6 +105,11 @@ export class FixMessageThreadViewAndLabelIdentifierCommand extends ActiveOrSuspe return; } + const viewToCreate = this.computeMessageThreadViewToCreate({ + standardAllFlatEntityMaps, + existingFlatViewMaps, + }); + const viewFieldsToDelete = this.computeMessageThreadViewFieldsToDelete({ existingFlatViewFieldMaps, }); @@ -115,11 +125,12 @@ export class FixMessageThreadViewAndLabelIdentifierCommand extends ActiveOrSuspe existingFlatFieldMetadataMaps, }); + const hasViewToCreate = isDefined(viewToCreate); const hasViewFieldChanges = viewFieldsToDelete.length > 0 || viewFieldsToCreate.length > 0; const hasObjectMetadataChange = isDefined(flatObjectMetadataToUpdate); - if (!hasViewFieldChanges && !hasObjectMetadataChange) { + if (!hasViewToCreate && !hasViewFieldChanges && !hasObjectMetadataChange) { this.logger.log( `Nothing to fix for messageThread in workspace ${workspaceId}`, ); @@ -128,7 +139,7 @@ export class FixMessageThreadViewAndLabelIdentifierCommand extends ActiveOrSuspe } this.logger.log( - `${isDryRun ? '[DRY RUN] ' : ''}Fixing messageThread in workspace ${workspaceId}: deleting ${viewFieldsToDelete.length} view fields, creating ${viewFieldsToCreate.length} view fields${hasObjectMetadataChange ? ', repointing labelIdentifierFieldMetadataId to subject' : ''}`, + `${isDryRun ? '[DRY RUN] ' : ''}Fixing messageThread in workspace ${workspaceId}: ${hasViewToCreate ? 'creating allMessageThreads view, ' : ''}deleting ${viewFieldsToDelete.length} view fields, creating ${viewFieldsToCreate.length} view fields${hasObjectMetadataChange ? ', repointing labelIdentifierFieldMetadataId to subject' : ''}`, ); if (isDryRun) { @@ -146,6 +157,11 @@ export class FixMessageThreadViewAndLabelIdentifierCommand extends ActiveOrSuspe ? [flatObjectMetadataToUpdate] : [], }, + view: { + flatEntityToCreate: hasViewToCreate ? [viewToCreate] : [], + flatEntityToDelete: [], + flatEntityToUpdate: [], + }, viewField: { flatEntityToCreate: viewFieldsToCreate, flatEntityToDelete: viewFieldsToDelete, @@ -174,6 +190,42 @@ export class FixMessageThreadViewAndLabelIdentifierCommand extends ActiveOrSuspe ); } + // If the allMessageThreads view does not exist in the workspace, return the + // standard definition so it can be created before its view fields. + private computeMessageThreadViewToCreate({ + standardAllFlatEntityMaps, + existingFlatViewMaps, + }: { + standardAllFlatEntityMaps: ReturnType< + typeof computeTwentyStandardApplicationAllFlatEntityMaps + >['allFlatEntityMaps']; + existingFlatViewMaps: FlatViewMaps; + }): UniversalFlatView | undefined { + const existingView = findFlatEntityByUniversalIdentifier({ + flatEntityMaps: existingFlatViewMaps, + universalIdentifier: ALL_MESSAGE_THREADS_VIEW_UNIVERSAL_IDENTIFIER, + }); + + if (isDefined(existingView)) { + return undefined; + } + + const standardView = findFlatEntityByUniversalIdentifier( + { + flatEntityMaps: standardAllFlatEntityMaps.flatViewMaps, + universalIdentifier: ALL_MESSAGE_THREADS_VIEW_UNIVERSAL_IDENTIFIER, + }, + ); + + if (!isDefined(standardView)) { + throw new Error( + 'allMessageThreads view not found in standard definition', + ); + } + + return standardView; + } + // Delete every existing view field attached to the standard allMessageThreads view // so we can recreate them from the current standard definition in one diff. private computeMessageThreadViewFieldsToDelete({