Add integration tests for /metadata + fix relation deletion (#8706)
In this PR 1. Add integration tests for /metadata (master issue: https://github.com/twentyhq/twenty/issues/8719) 2. Fix relation deletion: index on "from" object was not deleted, impeding creation of a new relation between the same two objects A and B after relation between A and B was deleted
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isDefined } from 'class-validator';
|
||||
import isEmpty from 'lodash.isempty';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
@ -19,6 +19,7 @@ import {
|
||||
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
||||
import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service';
|
||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||
import { isDefined } from 'src/utils/is-defined';
|
||||
|
||||
@Injectable()
|
||||
export class IndexMetadataService {
|
||||
@ -43,6 +44,10 @@ export class IndexMetadataService {
|
||||
(fieldMetadata) => fieldMetadata.name as string,
|
||||
);
|
||||
|
||||
if (isEmpty(columnNames)) {
|
||||
throw new Error('Column names must not be empty');
|
||||
}
|
||||
|
||||
const indexName = `IDX_${generateDeterministicIndexName([tableName, ...columnNames])}`;
|
||||
|
||||
let result: IndexMetadataEntity;
|
||||
@ -98,6 +103,44 @@ export class IndexMetadataService {
|
||||
);
|
||||
}
|
||||
|
||||
async deleteIndexMetadata(
|
||||
workspaceId: string,
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
fieldMetadataToIndex: Partial<FieldMetadataEntity>[],
|
||||
) {
|
||||
const tableName = computeObjectTargetTable(objectMetadata);
|
||||
|
||||
const columnNames: string[] = fieldMetadataToIndex.map(
|
||||
(fieldMetadata) => fieldMetadata.name as string,
|
||||
);
|
||||
|
||||
if (isEmpty(columnNames)) {
|
||||
throw new Error('Column names must not be empty');
|
||||
}
|
||||
|
||||
const indexName = `IDX_${generateDeterministicIndexName([tableName, ...columnNames])}`;
|
||||
|
||||
const indexMetadata = await this.indexMetadataRepository.findOne({
|
||||
where: {
|
||||
name: indexName,
|
||||
objectMetadataId: objectMetadata.id,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!indexMetadata) {
|
||||
throw new Error(`Index metadata with name ${indexName} not found`);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.indexMetadataRepository.delete(indexMetadata.id);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to delete index metadata with name ${indexName} (error: ${error.message})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async createIndexCreationMigration(
|
||||
workspaceId: string,
|
||||
objectMetadata: ObjectMetadataEntity,
|
||||
|
||||
@ -36,6 +36,7 @@ import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target
|
||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
|
||||
import { BASE_OBJECT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
||||
import { isDefined } from 'src/utils/is-defined';
|
||||
|
||||
import {
|
||||
RelationMetadataEntity,
|
||||
@ -137,22 +138,20 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
||||
);
|
||||
}
|
||||
|
||||
const deletedFieldMetadata = toObjectMetadata.fields.find(
|
||||
const deletedAtFieldMetadata = toObjectMetadata.fields.find(
|
||||
(fieldMetadata) =>
|
||||
fieldMetadata.standardId === BASE_OBJECT_STANDARD_FIELD_IDS.deletedAt,
|
||||
);
|
||||
|
||||
if (!deletedFieldMetadata) {
|
||||
throw new RelationMetadataException(
|
||||
`Deleted field metadata not found`,
|
||||
RelationMetadataExceptionCode.RELATION_METADATA_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
this.throwIfDeletedAtFieldMetadataNotFound(deletedAtFieldMetadata);
|
||||
|
||||
await this.indexMetadataService.createIndexMetadata(
|
||||
relationMetadataInput.workspaceId,
|
||||
toObjectMetadata,
|
||||
[foreignKeyFieldMetadata, deletedFieldMetadata],
|
||||
[
|
||||
foreignKeyFieldMetadata,
|
||||
deletedAtFieldMetadata as FieldMetadataEntity<'default'>,
|
||||
],
|
||||
false,
|
||||
false,
|
||||
);
|
||||
@ -441,6 +440,24 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
||||
columnName,
|
||||
);
|
||||
|
||||
const deletedAtFieldMetadata = await this.fieldMetadataRepository.findOneBy(
|
||||
{
|
||||
objectMetadataId: relationMetadata.toObjectMetadataId,
|
||||
name: 'deletedAt',
|
||||
},
|
||||
);
|
||||
|
||||
this.throwIfDeletedAtFieldMetadataNotFound(deletedAtFieldMetadata);
|
||||
|
||||
await this.indexMetadataService.deleteIndexMetadata(
|
||||
workspaceId,
|
||||
relationMetadata.toObjectMetadata,
|
||||
[
|
||||
foreignKeyFieldMetadata,
|
||||
deletedAtFieldMetadata as FieldMetadataEntity<'default'>,
|
||||
],
|
||||
);
|
||||
|
||||
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(
|
||||
relationMetadata.workspaceId,
|
||||
);
|
||||
@ -554,4 +571,15 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
private throwIfDeletedAtFieldMetadataNotFound(
|
||||
deletedAtFieldMetadata?: FieldMetadataEntity<'default'> | null,
|
||||
) {
|
||||
if (!isDefined(deletedAtFieldMetadata)) {
|
||||
throw new RelationMetadataException(
|
||||
`Deleted field metadata not found`,
|
||||
RelationMetadataExceptionCode.RELATION_METADATA_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user