From ef92d2d321f92eb202a6ec829f559adaa37c748a Mon Sep 17 00:00:00 2001 From: Weiko Date: Wed, 11 Mar 2026 13:48:59 +0100 Subject: [PATCH] Backfill standard views command (#18540) --- ...backfill-missing-standard-views.command.ts | 186 ++++++++++++++++++ .../1-19-upgrade-version-command.module.ts | 3 + .../upgrade.command.ts | 3 + 3 files changed, 192 insertions(+) create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/1-19/1-19-backfill-missing-standard-views.command.ts diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-19/1-19-backfill-missing-standard-views.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-19/1-19-backfill-missing-standard-views.command.ts new file mode 100644 index 0000000000..b7f6da20e9 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-19/1-19-backfill-missing-standard-views.command.ts @@ -0,0 +1,186 @@ +import { InjectRepository } from '@nestjs/typeorm'; + +import { Command } from 'nest-commander'; +import { isDefined } from 'twenty-shared/utils'; +import { Repository } from 'typeorm'; + +import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; +import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner'; +import { ApplicationService } from 'src/engine/core-modules/application/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 { 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'; + +@Command({ + name: 'upgrade:1-19:backfill-missing-standard-views', + description: + 'Backfill missing standard views and their child entities (fields, groups, filters, field groups) for workspaces', +}) +export class BackfillMissingStandardViewsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { + constructor( + @InjectRepository(WorkspaceEntity) + protected readonly workspaceRepository: Repository, + protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager, + protected readonly dataSourceService: DataSourceService, + private readonly applicationService: ApplicationService, + private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService, + private readonly workspaceCacheService: WorkspaceCacheService, + ) { + super(workspaceRepository, twentyORMGlobalManager, dataSourceService); + } + + override async runOnWorkspace({ + workspaceId, + options, + }: RunOnWorkspaceArgs): Promise { + const isDryRun = options.dryRun ?? false; + + this.logger.log( + `${isDryRun ? '[DRY RUN] ' : ''}Starting backfill of missing standard views for workspace ${workspaceId}`, + ); + + const { twentyStandardFlatApplication } = + await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( + { workspaceId }, + ); + + const { allFlatEntityMaps: standardAllFlatEntityMaps } = + computeTwentyStandardApplicationAllFlatEntityMaps({ + shouldIncludeRecordPageLayouts: true, + now: new Date().toISOString(), + workspaceId, + twentyStandardApplicationId: twentyStandardFlatApplication.id, + }); + + const { flatViewMaps: existingFlatViewMaps } = + await this.workspaceCacheService.getOrRecompute(workspaceId, [ + 'flatViewMaps', + ]); + + const missingViewUniversalIdentifiers = new Set(); + + const viewsToCreate = Object.values( + standardAllFlatEntityMaps.flatViewMaps.byUniversalIdentifier, + ) + .filter(isDefined) + .filter((view) => { + const alreadyExists = isDefined( + existingFlatViewMaps.byUniversalIdentifier[view.universalIdentifier], + ); + + if (alreadyExists) { + return false; + } + + missingViewUniversalIdentifiers.add(view.universalIdentifier); + + return true; + }); + + if (viewsToCreate.length === 0) { + this.logger.log( + `No missing standard views for workspace ${workspaceId}, skipping`, + ); + + return; + } + + this.logger.log( + `Found ${viewsToCreate.length} missing standard view(s) for workspace ${workspaceId}`, + ); + + if (isDryRun) { + this.logger.log( + `[DRY RUN] Would create ${viewsToCreate.length} view(s) for workspace ${workspaceId}`, + ); + + return; + } + + const viewFieldsToCreate = Object.values( + standardAllFlatEntityMaps.flatViewFieldMaps.byUniversalIdentifier, + ) + .filter(isDefined) + .filter((viewField) => + missingViewUniversalIdentifiers.has(viewField.viewUniversalIdentifier), + ); + + const viewGroupsToCreate = Object.values( + standardAllFlatEntityMaps.flatViewGroupMaps.byUniversalIdentifier, + ) + .filter(isDefined) + .filter((viewGroup) => + missingViewUniversalIdentifiers.has(viewGroup.viewUniversalIdentifier), + ); + + const viewFiltersToCreate = Object.values( + standardAllFlatEntityMaps.flatViewFilterMaps.byUniversalIdentifier, + ) + .filter(isDefined) + .filter((viewFilter) => + missingViewUniversalIdentifiers.has(viewFilter.viewUniversalIdentifier), + ); + + const viewFieldGroupsToCreate = Object.values( + standardAllFlatEntityMaps.flatViewFieldGroupMaps.byUniversalIdentifier, + ) + .filter(isDefined) + .filter((viewFieldGroup) => + missingViewUniversalIdentifiers.has( + viewFieldGroup.viewUniversalIdentifier, + ), + ); + + const validateAndBuildResult = + await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( + { + allFlatEntityOperationByMetadataName: { + view: { + flatEntityToCreate: viewsToCreate, + flatEntityToDelete: [], + flatEntityToUpdate: [], + }, + viewField: { + flatEntityToCreate: viewFieldsToCreate, + flatEntityToDelete: [], + flatEntityToUpdate: [], + }, + viewGroup: { + flatEntityToCreate: viewGroupsToCreate, + flatEntityToDelete: [], + flatEntityToUpdate: [], + }, + viewFilter: { + flatEntityToCreate: viewFiltersToCreate, + flatEntityToDelete: [], + flatEntityToUpdate: [], + }, + viewFieldGroup: { + flatEntityToCreate: viewFieldGroupsToCreate, + flatEntityToDelete: [], + flatEntityToUpdate: [], + }, + }, + workspaceId, + applicationUniversalIdentifier: + twentyStandardFlatApplication.universalIdentifier, + }, + ); + + if (validateAndBuildResult.status === 'fail') { + this.logger.error( + `Failed to backfill missing standard views:\n${JSON.stringify(validateAndBuildResult, null, 2)}`, + ); + throw new Error( + `Failed to backfill missing standard views for workspace ${workspaceId}`, + ); + } + + this.logger.log( + `Successfully backfilled ${viewsToCreate.length} standard view(s) for workspace ${workspaceId}`, + ); + } +} 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 3d8e1d58c6..6cb1c812d1 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 @@ -3,6 +3,7 @@ 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 { BackfillPageLayoutsCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-backfill-page-layouts.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'; @@ -33,6 +34,7 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace BackfillSystemFieldsIsSystemCommand, AddMissingSystemFieldsToStandardObjectsCommand, BackfillMessageChannelMessageAssociationMessageFolderCommand, + BackfillMissingStandardViewsCommand, BackfillPageLayoutsCommand, FixInvalidStandardUniversalIdentifiersCommand, SeedServerIdCommand, @@ -41,6 +43,7 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace BackfillSystemFieldsIsSystemCommand, AddMissingSystemFieldsToStandardObjectsCommand, BackfillMessageChannelMessageAssociationMessageFolderCommand, + BackfillMissingStandardViewsCommand, BackfillPageLayoutsCommand, FixInvalidStandardUniversalIdentifiersCommand, SeedServerIdCommand, 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 e8ecd7bc9d..2a13c0c14e 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 @@ -29,6 +29,7 @@ import { MigrateWorkflowSendEmailAttachmentsCommand } from 'src/database/command import { MigrateWorkspacePicturesCommand } from 'src/database/commands/upgrade-version-command/1-18/1-18-migrate-workspace-pictures.command'; 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 { BackfillPageLayoutsCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-backfill-page-layouts.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'; @@ -78,6 +79,7 @@ export class UpgradeCommand extends UpgradeCommandRunner { protected readonly backfillSystemFieldsIsSystemCommand: BackfillSystemFieldsIsSystemCommand, protected readonly addMissingSystemFieldsToStandardObjectsCommand: AddMissingSystemFieldsToStandardObjectsCommand, protected readonly backfillMessageChannelMessageAssociationMessageFolderCommand: BackfillMessageChannelMessageAssociationMessageFolderCommand, + protected readonly backfillMissingStandardViewsCommand: BackfillMissingStandardViewsCommand, protected readonly backfillPageLayoutsCommand: BackfillPageLayoutsCommand, protected readonly fixRoleAndAgentUniversalIdentifiersCommand: FixInvalidStandardUniversalIdentifiersCommand, protected readonly seedServerIdCommand: SeedServerIdCommand, @@ -121,6 +123,7 @@ export class UpgradeCommand extends UpgradeCommandRunner { this.backfillSystemFieldsIsSystemCommand, this.addMissingSystemFieldsToStandardObjectsCommand, this.backfillMessageChannelMessageAssociationMessageFolderCommand, + this.backfillMissingStandardViewsCommand, this.backfillPageLayoutsCommand, this.fixRoleAndAgentUniversalIdentifiersCommand, this.seedServerIdCommand,