Delete unused objects (#7823)

Fixes #7113

---------

Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
Félix Malfait
2024-10-30 13:35:43 +01:00
committed by GitHub
parent 57d9b8e8b4
commit 50c912d57f
45 changed files with 422 additions and 1094 deletions

View File

@ -386,7 +386,6 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
) {
const relatedObjectTypes = [
'timelineActivity',
'activityTarget',
'favorite',
'attachment',
'noteTarget',

View File

@ -1,28 +1,27 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { In, Repository } from 'typeorm';
import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util';
import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service';
import {
FieldMetadataEntity,
FieldMetadataType,
} from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { createForeignKeyDeterministicUuid } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { createRelationForeignKeyFieldMetadataName } from 'src/engine/metadata-modules/relation-metadata/utils/create-relation-foreign-key-field-metadata-name.util';
import { buildMigrationsToCreateRemoteTableRelations } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/utils/build-migrations-to-create-remote-table-relations.util';
import { buildMigrationsToRemoveRemoteTableRelations } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/utils/build-migrations-to-remove-remote-table-relations.util';
import { mapUdtNameToFieldType } from 'src/engine/metadata-modules/remote-server/remote-table/utils/udt-name-mapper.util';
import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util';
import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service';
import {
ACTIVITY_TARGET_STANDARD_FIELD_IDS,
ATTACHMENT_STANDARD_FIELD_IDS,
FAVORITE_STANDARD_FIELD_IDS,
TIMELINE_ACTIVITY_STANDARD_FIELD_IDS,
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
import { buildMigrationsToCreateRemoteTableRelations } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/utils/build-migrations-to-create-remote-table-relations.util';
import { buildMigrationsToRemoveRemoteTableRelations } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/utils/build-migrations-to-remove-remote-table-relations.util';
import { mapUdtNameToFieldType } from 'src/engine/metadata-modules/remote-server/remote-table/utils/udt-name-mapper.util';
import { createRelationForeignKeyFieldMetadataName } from 'src/engine/metadata-modules/relation-metadata/utils/create-relation-foreign-key-field-metadata-name.util';
import { createForeignKeyDeterministicUuid } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util';
@Injectable()
export class RemoteTableRelationsService {
@ -54,14 +53,6 @@ export class RemoteTableRelationsService {
objectPrimaryKeyFieldSettings,
);
const activityTargetObjectMetadata =
await this.createActivityTargetRelation(
workspaceId,
remoteObjectMetadata,
objectPrimaryKeyFieldType,
objectPrimaryKeyFieldSettings,
);
const attachmentObjectMetadata = await this.createAttachmentRelation(
workspaceId,
remoteObjectMetadata,
@ -87,7 +78,6 @@ export class RemoteTableRelationsService {
remoteObjectMetadata.nameSingular,
[
favoriteObjectMetadata,
activityTargetObjectMetadata,
attachmentObjectMetadata,
timelineActivityObjectMetadata,
],
@ -107,12 +97,6 @@ export class RemoteTableRelationsService {
workspaceId: workspaceId,
});
const activityTargetObjectMetadata =
await this.objectMetadataRepository.findOneByOrFail({
nameSingular: 'activityTarget',
workspaceId: workspaceId,
});
const attachmentObjectMetadata =
await this.objectMetadataRepository.findOneByOrFail({
nameSingular: 'attachment',
@ -136,7 +120,6 @@ export class RemoteTableRelationsService {
name: targetColumnName,
objectMetadataId: In([
favoriteObjectMetadata.id,
activityTargetObjectMetadata.id,
attachmentObjectMetadata.id,
timelineActivityObjectMetadata.id,
]),
@ -158,53 +141,12 @@ export class RemoteTableRelationsService {
workspaceId,
buildMigrationsToRemoveRemoteTableRelations(targetColumnName, [
favoriteObjectMetadata,
activityTargetObjectMetadata,
attachmentObjectMetadata,
timelineActivityObjectMetadata,
]),
);
}
private async createActivityTargetRelation(
workspaceId: string,
createdObjectMetadata: ObjectMetadataEntity,
objectPrimaryKeyType: FieldMetadataType,
objectPrimaryKeyFieldSettings:
| FieldMetadataSettings<FieldMetadataType | 'default'>
| undefined,
) {
const activityTargetObjectMetadata =
await this.objectMetadataRepository.findOneByOrFail({
nameSingular: 'activityTarget',
workspaceId: workspaceId,
});
await this.fieldMetadataRepository.save(
// Foreign key
{
standardId: createForeignKeyDeterministicUuid({
objectId: createdObjectMetadata.id,
standardId: ACTIVITY_TARGET_STANDARD_FIELD_IDS.custom,
}),
objectMetadataId: activityTargetObjectMetadata.id,
workspaceId: workspaceId,
isCustom: false,
isActive: true,
type: objectPrimaryKeyType,
name: `${createdObjectMetadata.nameSingular}Id`,
label: `${createdObjectMetadata.labelSingular} ID (foreign key)`,
description: `ActivityTarget ${createdObjectMetadata.labelSingular} id foreign key`,
icon: undefined,
isNullable: true,
isSystem: true,
defaultValue: undefined,
settings: { ...objectPrimaryKeyFieldSettings, isForeignKey: true },
},
);
return activityTargetObjectMetadata;
}
private async createAttachmentRelation(
workspaceId: string,
createdObjectMetadata: ObjectMetadataEntity,

View File

@ -1,9 +1,9 @@
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import {
WorkspaceMigrationTableAction,
WorkspaceMigrationTableActionType,
WorkspaceMigrationColumnActionType,
WorkspaceMigrationColumnDrop,
WorkspaceMigrationTableAction,
WorkspaceMigrationTableActionType,
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
import { computeTableName } from 'src/engine/utils/compute-table-name.util';

View File

@ -22,7 +22,6 @@ import {
FieldTypeAndNameMetadata,
getTsVectorColumnExpressionFromFields,
} from 'src/engine/workspace-manager/workspace-sync-metadata/utils/get-ts-vector-column-expression.util';
import { ActivityTargetWorkspaceEntity } from 'src/modules/activity/standard-objects/activity-target.workspace-entity';
import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity';
import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity';
import { NoteTargetWorkspaceEntity } from 'src/modules/note/standard-objects/note-target.workspace-entity';
@ -71,19 +70,6 @@ export class CustomWorkspaceEntity extends BaseWorkspaceEntity {
})
createdBy: ActorMetadata;
@WorkspaceRelation({
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.activityTargets,
label: 'Activities',
type: RelationMetadataType.ONE_TO_MANY,
description: (objectMetadata) =>
`Activities tied to the ${objectMetadata.labelSingular}`,
icon: 'IconCheckbox',
inverseSideTarget: () => ActivityTargetWorkspaceEntity,
onDelete: RelationOnDeleteAction.CASCADE,
})
@WorkspaceIsNullable()
activityTargets: ActivityTargetWorkspaceEntity[];
@WorkspaceRelation({
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.noteTargets,
label: 'Notes',

View File

@ -67,7 +67,7 @@ export const notesAllView = (
TODO: Add later, since we don't have real-time it probably doesn't work well?
{
fieldMetadataId:
objectMetadataMap[STANDARD_OBJECT_IDS.activity].fields[
objectMetadataMap[STANDARD_OBJECT_IDS.note].fields[
BASE_OBJECT_STANDARD_FIELD_IDS.updatedAt
],
position: 0,

View File

@ -4,15 +4,16 @@ import { WorkspaceMigrationBuilderAction } from 'src/engine/workspace-manager/wo
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util';
import {
WorkspaceMigrationColumnActionType,
WorkspaceMigrationEntity,
WorkspaceMigrationTableAction,
WorkspaceMigrationTableActionType,
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
import { WorkspaceMigrationFactory } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.factory';
import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util';
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
export interface ObjectMetadataUpdate {
current: ObjectMetadataEntity;
@ -27,9 +28,7 @@ export class WorkspaceMigrationObjectFactory {
async create(
objectMetadataCollection: ObjectMetadataEntity[],
action:
| WorkspaceMigrationBuilderAction.CREATE
| WorkspaceMigrationBuilderAction.DELETE,
action: WorkspaceMigrationBuilderAction.CREATE,
): Promise<Partial<WorkspaceMigrationEntity>[]>;
async create(
@ -37,11 +36,24 @@ export class WorkspaceMigrationObjectFactory {
action: WorkspaceMigrationBuilderAction.UPDATE,
): Promise<Partial<WorkspaceMigrationEntity>[]>;
async create(
objectMetadataCollection: ObjectMetadataEntity[],
action: WorkspaceMigrationBuilderAction.DELETE,
relationMetadataByFromObjectMetadataId: Record<
string,
RelationMetadataEntity[]
>,
): Promise<Partial<WorkspaceMigrationEntity>[]>;
async create(
objectMetadataCollectionOrObjectMetadataUpdateCollection:
| ObjectMetadataEntity[]
| ObjectMetadataUpdate[],
action: WorkspaceMigrationBuilderAction,
relationMetadataByFromObjectMetadataId?: Record<
string,
RelationMetadataEntity[]
>,
): Promise<Partial<WorkspaceMigrationEntity>[]> {
switch (action) {
case WorkspaceMigrationBuilderAction.CREATE:
@ -55,6 +67,10 @@ export class WorkspaceMigrationObjectFactory {
case WorkspaceMigrationBuilderAction.DELETE:
return this.deleteObjectMigration(
objectMetadataCollectionOrObjectMetadataUpdateCollection as ObjectMetadataEntity[],
relationMetadataByFromObjectMetadataId as Record<
string,
RelationMetadataEntity[]
>,
);
default:
return [];
@ -136,22 +152,43 @@ export class WorkspaceMigrationObjectFactory {
private async deleteObjectMigration(
objectMetadataCollection: ObjectMetadataEntity[],
relationMetadataByFromObjectMetadataId: Record<
string,
RelationMetadataEntity[]
>,
): Promise<Partial<WorkspaceMigrationEntity>[]> {
const workspaceMigrations: Partial<WorkspaceMigrationEntity>[] = [];
for (const objectMetadata of objectMetadataCollection) {
const migrations: WorkspaceMigrationTableAction[] = [
{
name: computeObjectTargetTable(objectMetadata),
action: WorkspaceMigrationTableActionType.DROP,
},
];
const relationMetadataCollection =
relationMetadataByFromObjectMetadataId[objectMetadata.id];
workspaceMigrations.push({
workspaceId: objectMetadata.workspaceId,
name: generateMigrationName(`delete-${objectMetadata.nameSingular}`),
isCustom: false,
migrations,
migrations: [
...(relationMetadataCollection ?? []).map(
(relationMetadata) =>
({
name: computeObjectTargetTable(
relationMetadata.toObjectMetadata,
),
action: WorkspaceMigrationTableActionType.ALTER,
columns: [
{
action: WorkspaceMigrationColumnActionType.DROP_FOREIGN_KEY,
columnName: `${relationMetadata.toFieldMetadata.name}Id`,
},
],
}) satisfies WorkspaceMigrationTableAction,
),
{
name: computeObjectTargetTable(objectMetadata),
action: WorkspaceMigrationTableActionType.DROP,
columns: [],
} satisfies WorkspaceMigrationTableAction,
],
});
}

View File

@ -5,6 +5,7 @@
* For readability keys can be edited but the values should not be changed.
*/
// TODO: check if this can be deleted
export const ACTIVITY_TARGET_STANDARD_FIELD_IDS = {
activity: '20202020-ca58-478c-a4f5-ae825671c30e',
person: '20202020-4afd-4ae7-99c2-de57d795a93f',
@ -13,6 +14,7 @@ export const ACTIVITY_TARGET_STANDARD_FIELD_IDS = {
custom: '20202020-7f21-442f-94be-32462281b1ca',
};
// TODO: check if this can be deleted
export const ACTIVITY_STANDARD_FIELD_IDS = {
title: '20202020-24a1-4d94-a071-617f3eeed7b0',
body: '20202020-209b-440a-b2a8-043fa36a7d37',
@ -109,6 +111,7 @@ export const CALENDAR_EVENT_STANDARD_FIELD_IDS = {
calendarEventParticipants: '20202020-e07e-4ccb-88f5-6f3d00458eec',
};
// TODO: check if this can be deleted
export const COMMENT_STANDARD_FIELD_IDS = {
body: '20202020-d5eb-49d2-b3e0-1ed04145ebb7',
author: '20202020-2ab1-427e-a981-cf089de3a9bd',
@ -129,6 +132,7 @@ export const COMPANY_STANDARD_FIELD_IDS = {
createdBy: '20202020-fabc-451d-ab7d-412170916baa',
people: '20202020-3213-4ddf-9494-6422bcff8d7c',
accountOwner: '20202020-95b8-4e10-9881-edb5d4765f9d',
// TODO: check if activityTargets field can be deleted
activityTargets: '20202020-c2a5-4c9b-9d9a-582bcd57fbc8',
taskTargets: '20202020-cb17-4a61-8f8f-3be6730480de',
noteTargets: '20202020-bae0-4556-a74a-a9c686f77a88',
@ -304,6 +308,7 @@ export const OPPORTUNITY_STANDARD_FIELD_IDS = {
pointOfContact: '20202020-8dfb-42fc-92b6-01afb759ed16',
company: '20202020-cbac-457e-b565-adece5fc815f',
favorites: '20202020-a1c2-4500-aaae-83ba8a0e827a',
// TODO: check if activityTargets field can be deleted
activityTargets: '20202020-220a-42d6-8261-b2102d6eab35',
taskTargets: '20202020-59c0-4179-a208-4a255f04a5be',
noteTargets: '20202020-dd3f-42d5-a382-db58aabf43d3',
@ -327,6 +332,7 @@ export const PERSON_STANDARD_FIELD_IDS = {
createdBy: '20202020-f6ab-4d98-af24-a3d5b664148a',
company: '20202020-e2f3-448e-b34c-2d625f0025fd',
pointOfContactForOpportunities: '20202020-911b-4a7d-b67b-918aa9a5b33a',
// TODO: check if activityTargets field can be deleted
activityTargets: '20202020-dee7-4b7f-b50a-1f50bd3be452',
taskTargets: '20202020-584b-4d3e-88b6-53ab1fa03c3a',
noteTargets: '20202020-c8fc-4258-8250-15905d3fcfec',
@ -498,6 +504,7 @@ export const CUSTOM_OBJECT_STANDARD_FIELD_IDS = {
name: '20202020-ba07-4ffd-ba63-009491f5749c',
position: '20202020-c2bd-4e16-bb9a-c8b0411bf49d',
createdBy: '20202020-be0e-4971-865b-32ca87cbb315',
// TODO: check if activityTargets field can be deleted
activityTargets: '20202020-7f42-40ae-b96c-c8a61acc83bf',
noteTargets: '20202020-01fd-4f37-99dc-9427a444018a',
taskTargets: '20202020-0860-4566-b865-bff3c626c303',

View File

@ -6,6 +6,7 @@
*/
export const STANDARD_OBJECT_IDS = {
// TODO: check if activity, activityTarget and comment can be deleted
activityTarget: '20202020-2945-440e-8d1a-f84672d33d5e',
activity: '20202020-39aa-4a89-843b-eb5f2a8b677f',
apiKey: '20202020-4c00-401d-8cda-ec6a4c41cd7d',

View File

@ -6,6 +6,7 @@ import {
FindOptionsWhere,
In,
ObjectLiteral,
Repository,
} from 'typeorm';
import { DeepPartial } from 'typeorm/common/DeepPartial';
import { v4 as uuidV4 } from 'uuid';
@ -127,6 +128,11 @@ export class WorkspaceMetadataUpdaterService {
updatedFieldMetadataCollection: FieldMetadataUpdate[];
}> {
const fieldMetadataRepository = manager.getRepository(FieldMetadataEntity);
const indexFieldMetadataRepository = manager.getRepository(
IndexFieldMetadataEntity,
);
const indexMetadataRepository = manager.getRepository(IndexMetadataEntity);
/**
* Update field metadata
*/
@ -157,6 +163,12 @@ export class WorkspaceMetadataUpdaterService {
);
if (fieldMetadataDeleteCollectionWithoutRelationType.length > 0) {
await this.deleteIndexFieldMetadata(
fieldMetadataDeleteCollectionWithoutRelationType,
indexFieldMetadataRepository,
indexMetadataRepository,
);
await fieldMetadataRepository.delete(
fieldMetadataDeleteCollectionWithoutRelationType.map(
(field) => field.id,
@ -171,6 +183,33 @@ export class WorkspaceMetadataUpdaterService {
};
}
async deleteIndexFieldMetadata(
fieldMetadataDeleteCollectionWithoutRelationType: Partial<FieldMetadataEntity>[],
indexFieldMetadataRepository: Repository<IndexFieldMetadataEntity>,
indexMetadataRepository: Repository<IndexMetadataEntity>,
) {
const indexFieldMetadatas = await indexFieldMetadataRepository.find({
where: {
fieldMetadataId: In(
fieldMetadataDeleteCollectionWithoutRelationType.map(
(field) => field.id,
),
),
},
relations: {
indexMetadata: true,
},
});
const uniqueIndexMetadataIds = [
...new Set(indexFieldMetadatas.map((field) => field.indexMetadataId)),
];
if (uniqueIndexMetadataIds.length > 0) {
await indexMetadataRepository.delete(uniqueIndexMetadataIds);
}
}
async updateRelationMetadata(
manager: EntityManager,
storage: WorkspaceSyncStorage,

View File

@ -8,6 +8,7 @@ import { ComparatorAction } from 'src/engine/workspace-manager/workspace-sync-me
import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
import { WorkspaceMigrationEntity } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
import { WorkspaceMigrationObjectFactory } from 'src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-object.factory';
import { WorkspaceObjectComparator } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-object.comparator';
@ -37,6 +38,10 @@ export class WorkspaceSyncObjectMetadataService {
const objectMetadataRepository =
manager.getRepository(ObjectMetadataEntity);
const relationMetadataRepository = manager.getRepository(
RelationMetadataEntity,
);
// Retrieve object metadata collection from DB
const originalObjectMetadataCollection =
await objectMetadataRepository.find({
@ -47,6 +52,33 @@ export class WorkspaceSyncObjectMetadataService {
relations: ['dataSource', 'fields'],
});
// Retrieve relation metadata collection from DB
const originalRelationMetadataCollection =
await relationMetadataRepository.find({
where: {
workspaceId: context.workspaceId,
},
relations: ['toObjectMetadata', 'toFieldMetadata'],
});
const relationMetadataByFromObjectMetadataId: Record<
string,
RelationMetadataEntity[]
> = originalRelationMetadataCollection.reduce(
(acc, relationMetadata) => {
const fromObjectMetadataId = relationMetadata.fromObjectMetadataId;
if (!acc[fromObjectMetadataId]) {
acc[fromObjectMetadataId] = [];
}
acc[fromObjectMetadataId].push(relationMetadata);
return acc;
},
{} as Record<string, RelationMetadataEntity[]>,
);
// Create standard object metadata collection
const standardObjectMetadataCollection = this.standardObjectFactory.create(
standardObjectMetadataDefinitions,
@ -129,6 +161,7 @@ export class WorkspaceSyncObjectMetadataService {
await this.workspaceMigrationObjectFactory.create(
storage.objectMetadataDeleteCollection,
WorkspaceMigrationBuilderAction.DELETE,
relationMetadataByFromObjectMetadataId,
);
this.logger.log('Saving migrations');

View File

@ -1,6 +1,3 @@
import { ActivityTargetWorkspaceEntity } from 'src/modules/activity/standard-objects/activity-target.workspace-entity';
import { ActivityWorkspaceEntity } from 'src/modules/activity/standard-objects/activity.workspace-entity';
import { CommentWorkspaceEntity } from 'src/modules/activity/standard-objects/comment.workspace-entity';
import { ApiKeyWorkspaceEntity } from 'src/modules/api-key/standard-objects/api-key.workspace-entity';
import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity';
import { BlocklistWorkspaceEntity } from 'src/modules/blocklist/standard-objects/blocklist.workspace-entity';
@ -41,8 +38,6 @@ import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/sta
// TODO: Maybe we should automate this with the DiscoverService of Nest.JS
export const standardObjectMetadataDefinitions = [
ActivityTargetWorkspaceEntity,
ActivityWorkspaceEntity,
ApiKeyWorkspaceEntity,
AuditLogWorkspaceEntity,
AttachmentWorkspaceEntity,
@ -52,7 +47,6 @@ export const standardObjectMetadataDefinitions = [
CalendarChannelWorkspaceEntity,
CalendarChannelEventAssociationWorkspaceEntity,
CalendarEventParticipantWorkspaceEntity,
CommentWorkspaceEntity,
CompanyWorkspaceEntity,
ConnectedAccountWorkspaceEntity,
FavoriteWorkspaceEntity,