From 58336fb70f69335974164fb55957ce72e00dcfec Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Wed, 18 Mar 2026 11:56:19 +0100 Subject: [PATCH] fix: navigation menu item type backfill and frontend loading (#18730) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Move `BackfillNavigationMenuItemTypeCommand` from the 1-19 to the 1-20 upgrade path and split the DB transaction into two phases (data backfill, then schema changes) to avoid the PostgreSQL error "cannot ALTER TABLE because it has pending trigger events." - Fix backfill logic to prefer `OBJECT` over `VIEW` for navigation menu items that have `targetObjectMetadataId`, and correct already mis-typed items. Tighten the `CHECK` constraint to enforce `viewId IS NULL` for `OBJECT` type items. - On the frontend, force `navigationMenuItems` into `staleEntityKeys` when the server's `minimalMetadata` response omits the collection hash (happens when the Redis cache hasn't been warmed after an upgrade), ensuring the sidebar loads navigation items. ## Test plan - [ ] Upgrade from 1.18 or 1.19 to 1.20 and verify the migration completes without errors - [ ] Verify navigation menu items of type `OBJECT` do not have a `viewId` set in the database - [ ] Sign out and sign in — confirm navigation menu items appear in the sidebar on first load - [ ] Verify `VIEW`-typed items also appear correctly in the sidebar Made with [Cursor](https://cursor.com) --- .../hooks/useLoadMinimalMetadata.ts | 14 ++++++++++ .../1-19-upgrade-version-command.module.ts | 3 --- ...fill-navigation-menu-item-type.command.ts} | 26 ++++++++++++++++--- .../1-20-upgrade-version-command.module.ts | 3 +++ .../upgrade.command.ts | 5 ++-- 5 files changed, 41 insertions(+), 10 deletions(-) rename packages/twenty-server/src/database/commands/upgrade-version-command/{1-19/1-19-backfill-navigation-menu-item-type.command.ts => 1-20/1-20-backfill-navigation-menu-item-type.command.ts} (86%) diff --git a/packages/twenty-front/src/modules/metadata-store/hooks/useLoadMinimalMetadata.ts b/packages/twenty-front/src/modules/metadata-store/hooks/useLoadMinimalMetadata.ts index 5b8cde955e..5ec6ca6b0f 100644 --- a/packages/twenty-front/src/modules/metadata-store/hooks/useLoadMinimalMetadata.ts +++ b/packages/twenty-front/src/modules/metadata-store/hooks/useLoadMinimalMetadata.ts @@ -1,5 +1,6 @@ import { FIND_MINIMAL_METADATA } from '@/metadata-store/graphql/queries/findMinimalMetadata'; import { + ALL_METADATA_ENTITY_KEYS, metadataStoreState, type MetadataEntityKey, } from '@/metadata-store/states/metadataStoreState'; @@ -31,6 +32,8 @@ export const useLoadMinimalMetadata = () => { const staleEntityKeys: MetadataEntityKey[] = []; + const entityKeysWithServerHash = new Set(); + if (isDefined(collectionHashes)) { for (const { collectionName, hash } of collectionHashes) { const entityKey = mapAllMetadataNameToEntityKey(collectionName); @@ -39,6 +42,8 @@ export const useLoadMinimalMetadata = () => { continue; } + entityKeysWithServerHash.add(entityKey); + const entry = store.get(metadataStoreState.atomFamily(entityKey)); if (entry.currentCollectionHash !== hash) { @@ -52,6 +57,15 @@ export const useLoadMinimalMetadata = () => { } } + for (const entityKey of ALL_METADATA_ENTITY_KEYS) { + if ( + !entityKeysWithServerHash.has(entityKey) && + !staleEntityKeys.includes(entityKey) + ) { + staleEntityKeys.push(entityKey); + } + } + const objectsEntry = store.get( metadataStoreState.atomFamily('objectMetadataItems'), ); diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-19/1-19-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-19/1-19-upgrade-version-command.module.ts index f440e67e64..5e51e2b97d 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-19/1-19-upgrade-version-command.module.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-19/1-19-upgrade-version-command.module.ts @@ -4,7 +4,6 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { AddMissingSystemFieldsToStandardObjectsCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-add-missing-system-fields-to-standard-objects.command'; import { BackfillMessageChannelMessageAssociationMessageFolderCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-backfill-message-channel-message-association-message-folder.command'; import { BackfillMissingStandardViewsCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-backfill-missing-standard-views.command'; -import { BackfillNavigationMenuItemTypeCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-backfill-navigation-menu-item-type.command'; import { BackfillSystemFieldsIsSystemCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-backfill-system-fields-is-system.command'; import { FixInvalidStandardUniversalIdentifiersCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-fix-invalid-standard-universal-identifiers.command'; import { SeedServerIdCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-seed-server-id.command'; @@ -35,7 +34,6 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace AddMissingSystemFieldsToStandardObjectsCommand, BackfillMessageChannelMessageAssociationMessageFolderCommand, BackfillMissingStandardViewsCommand, - BackfillNavigationMenuItemTypeCommand, FixInvalidStandardUniversalIdentifiersCommand, SeedServerIdCommand, ], @@ -44,7 +42,6 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace AddMissingSystemFieldsToStandardObjectsCommand, BackfillMessageChannelMessageAssociationMessageFolderCommand, BackfillMissingStandardViewsCommand, - BackfillNavigationMenuItemTypeCommand, FixInvalidStandardUniversalIdentifiersCommand, SeedServerIdCommand, ], diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-19/1-19-backfill-navigation-menu-item-type.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-20/1-20-backfill-navigation-menu-item-type.command.ts similarity index 86% rename from packages/twenty-server/src/database/commands/upgrade-version-command/1-19/1-19-backfill-navigation-menu-item-type.command.ts rename to packages/twenty-server/src/database/commands/upgrade-version-command/1-20/1-20-backfill-navigation-menu-item-type.command.ts index afdca4efbf..e2f7778abb 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-19/1-19-backfill-navigation-menu-item-type.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-20/1-20-backfill-navigation-menu-item-type.command.ts @@ -11,7 +11,7 @@ import { DataSourceService } from 'src/engine/metadata-modules/data-source/data- import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; @Command({ - name: 'upgrade:1-19:backfill-navigation-menu-item-type', + name: 'upgrade:1-20:backfill-navigation-menu-item-type', description: 'Backfill navigation menu item type based on existing columns, then apply NOT NULL and CHECK constraints', }) @@ -52,15 +52,29 @@ export class BackfillNavigationMenuItemTypeCommand extends ActiveOrSuspendedWork try { await this.backfillType(queryRunner); await this.cleanConflictingColumns(queryRunner); - await makeNavigationMenuItemTypeNotNullQueries(queryRunner); + await queryRunner.commitTransaction(); + } catch (error) { + await queryRunner.rollbackTransaction(); + this.logger.error( + `Rolling back BackfillNavigationMenuItemTypeCommand data backfill: ${error.message}`, + ); + await queryRunner.release(); + + return; + } + + await queryRunner.startTransaction(); + + try { + await makeNavigationMenuItemTypeNotNullQueries(queryRunner); await queryRunner.commitTransaction(); this.logger.log('Successfully run BackfillNavigationMenuItemTypeCommand'); this.hasRunOnce = true; } catch (error) { await queryRunner.rollbackTransaction(); this.logger.error( - `Rolling back BackfillNavigationMenuItemTypeCommand: ${error.message}`, + `Rolling back BackfillNavigationMenuItemTypeCommand schema changes: ${error.message}`, ); } finally { await queryRunner.release(); @@ -71,7 +85,7 @@ export class BackfillNavigationMenuItemTypeCommand extends ActiveOrSuspendedWork queryRunner: ReturnType, ): Promise { await queryRunner.query( - `UPDATE "core"."navigationMenuItem" SET "type" = 'VIEW' WHERE "type" IS NULL AND "viewId" IS NOT NULL`, + `UPDATE "core"."navigationMenuItem" SET "type" = 'OBJECT' WHERE "type" = 'VIEW' AND "targetObjectMetadataId" IS NOT NULL AND "targetRecordId" IS NULL`, ); await queryRunner.query( @@ -82,6 +96,10 @@ export class BackfillNavigationMenuItemTypeCommand extends ActiveOrSuspendedWork `UPDATE "core"."navigationMenuItem" SET "type" = 'OBJECT' WHERE "type" IS NULL AND "targetObjectMetadataId" IS NOT NULL AND "targetRecordId" IS NULL`, ); + await queryRunner.query( + `UPDATE "core"."navigationMenuItem" SET "type" = 'VIEW' WHERE "type" IS NULL AND "viewId" IS NOT NULL`, + ); + await queryRunner.query( `UPDATE "core"."navigationMenuItem" SET "type" = 'LINK' WHERE "type" IS NULL AND "link" IS NOT NULL`, ); diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-20/1-20-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-20/1-20-upgrade-version-command.module.ts index 6a2daea471..be48499a10 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-20/1-20-upgrade-version-command.module.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-20/1-20-upgrade-version-command.module.ts @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { BackfillCommandMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-command-menu-items.command'; +import { BackfillNavigationMenuItemTypeCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-navigation-menu-item-type.command'; import { BackfillPageLayoutsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-page-layouts.command'; import { SeedCliApplicationRegistrationCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-seed-cli-application-registration.command'; import { ApplicationRegistrationModule } from 'src/engine/core-modules/application/application-registration/application-registration.module'; @@ -31,12 +32,14 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace ], providers: [ BackfillCommandMenuItemsCommand, + BackfillNavigationMenuItemTypeCommand, BackfillPageLayoutsCommand, SeedCliApplicationRegistrationCommand, MigrateRichTextToTextCommand, ], exports: [ BackfillCommandMenuItemsCommand, + BackfillNavigationMenuItemTypeCommand, BackfillPageLayoutsCommand, SeedCliApplicationRegistrationCommand, MigrateRichTextToTextCommand, diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts index d71d1ffb44..1cd2be1472 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts @@ -30,7 +30,7 @@ import { MigrateWorkspacePicturesCommand } from 'src/database/commands/upgrade-v import { AddMissingSystemFieldsToStandardObjectsCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-add-missing-system-fields-to-standard-objects.command'; import { BackfillMessageChannelMessageAssociationMessageFolderCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-backfill-message-channel-message-association-message-folder.command'; import { BackfillMissingStandardViewsCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-backfill-missing-standard-views.command'; -import { BackfillNavigationMenuItemTypeCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-backfill-navigation-menu-item-type.command'; +import { BackfillNavigationMenuItemTypeCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-navigation-menu-item-type.command'; import { BackfillSystemFieldsIsSystemCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-backfill-system-fields-is-system.command'; import { FixInvalidStandardUniversalIdentifiersCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-fix-invalid-standard-universal-identifiers.command'; import { SeedServerIdCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-seed-server-id.command'; @@ -84,11 +84,11 @@ export class UpgradeCommand extends UpgradeCommandRunner { protected readonly addMissingSystemFieldsToStandardObjectsCommand: AddMissingSystemFieldsToStandardObjectsCommand, protected readonly backfillMessageChannelMessageAssociationMessageFolderCommand: BackfillMessageChannelMessageAssociationMessageFolderCommand, protected readonly backfillMissingStandardViewsCommand: BackfillMissingStandardViewsCommand, - protected readonly backfillNavigationMenuItemTypeCommand: BackfillNavigationMenuItemTypeCommand, protected readonly fixRoleAndAgentUniversalIdentifiersCommand: FixInvalidStandardUniversalIdentifiersCommand, protected readonly seedServerIdCommand: SeedServerIdCommand, // 1.20 Commands + protected readonly backfillNavigationMenuItemTypeCommand: BackfillNavigationMenuItemTypeCommand, protected readonly backfillCommandMenuItemsCommand: BackfillCommandMenuItemsCommand, protected readonly backfillPageLayoutsCommand: BackfillPageLayoutsCommand, protected readonly seedCliApplicationRegistrationCommand: SeedCliApplicationRegistrationCommand, @@ -135,7 +135,6 @@ export class UpgradeCommand extends UpgradeCommandRunner { this.addMissingSystemFieldsToStandardObjectsCommand, this.backfillMessageChannelMessageAssociationMessageFolderCommand, this.backfillMissingStandardViewsCommand, - this.backfillNavigationMenuItemTypeCommand, this.seedServerIdCommand, ];