feat: new relation sync-metadata, twenty-orm, create/update (#10217)
Fix https://github.com/twentyhq/core-team-issues/issues/330#issue-2827026606 and https://github.com/twentyhq/core-team-issues/issues/327#issue-2827001814 What this PR does when `isNewRelationEnabled` is set to `true`: - [x] Drop the creation of the foreign key as a `FieldMetadata` - [x] Stop creating `RelationMetadata` - [x] Properly fill `FieldMetadata` of type `RELATION` during the sync command - [x] Use new relation settings in TwentyORM - [x] Properly create `FieldMetadata` relations when we create a new object - [x] Handle `database:reset` with new relations --------- Co-authored-by: Charles Bochet <charles@twenty.com> Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
This commit is contained in:
@ -3,13 +3,17 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||
import camelCase from 'lodash.camelcase';
|
||||
import { FindOneOptions, In, Repository } from 'typeorm';
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { FindOneOptions, In, Repository } from 'typeorm';
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
|
||||
import { FieldMetadataDefaultSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
|
||||
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 { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/field-metadata.service';
|
||||
import { IndexMetadataService } from 'src/engine/metadata-modules/index-metadata/index-metadata.service';
|
||||
@ -20,6 +24,7 @@ import {
|
||||
RelationMetadataException,
|
||||
RelationMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.exception';
|
||||
import { InvalidMetadataNameException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata-name.exception';
|
||||
import { validateFieldNameAvailabilityOrThrow } from 'src/engine/metadata-modules/utils/validate-field-name-availability.utils';
|
||||
import { validateMetadataNameOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils';
|
||||
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
|
||||
@ -34,7 +39,6 @@ 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 { InvalidMetadataNameException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata-name.exception';
|
||||
|
||||
import {
|
||||
RelationMetadataEntity,
|
||||
@ -56,6 +60,7 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
||||
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
||||
private readonly indexMetadataService: IndexMetadataService,
|
||||
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {
|
||||
super(relationMetadataRepository);
|
||||
}
|
||||
@ -67,6 +72,11 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
||||
relationMetadataInput,
|
||||
);
|
||||
|
||||
const isNewRelationEnabled = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IsNewRelationEnabled,
|
||||
relationMetadataInput.workspaceId,
|
||||
);
|
||||
|
||||
try {
|
||||
validateMetadataNameOrThrow(relationMetadataInput.fromName);
|
||||
validateMetadataNameOrThrow(relationMetadataInput.toName);
|
||||
@ -92,21 +102,31 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
||||
const toId = uuidV4();
|
||||
|
||||
const createdRelationFieldsMetadata =
|
||||
await this.fieldMetadataRepository.save([
|
||||
this.createFieldMetadataForRelationMetadata(
|
||||
relationMetadataInput,
|
||||
'from',
|
||||
isCustom,
|
||||
fromId,
|
||||
),
|
||||
this.createFieldMetadataForRelationMetadata(
|
||||
relationMetadataInput,
|
||||
'to',
|
||||
isCustom,
|
||||
toId,
|
||||
),
|
||||
this.createForeignKeyFieldMetadata(relationMetadataInput, columnName),
|
||||
]);
|
||||
await this.fieldMetadataRepository.save(
|
||||
[
|
||||
this.createFieldMetadataForRelationMetadata(
|
||||
relationMetadataInput,
|
||||
'from',
|
||||
isCustom,
|
||||
fromId,
|
||||
),
|
||||
this.createFieldMetadataForRelationMetadata(
|
||||
relationMetadataInput,
|
||||
'to',
|
||||
isCustom,
|
||||
toId,
|
||||
),
|
||||
// We don't want to create the join column field metadata for new relation
|
||||
...(isNewRelationEnabled
|
||||
? []
|
||||
: [
|
||||
this.createForeignKeyFieldMetadata(
|
||||
relationMetadataInput,
|
||||
columnName,
|
||||
),
|
||||
]),
|
||||
].flat(),
|
||||
);
|
||||
|
||||
const createdRelationMetadata = await super.createOne({
|
||||
...relationMetadataInput,
|
||||
@ -114,6 +134,46 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
||||
toFieldMetadataId: toId,
|
||||
});
|
||||
|
||||
const fromFieldMetadata = createdRelationFieldsMetadata.find(
|
||||
(fieldMetadata) => fieldMetadata.id === fromId,
|
||||
);
|
||||
|
||||
if (!fromFieldMetadata) {
|
||||
throw new RelationMetadataException(
|
||||
`From field metadata not found`,
|
||||
RelationMetadataExceptionCode.RELATION_METADATA_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const toFieldMetadata = createdRelationFieldsMetadata.find(
|
||||
(fieldMetadata) => fieldMetadata.id === toId,
|
||||
);
|
||||
|
||||
if (!toFieldMetadata) {
|
||||
throw new RelationMetadataException(
|
||||
`To field metadata not found`,
|
||||
RelationMetadataExceptionCode.RELATION_METADATA_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
await this.fieldMetadataRepository.update(fromId, {
|
||||
settings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
} as Partial<FieldMetadataDefaultSettings>,
|
||||
relationTargetFieldMetadataId: toId,
|
||||
relationTargetObjectMetadataId: relationMetadataInput.toObjectMetadataId,
|
||||
});
|
||||
|
||||
await this.fieldMetadataRepository.update(toId, {
|
||||
settings: {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
joinColumnName: `${relationMetadataInput.toName}Id`,
|
||||
} as Partial<FieldMetadataDefaultSettings>,
|
||||
relationTargetFieldMetadataId: fromId,
|
||||
relationTargetObjectMetadataId:
|
||||
relationMetadataInput.fromObjectMetadataId,
|
||||
});
|
||||
|
||||
await this.createWorkspaceCustomMigration(
|
||||
relationMetadataInput,
|
||||
objectMetadataMap,
|
||||
@ -123,17 +183,6 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
||||
const toObjectMetadata =
|
||||
objectMetadataMap[relationMetadataInput.toObjectMetadataId];
|
||||
|
||||
const foreignKeyFieldMetadata = createdRelationFieldsMetadata.find(
|
||||
(fieldMetadata) => fieldMetadata.type === FieldMetadataType.UUID,
|
||||
);
|
||||
|
||||
if (!foreignKeyFieldMetadata) {
|
||||
throw new RelationMetadataException(
|
||||
`ForeignKey field metadata not found`,
|
||||
RelationMetadataExceptionCode.RELATION_METADATA_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const deletedAtFieldMetadata = toObjectMetadata.fields.find(
|
||||
(fieldMetadata) =>
|
||||
fieldMetadata.standardId === BASE_OBJECT_STANDARD_FIELD_IDS.deletedAt,
|
||||
@ -141,16 +190,29 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
||||
|
||||
this.throwIfDeletedAtFieldMetadataNotFound(deletedAtFieldMetadata);
|
||||
|
||||
await this.indexMetadataService.createIndexMetadata(
|
||||
relationMetadataInput.workspaceId,
|
||||
toObjectMetadata,
|
||||
[
|
||||
foreignKeyFieldMetadata,
|
||||
deletedAtFieldMetadata as FieldMetadataEntity<FieldMetadataType>,
|
||||
],
|
||||
false,
|
||||
false,
|
||||
);
|
||||
if (!isNewRelationEnabled) {
|
||||
const foreignKeyFieldMetadata = createdRelationFieldsMetadata.find(
|
||||
(fieldMetadata) => fieldMetadata.type === FieldMetadataType.UUID,
|
||||
);
|
||||
|
||||
if (!foreignKeyFieldMetadata) {
|
||||
throw new RelationMetadataException(
|
||||
`ForeignKey field metadata not found`,
|
||||
RelationMetadataExceptionCode.RELATION_METADATA_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
await this.indexMetadataService.createIndexMetadata(
|
||||
relationMetadataInput.workspaceId,
|
||||
toObjectMetadata,
|
||||
[
|
||||
foreignKeyFieldMetadata,
|
||||
deletedAtFieldMetadata as FieldMetadataEntity<FieldMetadataType>,
|
||||
],
|
||||
false,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(
|
||||
relationMetadataInput.workspaceId,
|
||||
|
||||
Reference in New Issue
Block a user