Skip to content

Commit 8964d26

Browse files
authored
Clean views without object metadata (#7153)
Add command for cleaning + clean on object deletion
1 parent 3025ac3 commit 8964d26

File tree

6 files changed

+195
-10
lines changed

6 files changed

+195
-10
lines changed

packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-backfill-workspace-favorites.command.ts

+32-3
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ import { InjectRepository } from '@nestjs/typeorm';
22

33
import chalk from 'chalk';
44
import { Command } from 'nest-commander';
5-
import { Repository } from 'typeorm';
5+
import { In, Repository } from 'typeorm';
66

77
import {
88
ActiveWorkspacesCommandOptions,
99
ActiveWorkspacesCommandRunner,
1010
} from 'src/database/commands/active-workspaces.command';
1111
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
12+
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
1213
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
1314
import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity';
1415
import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity';
@@ -21,6 +22,8 @@ export class BackfillWorkspaceFavoritesCommand extends ActiveWorkspacesCommandRu
2122
constructor(
2223
@InjectRepository(Workspace, 'core')
2324
protected readonly workspaceRepository: Repository<Workspace>,
25+
@InjectRepository(ObjectMetadataEntity, 'metadata')
26+
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
2427
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
2528
) {
2629
super(workspaceRepository);
@@ -37,11 +40,17 @@ export class BackfillWorkspaceFavoritesCommand extends ActiveWorkspacesCommandRu
3740
this.logger.log(`Running command for workspace ${workspaceId}`);
3841

3942
try {
40-
const workspaceIndexViews = await this.getIndexViews(workspaceId);
43+
const allWorkspaceIndexViews = await this.getIndexViews(workspaceId);
44+
45+
const activeWorkspaceIndexViews =
46+
await this.filterViewsWithoutObjectMetadata(
47+
workspaceId,
48+
allWorkspaceIndexViews,
49+
);
4150

4251
await this.createViewWorkspaceFavorites(
4352
workspaceId,
44-
workspaceIndexViews.map((view) => view.id),
53+
activeWorkspaceIndexViews.map((view) => view.id),
4554
);
4655

4756
this.logger.log(
@@ -85,6 +94,26 @@ export class BackfillWorkspaceFavoritesCommand extends ActiveWorkspacesCommandRu
8594
});
8695
}
8796

97+
private async filterViewsWithoutObjectMetadata(
98+
workspaceId: string,
99+
views: ViewWorkspaceEntity[],
100+
): Promise<ViewWorkspaceEntity[]> {
101+
const viewObjectMetadataIds = views.map((view) => view.objectMetadataId);
102+
103+
const objectMetadataEntities = await this.objectMetadataRepository.find({
104+
where: {
105+
workspaceId,
106+
id: In(viewObjectMetadataIds),
107+
},
108+
});
109+
110+
const objectMetadataIds = new Set(
111+
objectMetadataEntities.map((entity) => entity.id),
112+
);
113+
114+
return views.filter((view) => objectMetadataIds.has(view.objectMetadataId));
115+
}
116+
88117
private async createViewWorkspaceFavorites(
89118
workspaceId: string,
90119
viewIds: string[],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { InjectRepository } from '@nestjs/typeorm';
2+
3+
import chalk from 'chalk';
4+
import { Command } from 'nest-commander';
5+
import { In, Repository } from 'typeorm';
6+
7+
import {
8+
ActiveWorkspacesCommandOptions,
9+
ActiveWorkspacesCommandRunner,
10+
} from 'src/database/commands/active-workspaces.command';
11+
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
12+
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
13+
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
14+
import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity';
15+
16+
@Command({
17+
name: 'upgrade-0.31:clean-views-with-deleted-object-metadata',
18+
description: 'Clean views with deleted object metadata',
19+
})
20+
export class CleanViewsWithDeletedObjectMetadataCommand extends ActiveWorkspacesCommandRunner {
21+
constructor(
22+
@InjectRepository(Workspace, 'core')
23+
protected readonly workspaceRepository: Repository<Workspace>,
24+
@InjectRepository(ObjectMetadataEntity, 'metadata')
25+
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
26+
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
27+
) {
28+
super(workspaceRepository);
29+
}
30+
31+
async executeActiveWorkspacesCommand(
32+
_passedParam: string[],
33+
_options: ActiveWorkspacesCommandOptions,
34+
workspaceIds: string[],
35+
): Promise<void> {
36+
this.logger.log('Running command to fix migration');
37+
38+
for (const workspaceId of workspaceIds) {
39+
this.logger.log(`Running command for workspace ${workspaceId}`);
40+
41+
try {
42+
this.logger.log(chalk.green(`Cleaning views of ${workspaceId}.`));
43+
44+
await this.cleanViewsWithDeletedObjectMetadata(
45+
workspaceId,
46+
_options.dryRun ?? false,
47+
);
48+
49+
await this.twentyORMGlobalManager.destroyDataSourceForWorkspace(
50+
workspaceId,
51+
);
52+
} catch (error) {
53+
this.logger.log(
54+
chalk.red(
55+
`Running command on workspace ${workspaceId} failed with error: ${error}`,
56+
),
57+
);
58+
continue;
59+
} finally {
60+
this.logger.log(
61+
chalk.green(`Finished running command for workspace ${workspaceId}.`),
62+
);
63+
}
64+
65+
this.logger.log(chalk.green(`Command completed!`));
66+
}
67+
}
68+
69+
private async cleanViewsWithDeletedObjectMetadata(
70+
workspaceId: string,
71+
dryRun: boolean,
72+
): Promise<void> {
73+
const viewRepository =
74+
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ViewWorkspaceEntity>(
75+
workspaceId,
76+
'view',
77+
false,
78+
);
79+
80+
const allViews = await viewRepository.find();
81+
82+
const viewObjectMetadataIds = allViews.map((view) => view.objectMetadataId);
83+
84+
const objectMetadataEntities = await this.objectMetadataRepository.find({
85+
where: {
86+
id: In(viewObjectMetadataIds),
87+
},
88+
});
89+
90+
const validObjectMetadataIds = new Set(
91+
objectMetadataEntities.map((entity) => entity.id),
92+
);
93+
94+
const viewIdsToDelete = allViews
95+
.filter((view) => !validObjectMetadataIds.has(view.objectMetadataId))
96+
.map((view) => view.id);
97+
98+
if (dryRun) {
99+
this.logger.log(
100+
chalk.green(
101+
`Found ${viewIdsToDelete.length} views to clean in workspace ${workspaceId}.`,
102+
),
103+
);
104+
}
105+
106+
if (viewIdsToDelete.length > 0 && !dryRun) {
107+
await viewRepository.delete(viewIdsToDelete);
108+
this.logger.log(chalk.green(`Cleaning ${viewIdsToDelete.length} views.`));
109+
}
110+
111+
if (viewIdsToDelete.length === 0) {
112+
this.logger.log(chalk.green(`No views to clean.`));
113+
}
114+
}
115+
}

packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-upgrade-version.command.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import { Repository } from 'typeorm';
55

66
import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command';
77
import { BackfillWorkspaceFavoritesCommand } from 'src/database/commands/upgrade-version/0-31/0-31-backfill-workspace-favorites.command';
8+
import { CleanViewsWithDeletedObjectMetadataCommand } from 'src/database/commands/upgrade-version/0-31/0-31-clean-views-with-deleted-object-metadata.command';
89
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
10+
import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command';
911

1012
interface UpdateTo0_31CommandOptions {
1113
workspaceId?: string;
@@ -19,7 +21,9 @@ export class UpgradeTo0_31Command extends ActiveWorkspacesCommandRunner {
1921
constructor(
2022
@InjectRepository(Workspace, 'core')
2123
protected readonly workspaceRepository: Repository<Workspace>,
24+
private readonly syncWorkspaceMetadataCommand: SyncWorkspaceMetadataCommand,
2225
private readonly backfillWorkspaceFavoritesCommand: BackfillWorkspaceFavoritesCommand,
26+
private readonly cleanViewsWithDeletedObjectMetadataCommand: CleanViewsWithDeletedObjectMetadataCommand,
2327
) {
2428
super(workspaceRepository);
2529
}
@@ -29,12 +33,23 @@ export class UpgradeTo0_31Command extends ActiveWorkspacesCommandRunner {
2933
options: UpdateTo0_31CommandOptions,
3034
workspaceIds: string[],
3135
): Promise<void> {
32-
await this.backfillWorkspaceFavoritesCommand.executeActiveWorkspacesCommand(
36+
await this.syncWorkspaceMetadataCommand.executeActiveWorkspacesCommand(
3337
passedParam,
3438
{
3539
...options,
40+
force: true,
3641
},
3742
workspaceIds,
3843
);
44+
await this.cleanViewsWithDeletedObjectMetadataCommand.executeActiveWorkspacesCommand(
45+
passedParam,
46+
options,
47+
workspaceIds,
48+
);
49+
await this.backfillWorkspaceFavoritesCommand.executeActiveWorkspacesCommand(
50+
passedParam,
51+
options,
52+
workspaceIds,
53+
);
3954
}
4055
}

packages/twenty-server/src/database/commands/upgrade-version/0-31/0-31-upgrade-version.module.ts

+13-2
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,22 @@ import { Module } from '@nestjs/common';
22
import { TypeOrmModule } from '@nestjs/typeorm';
33

44
import { BackfillWorkspaceFavoritesCommand } from 'src/database/commands/upgrade-version/0-31/0-31-backfill-workspace-favorites.command';
5+
import { CleanViewsWithDeletedObjectMetadataCommand } from 'src/database/commands/upgrade-version/0-31/0-31-clean-views-with-deleted-object-metadata.command';
56
import { UpgradeTo0_31Command } from 'src/database/commands/upgrade-version/0-31/0-31-upgrade-version.command';
67
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
8+
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
9+
import { WorkspaceSyncMetadataCommandsModule } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/workspace-sync-metadata-commands.module';
710

811
@Module({
9-
imports: [TypeOrmModule.forFeature([Workspace], 'core')],
10-
providers: [UpgradeTo0_31Command, BackfillWorkspaceFavoritesCommand],
12+
imports: [
13+
TypeOrmModule.forFeature([Workspace], 'core'),
14+
TypeOrmModule.forFeature([ObjectMetadataEntity], 'metadata'),
15+
WorkspaceSyncMetadataCommandsModule,
16+
],
17+
providers: [
18+
UpgradeTo0_31Command,
19+
BackfillWorkspaceFavoritesCommand,
20+
CleanViewsWithDeletedObjectMetadataCommand,
21+
],
1122
})
1223
export class UpgradeTo0_31CommandModule {}

packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts

-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
1010

1111
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
1212
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
13-
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
1413
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
1514
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
1615
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
@@ -44,7 +43,6 @@ import { UpdateObjectPayload } from './dtos/update-object.input';
4443
WorkspaceMigrationModule,
4544
WorkspaceMigrationRunnerModule,
4645
WorkspaceMetadataVersionModule,
47-
FeatureFlagModule,
4846
RemoteTableRelationsModule,
4947
],
5048
services: [ObjectMetadataService],

packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts

+19-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { FindManyOptions, FindOneOptions, In, Repository } from 'typeorm';
1010
import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
1111

1212
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
13-
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
1413
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
1514
import {
1615
FieldMetadataEntity,
@@ -60,6 +59,7 @@ import {
6059
createRelationDeterministicUuid,
6160
} from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util';
6261
import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity';
62+
import { ViewWorkspaceEntity } from 'src/modules/view/standard-objects/view.workspace-entity';
6363

6464
import { ObjectMetadataEntity } from './object-metadata.entity';
6565

@@ -85,7 +85,6 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
8585
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
8686
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
8787
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
88-
private readonly featureFlagService: FeatureFlagService,
8988
) {
9089
super(objectMetadataRepository);
9190
}
@@ -143,6 +142,24 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
143142
await this.deleteAllRelationsAndDropTable(objectMetadata, workspaceId);
144143
}
145144

145+
// DELETE VIEWS
146+
const viewRepository =
147+
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ViewWorkspaceEntity>(
148+
workspaceId,
149+
'view',
150+
);
151+
152+
const views = await viewRepository.find({
153+
where: {
154+
objectMetadataId: objectMetadata.id,
155+
},
156+
});
157+
158+
if (views.length > 0) {
159+
await viewRepository.delete(views.map((view) => view.id));
160+
}
161+
162+
// DELETE OBJECT
146163
await this.objectMetadataRepository.delete(objectMetadata.id);
147164

148165
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(

0 commit comments

Comments
 (0)