feat: new relation schema generation (#9882)
Fix https://github.com/twentyhq/core-team-issues/issues/295 Based on the feature-flag `IsNewRelationEnabled` the schema will be marked as outdated and regenerated, this will cause an error on the front-end on the first request on the following ones schema will be well generated ans request will work.
This commit is contained in:
@ -4,6 +4,7 @@ import { ScalarsExplorerService } from 'src/engine/api/graphql/services/scalars-
|
||||
import { WorkspaceResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory';
|
||||
import { WorkspaceGraphQLSchemaFactory } from 'src/engine/api/graphql/workspace-schema-builder/workspace-graphql-schema.factory';
|
||||
import { WorkspaceSchemaFactory } from 'src/engine/api/graphql/workspace-schema.factory';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
||||
@ -44,6 +45,10 @@ describe('WorkspaceSchemaFactory', () => {
|
||||
provide: WorkspaceMetadataCacheService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: FeatureFlagService,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ import { Module } from '@nestjs/common';
|
||||
import { ScalarsExplorerService } from 'src/engine/api/graphql/services/scalars-explorer.service';
|
||||
import { WorkspaceResolverBuilderModule } from 'src/engine/api/graphql/workspace-resolver-builder/workspace-resolver-builder.module';
|
||||
import { WorkspaceSchemaBuilderModule } from 'src/engine/api/graphql/workspace-schema-builder/workspace-schema-builder.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { MetadataEngineModule } from 'src/engine/metadata-modules/metadata-engine.module';
|
||||
import { WorkspaceMetadataCacheModule } from 'src/engine/metadata-modules/workspace-metadata-cache/workspace-metadata-cache.module';
|
||||
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||
@ -16,6 +17,7 @@ import { WorkspaceSchemaFactory } from './workspace-schema.factory';
|
||||
WorkspaceResolverBuilderModule,
|
||||
WorkspaceCacheStorageModule,
|
||||
WorkspaceMetadataCacheModule,
|
||||
FeatureFlagModule,
|
||||
],
|
||||
providers: [WorkspaceSchemaFactory, ScalarsExplorerService],
|
||||
exports: [WorkspaceSchemaFactory],
|
||||
|
||||
@ -20,4 +20,5 @@ export enum GraphqlQueryRunnerExceptionCode {
|
||||
INVALID_ARGS_FIRST = 'INVALID_ARGS_FIRST',
|
||||
INVALID_ARGS_LAST = 'INVALID_ARGS_LAST',
|
||||
METADATA_CACHE_VERSION_NOT_FOUND = 'METADATA_CACHE_VERSION_NOT_FOUND',
|
||||
METADATA_CACHE_FEATURE_FLAG_RECOMPUTATION_REQUIRED = 'METADATA_CACHE_FEATURE_FLAG_RECOMPUTATION_REQUIRED',
|
||||
}
|
||||
|
||||
@ -0,0 +1,150 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import {
|
||||
GraphQLFieldConfigArgumentMap,
|
||||
GraphQLFieldConfigMap,
|
||||
GraphQLObjectType,
|
||||
} from 'graphql';
|
||||
|
||||
import { WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-optionts.interface';
|
||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
|
||||
import { RelationTypeV2Factory } from 'src/engine/api/graphql/workspace-schema-builder/factories/relation-type-v2.factory';
|
||||
import { TypeDefinitionsStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage';
|
||||
import { getResolverArgs } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util';
|
||||
import { objectContainsRelationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/object-contains-relation-field';
|
||||
import { isRelationFieldMetadata } from 'src/engine/utils/is-relation-field-metadata.util';
|
||||
|
||||
import { ArgsFactory } from './args.factory';
|
||||
|
||||
export enum ObjectTypeDefinitionKind {
|
||||
Connection = 'Connection',
|
||||
Edge = 'Edge',
|
||||
Plain = '',
|
||||
}
|
||||
|
||||
export interface ObjectTypeDefinition {
|
||||
target: string;
|
||||
kind: ObjectTypeDefinitionKind;
|
||||
type: GraphQLObjectType;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ExtendObjectTypeDefinitionV2Factory {
|
||||
private readonly logger = new Logger(
|
||||
ExtendObjectTypeDefinitionV2Factory.name,
|
||||
);
|
||||
|
||||
constructor(
|
||||
private readonly relationTypeV2Factory: RelationTypeV2Factory,
|
||||
private readonly argsFactory: ArgsFactory,
|
||||
private readonly typeDefinitionsStorage: TypeDefinitionsStorage,
|
||||
) {}
|
||||
|
||||
public create(
|
||||
objectMetadata: ObjectMetadataInterface,
|
||||
options: WorkspaceBuildSchemaOptions,
|
||||
): ObjectTypeDefinition {
|
||||
const kind = ObjectTypeDefinitionKind.Plain;
|
||||
const gqlType = this.typeDefinitionsStorage.getObjectTypeByKey(
|
||||
objectMetadata.id,
|
||||
kind,
|
||||
);
|
||||
const containsRelationField = objectContainsRelationField(objectMetadata);
|
||||
|
||||
if (!gqlType) {
|
||||
this.logger.error(
|
||||
`Could not find a GraphQL type for ${objectMetadata.id.toString()}`,
|
||||
{
|
||||
objectMetadata,
|
||||
options,
|
||||
},
|
||||
);
|
||||
|
||||
throw new Error(
|
||||
`Could not find a GraphQL type for ${objectMetadata.id.toString()}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Security check to avoid extending an object that does not need to be extended
|
||||
if (!containsRelationField) {
|
||||
this.logger.error(
|
||||
`This object does not need to be extended: ${objectMetadata.id.toString()}`,
|
||||
{
|
||||
objectMetadata,
|
||||
options,
|
||||
},
|
||||
);
|
||||
|
||||
throw new Error(
|
||||
`This object does not need to be extended: ${objectMetadata.id.toString()}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Extract current object config to extend it
|
||||
const config = gqlType.toConfig();
|
||||
|
||||
// Recreate the same object type with the new fields
|
||||
return {
|
||||
target: objectMetadata.id,
|
||||
kind,
|
||||
type: new GraphQLObjectType({
|
||||
...config,
|
||||
fields: () => ({
|
||||
...config.fields,
|
||||
...this.generateFields(objectMetadata, options),
|
||||
}),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
private generateFields(
|
||||
objectMetadata: ObjectMetadataInterface,
|
||||
options: WorkspaceBuildSchemaOptions,
|
||||
): GraphQLFieldConfigMap<any, any> {
|
||||
const fields: GraphQLFieldConfigMap<any, any> = {};
|
||||
|
||||
for (const fieldMetadata of objectMetadata.fields) {
|
||||
// Ignore non-relation fields as they are already defined
|
||||
if (!isRelationFieldMetadata(fieldMetadata)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!fieldMetadata.settings) {
|
||||
throw new Error(
|
||||
`Field Metadata of type RELATION with id ${fieldMetadata.id} has no settings`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!fieldMetadata.relationTargetObjectMetadataId) {
|
||||
throw new Error(
|
||||
`Field Metadata of type RELATION with id ${fieldMetadata.id} has no relation target object metadata id`,
|
||||
);
|
||||
}
|
||||
|
||||
const relationType = this.relationTypeV2Factory.create(fieldMetadata);
|
||||
let argsType: GraphQLFieldConfigArgumentMap | undefined = undefined;
|
||||
|
||||
if (fieldMetadata.settings.relationType === RelationType.ONE_TO_MANY) {
|
||||
const args = getResolverArgs('findMany');
|
||||
|
||||
argsType = this.argsFactory.create(
|
||||
{
|
||||
args,
|
||||
objectMetadataId: fieldMetadata.relationTargetObjectMetadataId,
|
||||
},
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
fields[fieldMetadata.name] = {
|
||||
type: relationType,
|
||||
args: argsType,
|
||||
description: fieldMetadata.description,
|
||||
};
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
@ -10,17 +10,17 @@ import { WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-sc
|
||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||
|
||||
import { TypeDefinitionsStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage';
|
||||
import { objectContainsRelationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/object-contains-relation-field';
|
||||
import { getResolverArgs } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util';
|
||||
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
||||
import { objectContainsRelationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/object-contains-relation-field';
|
||||
import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||
import {
|
||||
RelationDirection,
|
||||
deduceRelationDirection,
|
||||
} from 'src/engine/utils/deduce-relation-direction.util';
|
||||
import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
||||
|
||||
import { RelationTypeFactory } from './relation-type.factory';
|
||||
import { ArgsFactory } from './args.factory';
|
||||
import { RelationTypeFactory } from './relation-type.factory';
|
||||
|
||||
export enum ObjectTypeDefinitionKind {
|
||||
Connection = 'Connection',
|
||||
|
||||
@ -3,6 +3,8 @@ import { CompositeEnumTypeDefinitionFactory } from 'src/engine/api/graphql/works
|
||||
import { CompositeInputTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/composite-input-type-definition.factory';
|
||||
import { CompositeObjectTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/composite-object-type-definition.factory';
|
||||
import { EnumTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/enum-type-definition.factory';
|
||||
import { ExtendObjectTypeDefinitionV2Factory } from 'src/engine/api/graphql/workspace-schema-builder/factories/extend-object-type-definition-v2.factory';
|
||||
import { RelationTypeV2Factory } from 'src/engine/api/graphql/workspace-schema-builder/factories/relation-type-v2.factory';
|
||||
|
||||
import { ArgsFactory } from './args.factory';
|
||||
import { ConnectionTypeDefinitionFactory } from './connection-type-definition.factory';
|
||||
@ -31,7 +33,9 @@ export const workspaceSchemaBuilderFactories = [
|
||||
EnumTypeDefinitionFactory,
|
||||
CompositeEnumTypeDefinitionFactory,
|
||||
RelationTypeFactory,
|
||||
RelationTypeV2Factory,
|
||||
ExtendObjectTypeDefinitionFactory,
|
||||
ExtendObjectTypeDefinitionV2Factory,
|
||||
ConnectionTypeFactory,
|
||||
ConnectionTypeDefinitionFactory,
|
||||
EdgeTypeFactory,
|
||||
|
||||
@ -0,0 +1,56 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { GraphQLOutputType } from 'graphql';
|
||||
import { FieldMetadataType } from 'twenty-shared';
|
||||
|
||||
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 { TypeDefinitionsStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage';
|
||||
|
||||
import { ObjectTypeDefinitionKind } from './object-type-definition.factory';
|
||||
|
||||
@Injectable()
|
||||
export class RelationTypeV2Factory {
|
||||
private readonly logger = new Logger(RelationTypeV2Factory.name);
|
||||
|
||||
constructor(
|
||||
private readonly typeDefinitionsStorage: TypeDefinitionsStorage,
|
||||
) {}
|
||||
|
||||
public create(
|
||||
fieldMetadata: FieldMetadataInterface<FieldMetadataType.RELATION>,
|
||||
): GraphQLOutputType {
|
||||
if (!fieldMetadata.settings) {
|
||||
throw new Error(
|
||||
`Field Metadata of type RELATION with id ${fieldMetadata.id} has no settings`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!fieldMetadata.relationTargetObjectMetadataId) {
|
||||
throw new Error(
|
||||
`Field Metadata of type RELATION with id ${fieldMetadata.id} has no relation target object metadata id`,
|
||||
);
|
||||
}
|
||||
|
||||
const relationGqlType = this.typeDefinitionsStorage.getObjectTypeByKey(
|
||||
fieldMetadata.relationTargetObjectMetadataId,
|
||||
fieldMetadata.settings.relationType === RelationType.ONE_TO_MANY
|
||||
? ObjectTypeDefinitionKind.Connection
|
||||
: ObjectTypeDefinitionKind.Plain,
|
||||
);
|
||||
|
||||
if (!relationGqlType) {
|
||||
this.logger.error(
|
||||
`Could not find a relation type for ${fieldMetadata.id}`,
|
||||
{
|
||||
fieldMetadata,
|
||||
},
|
||||
);
|
||||
|
||||
throw new Error(`Could not find a relation type for ${fieldMetadata.id}`);
|
||||
}
|
||||
|
||||
return relationGqlType;
|
||||
}
|
||||
}
|
||||
@ -5,8 +5,8 @@ import { GraphQLOutputType } from 'graphql';
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
import { RelationMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-metadata.interface';
|
||||
|
||||
import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||
import { TypeDefinitionsStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage';
|
||||
import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||
import { RelationDirection } from 'src/engine/utils/deduce-relation-direction.util';
|
||||
|
||||
import { ObjectTypeDefinitionKind } from './object-type-definition.factory';
|
||||
@ -24,13 +24,13 @@ export class RelationTypeFactory {
|
||||
relationMetadata: RelationMetadataInterface,
|
||||
relationDirection: RelationDirection,
|
||||
): GraphQLOutputType {
|
||||
let relationQqlType: GraphQLOutputType | undefined = undefined;
|
||||
let relationGqlType: GraphQLOutputType | undefined = undefined;
|
||||
|
||||
if (
|
||||
relationDirection === RelationDirection.FROM &&
|
||||
relationMetadata.relationType === RelationMetadataType.ONE_TO_MANY
|
||||
) {
|
||||
relationQqlType = this.typeDefinitionsStorage.getObjectTypeByKey(
|
||||
relationGqlType = this.typeDefinitionsStorage.getObjectTypeByKey(
|
||||
relationMetadata.toObjectMetadataId,
|
||||
ObjectTypeDefinitionKind.Connection,
|
||||
);
|
||||
@ -40,13 +40,13 @@ export class RelationTypeFactory {
|
||||
? relationMetadata.toObjectMetadataId
|
||||
: relationMetadata.fromObjectMetadataId;
|
||||
|
||||
relationQqlType = this.typeDefinitionsStorage.getObjectTypeByKey(
|
||||
relationGqlType = this.typeDefinitionsStorage.getObjectTypeByKey(
|
||||
relationObjectId,
|
||||
ObjectTypeDefinitionKind.Plain,
|
||||
);
|
||||
}
|
||||
|
||||
if (!relationQqlType) {
|
||||
if (!relationGqlType) {
|
||||
this.logger.error(
|
||||
`Could not find a relation type for ${fieldMetadata.id}`,
|
||||
{
|
||||
@ -57,6 +57,6 @@ export class RelationTypeFactory {
|
||||
throw new Error(`Could not find a relation type for ${fieldMetadata.id}`);
|
||||
}
|
||||
|
||||
return relationQqlType;
|
||||
return relationGqlType;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import chalk from 'chalk';
|
||||
|
||||
import { CompositeType } from 'src/engine/metadata-modules/field-metadata/interfaces/composite-type.interface';
|
||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||
|
||||
@ -7,6 +9,9 @@ import { CompositeEnumTypeDefinitionFactory } from 'src/engine/api/graphql/works
|
||||
import { CompositeInputTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/composite-input-type-definition.factory';
|
||||
import { CompositeObjectTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/composite-object-type-definition.factory';
|
||||
import { EnumTypeDefinitionFactory } from 'src/engine/api/graphql/workspace-schema-builder/factories/enum-type-definition.factory';
|
||||
import { ExtendObjectTypeDefinitionV2Factory } from 'src/engine/api/graphql/workspace-schema-builder/factories/extend-object-type-definition-v2.factory';
|
||||
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 { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||
|
||||
import { ConnectionTypeDefinitionFactory } from './factories/connection-type-definition.factory';
|
||||
@ -39,6 +44,8 @@ export class TypeDefinitionsGenerator {
|
||||
private readonly edgeTypeDefinitionFactory: EdgeTypeDefinitionFactory,
|
||||
private readonly connectionTypeDefinitionFactory: ConnectionTypeDefinitionFactory,
|
||||
private readonly extendObjectTypeDefinitionFactory: ExtendObjectTypeDefinitionFactory,
|
||||
private readonly extendObjectTypeDefinitionV2Factory: ExtendObjectTypeDefinitionV2Factory,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {}
|
||||
|
||||
generate(
|
||||
@ -256,18 +263,48 @@ export class TypeDefinitionsGenerator {
|
||||
this.typeDefinitionsStorage.addEnumTypes(enumTypeDefs);
|
||||
}
|
||||
|
||||
private generateExtendedObjectTypeDefs(
|
||||
private async generateExtendedObjectTypeDefs(
|
||||
objectMetadataCollection: ObjectMetadataInterface[],
|
||||
options: WorkspaceBuildSchemaOptions,
|
||||
) {
|
||||
// Generate extended object type defs only for objects that contain composite fields
|
||||
const objectMetadataCollectionWithCompositeFields =
|
||||
objectMetadataCollection.filter(objectContainsRelationField);
|
||||
const objectTypeDefs = objectMetadataCollectionWithCompositeFields.map(
|
||||
(objectMetadata) =>
|
||||
this.extendObjectTypeDefinitionFactory.create(objectMetadata, options),
|
||||
const workspaceId =
|
||||
objectMetadataCollectionWithCompositeFields[0]?.workspaceId;
|
||||
|
||||
if (!workspaceId) {
|
||||
throw new Error('Workspace ID not found');
|
||||
}
|
||||
|
||||
const isNewRelationEnabled = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IsNewRelationEnabled,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
this.typeDefinitionsStorage.addObjectTypes(objectTypeDefs);
|
||||
if (!isNewRelationEnabled) {
|
||||
const objectTypeDefs = objectMetadataCollectionWithCompositeFields.map(
|
||||
(objectMetadata) =>
|
||||
this.extendObjectTypeDefinitionFactory.create(
|
||||
objectMetadata,
|
||||
options,
|
||||
),
|
||||
);
|
||||
|
||||
this.typeDefinitionsStorage.addObjectTypes(objectTypeDefs);
|
||||
} else {
|
||||
this.logger.log(
|
||||
chalk.green('Extend object type definition with new relation fields'),
|
||||
);
|
||||
const objectTypeDefsV2 = objectMetadataCollectionWithCompositeFields.map(
|
||||
(objectMetadata) =>
|
||||
this.extendObjectTypeDefinitionV2Factory.create(
|
||||
objectMetadata,
|
||||
options,
|
||||
),
|
||||
);
|
||||
|
||||
this.typeDefinitionsStorage.addObjectTypes(objectTypeDefsV2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,16 +1,17 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||
|
||||
import { TypeDefinitionsGenerator } from './type-definitions.generator';
|
||||
import { WorkspaceGraphQLSchemaFactory } from './workspace-graphql-schema.factory';
|
||||
|
||||
import { workspaceSchemaBuilderFactories } from './factories/factories';
|
||||
import { TypeDefinitionsStorage } from './storages/type-definitions.storage';
|
||||
import { TypeMapperService } from './services/type-mapper.service';
|
||||
import { TypeDefinitionsStorage } from './storages/type-definitions.storage';
|
||||
|
||||
@Module({
|
||||
imports: [ObjectMetadataModule],
|
||||
imports: [ObjectMetadataModule, FeatureFlagModule],
|
||||
providers: [
|
||||
TypeDefinitionsStorage,
|
||||
TypeMapperService,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { makeExecutableSchema } from '@graphql-tools/schema';
|
||||
import chalk from 'chalk';
|
||||
import { GraphQLSchema, printSchema } from 'graphql';
|
||||
import { gql } from 'graphql-tag';
|
||||
|
||||
@ -13,9 +14,12 @@ import { workspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/work
|
||||
import { WorkspaceResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory';
|
||||
import { WorkspaceGraphQLSchemaFactory } from 'src/engine/api/graphql/workspace-schema-builder/workspace-graphql-schema.factory';
|
||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
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 { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceSchemaFactory {
|
||||
constructor(
|
||||
@ -25,6 +29,7 @@ export class WorkspaceSchemaFactory {
|
||||
private readonly workspaceResolverFactory: WorkspaceResolverFactory,
|
||||
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
||||
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {}
|
||||
|
||||
async createGraphQLSchema(authContext: AuthContext): Promise<GraphQLSchema> {
|
||||
@ -32,6 +37,22 @@ export class WorkspaceSchemaFactory {
|
||||
return new GraphQLSchema({});
|
||||
}
|
||||
|
||||
const cachedIsNewRelationEnabled =
|
||||
await this.workspaceCacheStorageService.getIsNewRelationEnabled(
|
||||
authContext.workspace.id,
|
||||
);
|
||||
|
||||
const isNewRelationEnabled = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IsNewRelationEnabled,
|
||||
authContext.workspace.id,
|
||||
);
|
||||
|
||||
if (isNewRelationEnabled) {
|
||||
console.log(
|
||||
chalk.yellow('🚧 New relation schema generation is enabled 🚧'),
|
||||
);
|
||||
}
|
||||
|
||||
const dataSourcesMetadata =
|
||||
await this.dataSourceService.getDataSourcesMetadataFromWorkspaceId(
|
||||
authContext.workspace.id,
|
||||
@ -50,12 +71,41 @@ export class WorkspaceSchemaFactory {
|
||||
await this.workspaceMetadataCacheService.recomputeMetadataCache({
|
||||
workspaceId: authContext.workspace.id,
|
||||
});
|
||||
|
||||
throw new GraphqlQueryRunnerException(
|
||||
'Metadata cache version not found',
|
||||
GraphqlQueryRunnerExceptionCode.METADATA_CACHE_VERSION_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: remove this after the feature flag is droped
|
||||
if (
|
||||
(isNewRelationEnabled && cachedIsNewRelationEnabled === undefined) ||
|
||||
(isNewRelationEnabled !== cachedIsNewRelationEnabled &&
|
||||
cachedIsNewRelationEnabled !== undefined)
|
||||
) {
|
||||
console.log(
|
||||
chalk.yellow('Recomputing due to new relation feature flag'),
|
||||
{
|
||||
isNewRelationEnabled,
|
||||
},
|
||||
);
|
||||
|
||||
await this.workspaceCacheStorageService.setIsNewRelationEnabled(
|
||||
authContext.workspace.id,
|
||||
isNewRelationEnabled,
|
||||
);
|
||||
|
||||
await this.workspaceMetadataCacheService.recomputeMetadataCache({
|
||||
workspaceId: authContext.workspace.id,
|
||||
});
|
||||
|
||||
throw new GraphqlQueryRunnerException(
|
||||
'Metadata cache recomputation required due to relation feature flag change',
|
||||
GraphqlQueryRunnerExceptionCode.METADATA_CACHE_FEATURE_FLAG_RECOMPUTATION_REQUIRED,
|
||||
);
|
||||
}
|
||||
|
||||
const objectMetadataMaps =
|
||||
await this.workspaceCacheStorageService.getObjectMetadataMaps(
|
||||
authContext.workspace.id,
|
||||
|
||||
@ -10,6 +10,7 @@ const mockObjectMetadata: ObjectMetadataInterface = {
|
||||
labelPlural: 'Objects',
|
||||
description: 'Test object metadata',
|
||||
targetTableName: 'test_table',
|
||||
workspaceId: '1',
|
||||
fromRelations: [],
|
||||
toRelations: [],
|
||||
fields: [],
|
||||
|
||||
@ -6,6 +6,7 @@ import { RelationMetadataInterface } from './relation-metadata.interface';
|
||||
export interface ObjectMetadataInterface {
|
||||
id: string;
|
||||
standardId?: string | null;
|
||||
workspaceId: string;
|
||||
nameSingular: string;
|
||||
namePlural: string;
|
||||
labelSingular: string;
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
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 { removeFieldMapsFromObjectMetadata } from 'src/engine/metadata-modules/utils/remove-field-maps-from-object-metadata.util';
|
||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||
|
||||
@Injectable()
|
||||
@ -95,11 +95,11 @@ export class FieldMetadataRelationService {
|
||||
}
|
||||
|
||||
return {
|
||||
sourceObjectMetadata: cleanObjectMetadata(
|
||||
sourceObjectMetadata: removeFieldMapsFromObjectMetadata(
|
||||
sourceObjectMetadata,
|
||||
) as ObjectMetadataEntity,
|
||||
sourceFieldMetadata: sourceFieldMetadata as FieldMetadataEntity,
|
||||
targetObjectMetadata: cleanObjectMetadata(
|
||||
targetObjectMetadata: removeFieldMapsFromObjectMetadata(
|
||||
targetObjectMetadata,
|
||||
) as ObjectMetadataEntity,
|
||||
targetFieldMetadata: targetFieldMetadata as FieldMetadataEntity,
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import console from 'console';
|
||||
|
||||
import { i18n } from '@lingui/core';
|
||||
import { Query, QueryOptions } from '@ptc-org/nestjs-query-core';
|
||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||
|
||||
@ -4,7 +4,7 @@ import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metad
|
||||
|
||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
|
||||
export const cleanObjectMetadata = (
|
||||
export const removeFieldMapsFromObjectMetadata = (
|
||||
objectMetadata: ObjectMetadataItemWithFieldMaps,
|
||||
): ObjectMetadataInterface =>
|
||||
omit(objectMetadata, ['fieldsById', 'fieldsByName']);
|
||||
@ -0,0 +1,9 @@
|
||||
import { FieldMetadataType } from 'twenty-shared';
|
||||
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
|
||||
export const isRelationFieldMetadata = (
|
||||
fieldMetadata: FieldMetadataInterface<'default' | FieldMetadataType.RELATION>,
|
||||
): fieldMetadata is FieldMetadataInterface<FieldMetadataType.RELATION> => {
|
||||
return fieldMetadata.type === FieldMetadataType.RELATION;
|
||||
};
|
||||
@ -5,6 +5,7 @@ import { EntitySchemaOptions } from 'typeorm';
|
||||
import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator';
|
||||
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
|
||||
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
|
||||
export enum WorkspaceCacheKeys {
|
||||
@ -12,6 +13,7 @@ export enum WorkspaceCacheKeys {
|
||||
GraphQLUsedScalarNames = 'graphql:used-scalar-names',
|
||||
GraphQLOperations = 'graphql:operations',
|
||||
ORMEntitySchemas = 'orm:entity-schemas',
|
||||
GraphQLFeatureFlag = 'graphql:feature-flag',
|
||||
MetadataObjectMetadataMaps = 'metadata:object-metadata-maps',
|
||||
MetadataObjectMetadataOngoingCachingLock = 'metadata:object-metadata-ongoing-caching-lock',
|
||||
MetadataVersion = 'metadata:workspace-metadata-version',
|
||||
@ -156,6 +158,22 @@ export class WorkspaceCacheStorageService {
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: remove this after the feature flag is droped
|
||||
setIsNewRelationEnabled(workspaceId: string, isNewRelationEnabled: boolean) {
|
||||
return this.cacheStorageService.set<boolean>(
|
||||
`${WorkspaceCacheKeys.GraphQLFeatureFlag}:${workspaceId}:${FeatureFlagKey.IsNewRelationEnabled}`,
|
||||
isNewRelationEnabled,
|
||||
TTL_INFINITE,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: remove this after the feature flag is droped
|
||||
getIsNewRelationEnabled(workspaceId: string): Promise<boolean | undefined> {
|
||||
return this.cacheStorageService.get<boolean>(
|
||||
`${WorkspaceCacheKeys.GraphQLFeatureFlag}:${workspaceId}:${FeatureFlagKey.IsNewRelationEnabled}`,
|
||||
);
|
||||
}
|
||||
|
||||
async flush(workspaceId: string, metadataVersion: number): Promise<void> {
|
||||
await this.cacheStorageService.del(
|
||||
`${WorkspaceCacheKeys.MetadataObjectMetadataMaps}:${workspaceId}:${metadataVersion}`,
|
||||
@ -172,9 +190,13 @@ export class WorkspaceCacheStorageService {
|
||||
await this.cacheStorageService.del(
|
||||
`${WorkspaceCacheKeys.ORMEntitySchemas}:${workspaceId}:${metadataVersion}`,
|
||||
);
|
||||
|
||||
await this.cacheStorageService.del(
|
||||
`${WorkspaceCacheKeys.MetadataObjectMetadataOngoingCachingLock}:${workspaceId}:${metadataVersion}`,
|
||||
);
|
||||
|
||||
// TODO: remove this after the feature flag is droped
|
||||
await this.cacheStorageService.del(
|
||||
`${FeatureFlagKey.IsNewRelationEnabled}:${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user