From b1cb8998f8edfd3cb5511594baba880cef279208 Mon Sep 17 00:00:00 2001 From: Thomas Trompette Date: Wed, 18 Sep 2024 18:26:55 +0200 Subject: [PATCH] Backfill workspace favorites (#7122) - command to backfill workspace favorites - create workspace favorites on workspace activation - create workspace favorites on demo seed --------- Co-authored-by: Charles Bochet --- .../data-seed-dev-workspace.command.ts | 29 +++-- .../commands/database-command.module.ts | 4 + ...31-backfill-workspace-favorites.command.ts | 121 ++++++++++++++++++ .../0-31/0-31-upgrade-version.command.ts | 40 ++++++ .../0-31/0-31-upgrade-version.module.ts | 12 ++ .../object-metadata.service.ts | 17 +-- .../demo-objects-prefill-data.ts | 22 +++- .../standard-objects-prefill-data.ts | 17 ++- .../standard-objects-prefill-data/view.ts | 9 +- .../views/workflows-all.view.ts | 2 +- .../workspace-manager.module.ts | 2 + .../workspace-manager.service.ts | 15 +++ 12 files changed, 250 insertions(+), 40 deletions(-) create mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-backfill-workspace-favorites.command.ts create mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-upgrade-version.command.ts create mode 100644 packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-upgrade-version.module.ts diff --git a/packages/twenty-server/src/database/commands/data-seed-dev-workspace.command.ts b/packages/twenty-server/src/database/commands/data-seed-dev-workspace.command.ts index 9fe02368c..dce5a07d9 100644 --- a/packages/twenty-server/src/database/commands/data-seed-dev-workspace.command.ts +++ b/packages/twenty-server/src/database/commands/data-seed-dev-workspace.command.ts @@ -34,7 +34,7 @@ import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decora import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service'; import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; -import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/field-metadata.service'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; @@ -63,6 +63,7 @@ export class DataSeedWorkspaceCommand extends CommandRunner { private readonly objectMetadataService: ObjectMetadataService, @InjectCacheStorage(CacheStorageNamespace.EngineWorkspace) private readonly workspaceSchemaCache: CacheStorageService, + private readonly featureFlagService: FeatureFlagService, ) { super(); } @@ -129,10 +130,17 @@ export class DataSeedWorkspaceCommand extends CommandRunner { return acc; }, {}); - const featureFlagRepository = - workspaceDataSource.getRepository('featureFlag'); + const isMessageThreadSubscriberEnabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IsMessageThreadSubscriberEnabled, + workspaceId, + ); - const featureFlags = await featureFlagRepository.find({}); + const isWorkflowEnabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IsWorkflowEnabled, + workspaceId, + ); await this.seedCompanyCustomFields( objectMetadataMap[STANDARD_OBJECT_IDS.company], @@ -161,13 +169,6 @@ export class DataSeedWorkspaceCommand extends CommandRunner { dataSourceMetadata.schema, ); - const isMessageThreadSubscriberEnabled = featureFlags.some( - (featureFlag) => - featureFlag.key === - FeatureFlagKey.IsMessageThreadSubscriberEnabled && - featureFlag.value === true, - ); - if (isMessageThreadSubscriberEnabled) { await seedMessageThreadSubscribers( entityManager, @@ -211,11 +212,13 @@ export class DataSeedWorkspaceCommand extends CommandRunner { entityManager, dataSourceMetadata.schema, objectMetadataMap, - featureFlags, + isWorkflowEnabled, ); await seedWorkspaceFavorites( - viewDefinitionsWithId.map((view) => view.id), + viewDefinitionsWithId + .filter((view) => view.key === 'INDEX') + .map((view) => view.id), entityManager, dataSourceMetadata.schema, ); diff --git a/packages/twenty-server/src/database/commands/database-command.module.ts b/packages/twenty-server/src/database/commands/database-command.module.ts index c8a16a93e..9d98d33ab 100644 --- a/packages/twenty-server/src/database/commands/database-command.module.ts +++ b/packages/twenty-server/src/database/commands/database-command.module.ts @@ -8,9 +8,11 @@ import { DataSeedDemoWorkspaceModule } from 'src/database/commands/data-seed-dem import { DataSeedWorkspaceCommand } from 'src/database/commands/data-seed-dev-workspace.command'; import { ConfirmationQuestion } from 'src/database/commands/questions/confirmation.question'; import { UpgradeTo0_30CommandModule } from 'src/database/commands/upgrade-version/0-30/0-30-upgrade-version.module'; +import { UpgradeTo0_31CommandModule } from 'src/database/commands/upgrade-version/0-31/0-31-upgrade-version.module'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity'; import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; +import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module'; import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; @@ -46,6 +48,8 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp WorkspaceCacheStorageModule, WorkspaceMetadataVersionModule, UpgradeTo0_30CommandModule, + UpgradeTo0_31CommandModule, + FeatureFlagModule, ], providers: [ DataSeedWorkspaceCommand, diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-backfill-workspace-favorites.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-backfill-workspace-favorites.command.ts new file mode 100644 index 000000000..c651e27de --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-backfill-workspace-favorites.command.ts @@ -0,0 +1,121 @@ +import { InjectRepository } from '@nestjs/typeorm'; + +import chalk from 'chalk'; +import { Command } from 'nest-commander'; +import { Repository } from 'typeorm'; + +import { + ActiveWorkspacesCommandOptions, + ActiveWorkspacesCommandRunner, +} from 'src/database/commands/active-workspaces.command'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity'; +import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity'; + +@Command({ + name: 'upgrade-0.31:backfill-workspace-favorites-migration', + description: 'Create a workspace favorite for all workspace views', +}) +export class BackfillWorkspaceFavoritesCommand extends ActiveWorkspacesCommandRunner { + constructor( + @InjectRepository(Workspace, 'core') + protected readonly workspaceRepository: Repository, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) { + super(workspaceRepository); + } + + async executeActiveWorkspacesCommand( + _passedParam: string[], + _options: ActiveWorkspacesCommandOptions, + workspaceIds: string[], + ): Promise { + this.logger.log('Running command to fix migration'); + + for (const workspaceId of workspaceIds) { + this.logger.log(`Running command for workspace ${workspaceId}`); + + try { + const workspaceIndexViews = await this.getIndexViews(workspaceId); + + await this.createViewWorkspaceFavorites( + workspaceId, + workspaceIndexViews.map((view) => view.id), + ); + + this.logger.log( + chalk.green(`Backfilled workspace favorites to ${workspaceId}.`), + ); + + await this.twentyORMGlobalManager.destroyDataSourceForWorkspace( + workspaceId, + ); + } catch (error) { + this.logger.log( + chalk.red( + `Running command on workspace ${workspaceId} failed with error: ${error}`, + ), + ); + continue; + } finally { + this.logger.log( + chalk.green(`Finished running command for workspace ${workspaceId}.`), + ); + } + + this.logger.log(chalk.green(`Command completed!`)); + } + } + + private async getIndexViews( + workspaceId: string, + ): Promise { + const viewRepository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, + 'view', + false, + ); + + return viewRepository.find({ + where: { + key: 'INDEX', + }, + }); + } + + private async createViewWorkspaceFavorites( + workspaceId: string, + viewIds: string[], + ) { + const favoriteRepository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, + 'favorite', + ); + + let nextFavoritePosition = await favoriteRepository.count(); + + for (const viewId of viewIds) { + const existingFavorites = await favoriteRepository.find({ + where: { + viewId, + }, + }); + + if (existingFavorites.length) { + continue; + } + + await favoriteRepository.insert( + favoriteRepository.create({ + viewId, + position: nextFavoritePosition, + }), + ); + + nextFavoritePosition++; + } + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-upgrade-version.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-upgrade-version.command.ts new file mode 100644 index 000000000..df03c303c --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-upgrade-version.command.ts @@ -0,0 +1,40 @@ +import { InjectRepository } from '@nestjs/typeorm'; + +import { Command } from 'nest-commander'; +import { Repository } from 'typeorm'; + +import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command'; +import { BackfillWorkspaceFavoritesCommand } from 'src/database/commands/upgrade-version/0-31/0-31-backfill-workspace-favorites.command'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; + +interface UpdateTo0_31CommandOptions { + workspaceId?: string; +} + +@Command({ + name: 'upgrade-0.31', + description: 'Upgrade to 0.31', +}) +export class UpgradeTo0_31Command extends ActiveWorkspacesCommandRunner { + constructor( + @InjectRepository(Workspace, 'core') + protected readonly workspaceRepository: Repository, + private readonly backfillWorkspaceFavoritesCommand: BackfillWorkspaceFavoritesCommand, + ) { + super(workspaceRepository); + } + + async executeActiveWorkspacesCommand( + passedParam: string[], + options: UpdateTo0_31CommandOptions, + workspaceIds: string[], + ): Promise { + await this.backfillWorkspaceFavoritesCommand.executeActiveWorkspacesCommand( + passedParam, + { + ...options, + }, + workspaceIds, + ); + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-upgrade-version.module.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-upgrade-version.module.ts new file mode 100644 index 000000000..93e3f965c --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-upgrade-version.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { BackfillWorkspaceFavoritesCommand } from 'src/database/commands/upgrade-version/0-31/0-31-backfill-workspace-favorites.command'; +import { UpgradeTo0_31Command } from 'src/database/commands/upgrade-version/0-31/0-31-upgrade-version.command'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; + +@Module({ + imports: [TypeOrmModule.forFeature([Workspace], 'core')], + providers: [UpgradeTo0_31Command, BackfillWorkspaceFavoritesCommand], +}) +export class UpgradeTo0_31CommandModule {} diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts index 6cb23d527..d6146870e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts @@ -10,7 +10,6 @@ import { FindManyOptions, FindOneOptions, In, Repository } from 'typeorm'; import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface'; import { TypeORMService } from 'src/database/typeorm/typeorm.service'; -import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; import { @@ -374,18 +373,10 @@ export class ObjectMetadataService extends TypeOrmQueryService { const objectMetadataMap = objectMetadata.reduce((acc, object) => { acc[object.standardId ?? ''] = { @@ -31,8 +33,20 @@ export const demoObjectsPrefillData = async ( await personPrefillDemoData(entityManager, schemaName); await opportunityPrefillDemoData(entityManager, schemaName); - await viewPrefillData(entityManager, schemaName, objectMetadataMap); + const viewDefinitionsWithId = await viewPrefillData( + entityManager, + schemaName, + objectMetadataMap, + isWorkflowEnabled, + ); + await seedWorkspaceFavorites( + viewDefinitionsWithId + .filter((view) => view.key === 'INDEX') + .map((view) => view.id), + entityManager, + schemaName, + ); await workspaceMemberPrefillData(entityManager, schemaName); }, ); diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/standard-objects-prefill-data.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/standard-objects-prefill-data.ts index 19ab7a055..7198fe4ef 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/standard-objects-prefill-data.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/standard-objects-prefill-data.ts @@ -1,5 +1,6 @@ import { DataSource, EntityManager } from 'typeorm'; +import { seedWorkspaceFavorites } from 'src/database/typeorm-seeds/workspace/favorites'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { companyPrefillData } from 'src/engine/workspace-manager/standard-objects-prefill-data/company'; import { personPrefillData } from 'src/engine/workspace-manager/standard-objects-prefill-data/person'; @@ -9,6 +10,7 @@ export const standardObjectsPrefillData = async ( workspaceDataSource: DataSource, schemaName: string, objectMetadata: ObjectMetadataEntity[], + isWorkflowEnabled: boolean, ) => { const objectMetadataMap = objectMetadata.reduce((acc, object) => { if (!object.standardId) { @@ -34,6 +36,19 @@ export const standardObjectsPrefillData = async ( workspaceDataSource.transaction(async (entityManager: EntityManager) => { await companyPrefillData(entityManager, schemaName); await personPrefillData(entityManager, schemaName); - await viewPrefillData(entityManager, schemaName, objectMetadataMap); + const viewDefinitionsWithId = await viewPrefillData( + entityManager, + schemaName, + objectMetadataMap, + isWorkflowEnabled, + ); + + await seedWorkspaceFavorites( + viewDefinitionsWithId + .filter((view) => view.key === 'INDEX') + .map((view) => view.id), + entityManager, + schemaName, + ); }); }; diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/view.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/view.ts index d58c8e9cb..a4f230eac 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/view.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/view.ts @@ -1,8 +1,6 @@ import { EntityManager } from 'typeorm'; import { v4 } from 'uuid'; -import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; -import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { companiesAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/companies-all.view'; import { notesAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/notes-all.view'; @@ -17,13 +15,8 @@ export const viewPrefillData = async ( entityManager: EntityManager, schemaName: string, objectMetadataMap: Record, - featureFlags?: FeatureFlagEntity[], + isWorkflowEnabled: boolean, ) => { - const isWorkflowEnabled = - featureFlags?.find( - (featureFlag) => featureFlag.key === FeatureFlagKey.IsWorkflowEnabled, - )?.value ?? false; - const viewDefinitions = [ await companiesAllView(objectMetadataMap), await peopleAllView(objectMetadataMap), diff --git a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflows-all.view.ts b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflows-all.view.ts index 280b512d3..8eba4bdc4 100644 --- a/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflows-all.view.ts +++ b/packages/twenty-server/src/engine/workspace-manager/standard-objects-prefill-data/views/workflows-all.view.ts @@ -9,7 +9,7 @@ export const workflowsAllView = async ( name: 'All Workflows', objectMetadataId: objectMetadataMap[STANDARD_OBJECT_IDS.workflow].id, type: 'table', - key: null, + key: 'INDEX', position: 0, icon: 'IconSettingsAutomation', kanbanFieldMetadataId: '', diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-manager.module.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-manager.module.ts index d3f362fa0..82b4cc54f 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-manager.module.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-manager.module.ts @@ -1,5 +1,6 @@ import { Module } from '@nestjs/common'; +import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module'; import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module'; @@ -17,6 +18,7 @@ import { WorkspaceManagerService } from './workspace-manager.service'; DataSourceModule, WorkspaceSyncMetadataModule, WorkspaceHealthModule, + FeatureFlagModule, ], exports: [WorkspaceManagerService], providers: [WorkspaceManagerService], diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-manager.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-manager.service.ts index 000b37d37..5532ef5ed 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-manager.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-manager.service.ts @@ -1,5 +1,7 @@ import { Injectable } from '@nestjs/common'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service'; @@ -17,6 +19,7 @@ export class WorkspaceManagerService { private readonly objectMetadataService: ObjectMetadataService, private readonly dataSourceService: DataSourceService, private readonly workspaceSyncMetadataService: WorkspaceSyncMetadataService, + private readonly featureFlagService: FeatureFlagService, ) {} /** @@ -117,10 +120,16 @@ export class WorkspaceManagerService { const createdObjectMetadata = await this.objectMetadataService.findManyWithinWorkspace(workspaceId); + const isWorkflowEnabled = await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IsWorkflowEnabled, + workspaceId, + ); + await standardObjectsPrefillData( workspaceDataSource, dataSourceMetadata.schema, createdObjectMetadata, + isWorkflowEnabled, ); } @@ -147,10 +156,16 @@ export class WorkspaceManagerService { const createdObjectMetadata = await this.objectMetadataService.findManyWithinWorkspace(workspaceId); + const isWorkflowEnabled = await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IsWorkflowEnabled, + workspaceId, + ); + await demoObjectsPrefillData( workspaceDataSource, dataSourceMetadata.schema, createdObjectMetadata, + isWorkflowEnabled, ); }