feat: new relation resolver (#9794)
Fix [#240](https://github.com/twentyhq/core-team-issues/issues/240)
This commit is contained in:
@ -75,6 +75,11 @@ export const seedFeatureFlags = async (
|
|||||||
workspaceId: workspaceId,
|
workspaceId: workspaceId,
|
||||||
value: true,
|
value: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: FeatureFlagKey.IsNewRelationEnabled,
|
||||||
|
workspaceId: workspaceId,
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
])
|
])
|
||||||
.execute();
|
.execute();
|
||||||
};
|
};
|
||||||
|
|||||||
@ -14,4 +14,5 @@ export enum FeatureFlagKey {
|
|||||||
IsJsonFilterEnabled = 'IS_JSON_FILTER_ENABLED',
|
IsJsonFilterEnabled = 'IS_JSON_FILTER_ENABLED',
|
||||||
IsLocalizationEnabled = 'IS_LOCALIZATION_ENABLED',
|
IsLocalizationEnabled = 'IS_LOCALIZATION_ENABLED',
|
||||||
IsBillingPlansEnabled = 'IS_BILLING_PLANS_ENABLED',
|
IsBillingPlansEnabled = 'IS_BILLING_PLANS_ENABLED',
|
||||||
|
IsNewRelationEnabled = 'IS_NEW_RELATION_ENABLED',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
import DataLoader from 'dataloader';
|
import DataLoader from 'dataloader';
|
||||||
|
|
||||||
import { RelationMetadataLoaderPayload } from 'src/engine/dataloaders/dataloader.service';
|
import {
|
||||||
|
RelationLoaderPayload,
|
||||||
|
RelationMetadataLoaderPayload,
|
||||||
|
} from 'src/engine/dataloaders/dataloader.service';
|
||||||
|
import { FieldMetadataEntity } 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 { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||||
|
|
||||||
export interface IDataloaders {
|
export interface IDataloaders {
|
||||||
@ -8,4 +13,14 @@ export interface IDataloaders {
|
|||||||
RelationMetadataLoaderPayload,
|
RelationMetadataLoaderPayload,
|
||||||
RelationMetadataEntity
|
RelationMetadataEntity
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
relationLoader: DataLoader<
|
||||||
|
RelationLoaderPayload,
|
||||||
|
{
|
||||||
|
sourceObjectMetadata: ObjectMetadataEntity;
|
||||||
|
targetObjectMetadata: ObjectMetadataEntity;
|
||||||
|
sourceFieldMetadata: FieldMetadataEntity;
|
||||||
|
targetFieldMetadata: FieldMetadataEntity;
|
||||||
|
}
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
import { DataloaderService } from 'src/engine/dataloaders/dataloader.service';
|
import { DataloaderService } from 'src/engine/dataloaders/dataloader.service';
|
||||||
|
import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
|
||||||
import { RelationMetadataModule } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.module';
|
import { RelationMetadataModule } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
imports: [RelationMetadataModule, FieldMetadataModule],
|
||||||
providers: [DataloaderService],
|
providers: [DataloaderService],
|
||||||
imports: [RelationMetadataModule],
|
|
||||||
exports: [DataloaderService],
|
exports: [DataloaderService],
|
||||||
})
|
})
|
||||||
export class DataloaderModule {}
|
export class DataloaderModule {}
|
||||||
|
|||||||
@ -5,6 +5,9 @@ import DataLoader from 'dataloader';
|
|||||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||||
|
|
||||||
import { IDataloaders } from 'src/engine/dataloaders/dataloader.interface';
|
import { IDataloaders } from 'src/engine/dataloaders/dataloader.interface';
|
||||||
|
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
|
import { FieldMetadataRelationService } from 'src/engine/metadata-modules/field-metadata/relation/field-metadata-relation.service';
|
||||||
|
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 { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||||
import { RelationMetadataService } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.service';
|
import { RelationMetadataService } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.service';
|
||||||
|
|
||||||
@ -16,14 +19,37 @@ export type RelationMetadataLoaderPayload = {
|
|||||||
>;
|
>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type RelationLoaderPayload = {
|
||||||
|
workspaceId: string;
|
||||||
|
fieldMetadata: Pick<
|
||||||
|
FieldMetadataInterface,
|
||||||
|
| 'type'
|
||||||
|
| 'id'
|
||||||
|
| 'objectMetadataId'
|
||||||
|
| 'relationTargetFieldMetadataId'
|
||||||
|
| 'relationTargetObjectMetadataId'
|
||||||
|
>;
|
||||||
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DataloaderService {
|
export class DataloaderService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly relationMetadataService: RelationMetadataService,
|
private readonly relationMetadataService: RelationMetadataService,
|
||||||
|
private readonly fieldMetadataRelationService: FieldMetadataRelationService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
createLoaders(): IDataloaders {
|
createLoaders(): IDataloaders {
|
||||||
const relationMetadataLoader = new DataLoader<
|
const relationMetadataLoader = this.createRelationMetadataLoader();
|
||||||
|
const relationLoader = this.createRelationLoader();
|
||||||
|
|
||||||
|
return {
|
||||||
|
relationMetadataLoader,
|
||||||
|
relationLoader,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private createRelationMetadataLoader() {
|
||||||
|
return new DataLoader<
|
||||||
RelationMetadataLoaderPayload,
|
RelationMetadataLoaderPayload,
|
||||||
RelationMetadataEntity
|
RelationMetadataEntity
|
||||||
>(async (dataLoaderParams: RelationMetadataLoaderPayload[]) => {
|
>(async (dataLoaderParams: RelationMetadataLoaderPayload[]) => {
|
||||||
@ -40,9 +66,30 @@ export class DataloaderService {
|
|||||||
|
|
||||||
return relationsMetadataCollection;
|
return relationsMetadataCollection;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
private createRelationLoader() {
|
||||||
relationMetadataLoader,
|
return new DataLoader<
|
||||||
};
|
RelationLoaderPayload,
|
||||||
|
{
|
||||||
|
sourceObjectMetadata: ObjectMetadataEntity;
|
||||||
|
targetObjectMetadata: ObjectMetadataEntity;
|
||||||
|
sourceFieldMetadata: FieldMetadataEntity;
|
||||||
|
targetFieldMetadata: FieldMetadataEntity;
|
||||||
|
}
|
||||||
|
>(async (dataLoaderParams: RelationLoaderPayload[]) => {
|
||||||
|
const workspaceId = dataLoaderParams[0].workspaceId;
|
||||||
|
const fieldMetadataItems = dataLoaderParams.map(
|
||||||
|
(dataLoaderParam) => dataLoaderParam.fieldMetadata,
|
||||||
|
);
|
||||||
|
|
||||||
|
const fieldMetadataRelationCollection =
|
||||||
|
await this.fieldMetadataRelationService.findCachedFieldMetadataRelation(
|
||||||
|
fieldMetadataItems,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return fieldMetadataRelationCollection;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,38 @@
|
|||||||
|
import { Field, ObjectType, registerEnumType } from '@nestjs/graphql';
|
||||||
|
|
||||||
|
import { IsEnum, IsNotEmpty } from 'class-validator';
|
||||||
|
import { Relation } from 'typeorm';
|
||||||
|
|
||||||
|
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||||
|
|
||||||
|
import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto';
|
||||||
|
import { ObjectMetadataDTO } from 'src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto';
|
||||||
|
|
||||||
|
registerEnumType(RelationType, {
|
||||||
|
name: 'RelationType',
|
||||||
|
description: 'Relation type',
|
||||||
|
});
|
||||||
|
|
||||||
|
@ObjectType('Relation')
|
||||||
|
export class RelationDTO {
|
||||||
|
@IsEnum(RelationType)
|
||||||
|
@IsNotEmpty()
|
||||||
|
@Field(() => RelationType)
|
||||||
|
type: RelationType;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@Field(() => ObjectMetadataDTO)
|
||||||
|
sourceObjectMetadata: Relation<ObjectMetadataDTO>;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@Field(() => ObjectMetadataDTO)
|
||||||
|
targetObjectMetadata: Relation<ObjectMetadataDTO>;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@Field(() => FieldMetadataDTO)
|
||||||
|
sourceFieldMetadata: Relation<FieldMetadataDTO>;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@Field(() => FieldMetadataDTO)
|
||||||
|
targetFieldMetadata: Relation<FieldMetadataDTO>;
|
||||||
|
}
|
||||||
@ -14,4 +14,6 @@ export enum FieldMetadataExceptionCode {
|
|||||||
FIELD_ALREADY_EXISTS = 'FIELD_ALREADY_EXISTS',
|
FIELD_ALREADY_EXISTS = 'FIELD_ALREADY_EXISTS',
|
||||||
OBJECT_METADATA_NOT_FOUND = 'OBJECT_METADATA_NOT_FOUND',
|
OBJECT_METADATA_NOT_FOUND = 'OBJECT_METADATA_NOT_FOUND',
|
||||||
INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR',
|
INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR',
|
||||||
|
FIELD_METADATA_RELATION_NOT_ENABLED = 'FIELD_METADATA_RELATION_NOT_ENABLED',
|
||||||
|
FIELD_METADATA_RELATION_MALFORMED = 'FIELD_METADATA_RELATION_MALFORMED',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,12 +9,14 @@ import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
|||||||
|
|
||||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
||||||
import { ActorModule } from 'src/engine/core-modules/actor/actor.module';
|
import { ActorModule } from 'src/engine/core-modules/actor/actor.module';
|
||||||
|
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||||
import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto';
|
import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto';
|
||||||
import { FieldMetadataValidationService } from 'src/engine/metadata-modules/field-metadata/field-metadata-validation.service';
|
import { FieldMetadataValidationService } from 'src/engine/metadata-modules/field-metadata/field-metadata-validation.service';
|
||||||
import { FieldMetadataResolver } from 'src/engine/metadata-modules/field-metadata/field-metadata.resolver';
|
import { FieldMetadataResolver } from 'src/engine/metadata-modules/field-metadata/field-metadata.resolver';
|
||||||
import { FieldMetadataGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/field-metadata/interceptors/field-metadata-graphql-api-exception.interceptor';
|
import { FieldMetadataGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/field-metadata/interceptors/field-metadata-graphql-api-exception.interceptor';
|
||||||
|
import { FieldMetadataRelationService } from 'src/engine/metadata-modules/field-metadata/relation/field-metadata-relation.service';
|
||||||
import { FieldMetadataRelatedRecordsService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata-related-records.service';
|
import { FieldMetadataRelatedRecordsService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata-related-records.service';
|
||||||
import { IsFieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/validators/is-field-metadata-default-value.validator';
|
import { IsFieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/validators/is-field-metadata-default-value.validator';
|
||||||
import { IsFieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/validators/is-field-metadata-options.validator';
|
import { IsFieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/validators/is-field-metadata-options.validator';
|
||||||
@ -22,6 +24,7 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat
|
|||||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||||
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
|
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
|
||||||
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
|
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
|
||||||
|
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||||
import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module';
|
import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module';
|
||||||
import { ViewModule } from 'src/modules/view/view.module';
|
import { ViewModule } from 'src/modules/view/view.module';
|
||||||
|
|
||||||
@ -42,6 +45,8 @@ import { UpdateFieldInput } from './dtos/update-field.input';
|
|||||||
WorkspaceMigrationModule,
|
WorkspaceMigrationModule,
|
||||||
WorkspaceMigrationRunnerModule,
|
WorkspaceMigrationRunnerModule,
|
||||||
WorkspaceMetadataVersionModule,
|
WorkspaceMetadataVersionModule,
|
||||||
|
WorkspaceCacheStorageModule,
|
||||||
|
FeatureFlagModule,
|
||||||
ObjectMetadataModule,
|
ObjectMetadataModule,
|
||||||
DataSourceModule,
|
DataSourceModule,
|
||||||
TypeORMModule,
|
TypeORMModule,
|
||||||
@ -86,9 +91,14 @@ import { UpdateFieldInput } from './dtos/update-field.input';
|
|||||||
IsFieldMetadataDefaultValue,
|
IsFieldMetadataDefaultValue,
|
||||||
IsFieldMetadataOptions,
|
IsFieldMetadataOptions,
|
||||||
FieldMetadataService,
|
FieldMetadataService,
|
||||||
|
FieldMetadataRelationService,
|
||||||
FieldMetadataRelatedRecordsService,
|
FieldMetadataRelatedRecordsService,
|
||||||
FieldMetadataResolver,
|
FieldMetadataResolver,
|
||||||
],
|
],
|
||||||
exports: [FieldMetadataService, FieldMetadataRelatedRecordsService],
|
exports: [
|
||||||
|
FieldMetadataService,
|
||||||
|
FieldMetadataRelationService,
|
||||||
|
FieldMetadataRelatedRecordsService,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class FieldMetadataModule {}
|
export class FieldMetadataModule {}
|
||||||
|
|||||||
@ -14,6 +14,8 @@ import {
|
|||||||
|
|
||||||
import { FieldMetadataType } from 'twenty-shared';
|
import { FieldMetadataType } from 'twenty-shared';
|
||||||
|
|
||||||
|
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 { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
import { IDataloaders } from 'src/engine/dataloaders/dataloader.interface';
|
import { IDataloaders } from 'src/engine/dataloaders/dataloader.interface';
|
||||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||||
@ -22,14 +24,24 @@ import { CreateOneFieldMetadataInput } from 'src/engine/metadata-modules/field-m
|
|||||||
import { DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/delete-field.input';
|
import { DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/delete-field.input';
|
||||||
import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto';
|
import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto';
|
||||||
import { RelationDefinitionDTO } from 'src/engine/metadata-modules/field-metadata/dtos/relation-definition.dto';
|
import { RelationDefinitionDTO } from 'src/engine/metadata-modules/field-metadata/dtos/relation-definition.dto';
|
||||||
|
import { RelationDTO } from 'src/engine/metadata-modules/field-metadata/dtos/relation.dto';
|
||||||
import { UpdateOneFieldMetadataInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
|
import { UpdateOneFieldMetadataInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
|
||||||
|
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
|
import {
|
||||||
|
FieldMetadataException,
|
||||||
|
FieldMetadataExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||||
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/field-metadata.service';
|
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/field-metadata.service';
|
||||||
import { fieldMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/field-metadata/utils/field-metadata-graphql-api-exception-handler.util';
|
import { fieldMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/field-metadata/utils/field-metadata-graphql-api-exception-handler.util';
|
||||||
|
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
||||||
|
|
||||||
@UseGuards(WorkspaceAuthGuard)
|
@UseGuards(WorkspaceAuthGuard)
|
||||||
@Resolver(() => FieldMetadataDTO)
|
@Resolver(() => FieldMetadataDTO)
|
||||||
export class FieldMetadataResolver {
|
export class FieldMetadataResolver {
|
||||||
constructor(private readonly fieldMetadataService: FieldMetadataService) {}
|
constructor(
|
||||||
|
private readonly fieldMetadataService: FieldMetadataService,
|
||||||
|
private readonly featureFlagService: FeatureFlagService,
|
||||||
|
) {}
|
||||||
|
|
||||||
@Mutation(() => FieldMetadataDTO)
|
@Mutation(() => FieldMetadataDTO)
|
||||||
async createOneField(
|
async createOneField(
|
||||||
@ -127,4 +139,56 @@ export class FieldMetadataResolver {
|
|||||||
fieldMetadataGraphqlApiExceptionHandler(error);
|
fieldMetadataGraphqlApiExceptionHandler(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ResolveField(() => RelationDTO, { nullable: true })
|
||||||
|
async relation(
|
||||||
|
@AuthWorkspace() workspace: Workspace,
|
||||||
|
@Parent() fieldMetadata: FieldMetadataEntity<FieldMetadataType.RELATION>,
|
||||||
|
@Context() context: { loaders: IDataloaders },
|
||||||
|
): Promise<RelationDTO | null | undefined> {
|
||||||
|
if (!isRelationFieldMetadataType(fieldMetadata.type)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const isNewRelationEnabled =
|
||||||
|
await this.featureFlagService.isFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsNewRelationEnabled,
|
||||||
|
workspace.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isNewRelationEnabled) {
|
||||||
|
throw new FieldMetadataException(
|
||||||
|
'New relation feature is not enabled for this workspace',
|
||||||
|
FieldMetadataExceptionCode.FIELD_METADATA_RELATION_NOT_ENABLED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const {
|
||||||
|
sourceObjectMetadata,
|
||||||
|
targetObjectMetadata,
|
||||||
|
sourceFieldMetadata,
|
||||||
|
targetFieldMetadata,
|
||||||
|
} = await context.loaders.relationLoader.load({
|
||||||
|
fieldMetadata,
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!fieldMetadata.settings) {
|
||||||
|
throw new FieldMetadataException(
|
||||||
|
'Relation settings are required',
|
||||||
|
FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: fieldMetadata.settings.relationType,
|
||||||
|
sourceObjectMetadata,
|
||||||
|
targetObjectMetadata,
|
||||||
|
sourceFieldMetadata,
|
||||||
|
targetFieldMetadata,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
fieldMetadataGraphqlApiExceptionHandler(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-met
|
|||||||
import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface';
|
import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface';
|
||||||
import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
|
import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
|
||||||
|
|
||||||
|
import { FieldMetadataEntity } 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 { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||||
|
|
||||||
export interface FieldMetadataInterface<
|
export interface FieldMetadataInterface<
|
||||||
@ -23,6 +25,10 @@ export interface FieldMetadataInterface<
|
|||||||
isUnique?: boolean;
|
isUnique?: boolean;
|
||||||
fromRelationMetadata?: RelationMetadataEntity;
|
fromRelationMetadata?: RelationMetadataEntity;
|
||||||
toRelationMetadata?: RelationMetadataEntity;
|
toRelationMetadata?: RelationMetadataEntity;
|
||||||
|
relationTargetFieldMetadataId?: string;
|
||||||
|
relationTargetFieldMetadata?: FieldMetadataEntity;
|
||||||
|
relationTargetObjectMetadataId?: string;
|
||||||
|
relationTargetObjectMetadata?: ObjectMetadataEntity;
|
||||||
isCustom?: boolean;
|
isCustom?: boolean;
|
||||||
generatedType?: 'STORED' | 'VIRTUAL';
|
generatedType?: 'STORED' | 'VIRTUAL';
|
||||||
asExpression?: string;
|
asExpression?: string;
|
||||||
|
|||||||
@ -0,0 +1,109 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||||
|
|
||||||
|
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||||
|
import {
|
||||||
|
FieldMetadataException,
|
||||||
|
FieldMetadataExceptionCode,
|
||||||
|
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||||
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
|
import { cleanObjectMetadata } from 'src/engine/metadata-modules/utils/clean-object-metadata.util';
|
||||||
|
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class FieldMetadataRelationService {
|
||||||
|
constructor(
|
||||||
|
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async findCachedFieldMetadataRelation(
|
||||||
|
fieldMetadataItems: Array<
|
||||||
|
Pick<
|
||||||
|
FieldMetadataInterface,
|
||||||
|
| 'id'
|
||||||
|
| 'type'
|
||||||
|
| 'objectMetadataId'
|
||||||
|
| 'relationTargetFieldMetadataId'
|
||||||
|
| 'relationTargetObjectMetadataId'
|
||||||
|
>
|
||||||
|
>,
|
||||||
|
workspaceId: string,
|
||||||
|
): Promise<
|
||||||
|
Array<{
|
||||||
|
sourceObjectMetadata: ObjectMetadataEntity;
|
||||||
|
sourceFieldMetadata: FieldMetadataEntity;
|
||||||
|
targetObjectMetadata: ObjectMetadataEntity;
|
||||||
|
targetFieldMetadata: FieldMetadataEntity;
|
||||||
|
}>
|
||||||
|
> {
|
||||||
|
const metadataVersion =
|
||||||
|
await this.workspaceCacheStorageService.getMetadataVersion(workspaceId);
|
||||||
|
|
||||||
|
if (!metadataVersion) {
|
||||||
|
throw new FieldMetadataException(
|
||||||
|
`Metadata version not found for workspace ${workspaceId}`,
|
||||||
|
FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const objectMetadataMaps =
|
||||||
|
await this.workspaceCacheStorageService.getObjectMetadataMaps(
|
||||||
|
workspaceId,
|
||||||
|
metadataVersion,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!objectMetadataMaps) {
|
||||||
|
throw new FieldMetadataException(
|
||||||
|
`Object metadata map not found for workspace ${workspaceId} and metadata version ${metadataVersion}`,
|
||||||
|
FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fieldMetadataItems.map((fieldMetadataItem) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
objectMetadataId,
|
||||||
|
relationTargetFieldMetadataId,
|
||||||
|
relationTargetObjectMetadataId,
|
||||||
|
} = fieldMetadataItem;
|
||||||
|
|
||||||
|
if (!relationTargetObjectMetadataId || !relationTargetFieldMetadataId) {
|
||||||
|
throw new FieldMetadataException(
|
||||||
|
`Relation target object metadata id or relation target field metadata id not found for field metadata ${id}`,
|
||||||
|
FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceObjectMetadata = objectMetadataMaps.byId[objectMetadataId];
|
||||||
|
const targetObjectMetadata =
|
||||||
|
objectMetadataMaps.byId[relationTargetObjectMetadataId];
|
||||||
|
const sourceFieldMetadata = sourceObjectMetadata.fieldsById[id];
|
||||||
|
const targetFieldMetadata =
|
||||||
|
targetObjectMetadata.fieldsById[relationTargetFieldMetadataId];
|
||||||
|
|
||||||
|
if (
|
||||||
|
!sourceObjectMetadata ||
|
||||||
|
!targetObjectMetadata ||
|
||||||
|
!sourceFieldMetadata ||
|
||||||
|
!targetFieldMetadata
|
||||||
|
) {
|
||||||
|
throw new FieldMetadataException(
|
||||||
|
`Field relation metadata not found for field metadata ${id}`,
|
||||||
|
FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
sourceObjectMetadata: cleanObjectMetadata(
|
||||||
|
sourceObjectMetadata,
|
||||||
|
) as ObjectMetadataEntity,
|
||||||
|
sourceFieldMetadata: sourceFieldMetadata as FieldMetadataEntity,
|
||||||
|
targetObjectMetadata: cleanObjectMetadata(
|
||||||
|
targetObjectMetadata,
|
||||||
|
) as ObjectMetadataEntity,
|
||||||
|
targetFieldMetadata: targetFieldMetadata as FieldMetadataEntity,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -23,6 +23,8 @@ export const fieldMetadataGraphqlApiExceptionHandler = (error: Error) => {
|
|||||||
throw new ConflictError(error.message);
|
throw new ConflictError(error.message);
|
||||||
case FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND:
|
case FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND:
|
||||||
case FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR:
|
case FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR:
|
||||||
|
case FieldMetadataExceptionCode.FIELD_METADATA_RELATION_NOT_ENABLED:
|
||||||
|
case FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED:
|
||||||
default:
|
default:
|
||||||
throw new InternalServerError(error.message);
|
throw new InternalServerError(error.message);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
import omit from 'lodash.omit';
|
||||||
|
|
||||||
|
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||||
|
|
||||||
|
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||||
|
|
||||||
|
export const cleanObjectMetadata = (
|
||||||
|
objectMetadata: ObjectMetadataItemWithFieldMaps,
|
||||||
|
): ObjectMetadataInterface =>
|
||||||
|
omit(objectMetadata, ['fieldsById', 'fieldsByName']);
|
||||||
Reference in New Issue
Block a user