Steps to test

1. Run metadata migrations
2. Run sync-metadata on your workspace
3. Enable the following feature flags: 
IS_SEARCH_ENABLED
IS_QUERY_RUNNER_TWENTY_ORM_ENABLED
IS_WORKSPACE_MIGRATED_FOR_SEARCH
4. Type Cmd + K and search anything
This commit is contained in:
Marie
2024-10-03 17:18:49 +02:00
committed by GitHub
parent 4c250dd811
commit 5f9435c718
71 changed files with 1517 additions and 209 deletions

View File

@ -1,14 +1,12 @@
import { Inject, Injectable, forwardRef } from '@nestjs/common';
import { GraphQLInputFieldConfigMap, GraphQLInputObjectType } from 'graphql';
import { GraphQLInputObjectType } 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 { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service';
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
import { generateFields } from 'src/engine/api/graphql/workspace-schema-builder/utils/generate-fields.utils';
import { pascalCase } from 'src/utils/pascal-case';
import { InputTypeFactory } from './input-type.factory';
@ -55,7 +53,12 @@ export class InputTypeDefinitionFactory {
});
return {
...this.generateFields(objectMetadata, kind, options),
...generateFields(
objectMetadata,
kind,
options,
this.inputTypeFactory,
),
and: {
type: andOrType,
},
@ -73,7 +76,12 @@ export class InputTypeDefinitionFactory {
* Other input types are generated with fields only
*/
default:
return this.generateFields(objectMetadata, kind, options);
return generateFields(
objectMetadata,
kind,
options,
this.inputTypeFactory,
);
}
},
});
@ -84,46 +92,4 @@ export class InputTypeDefinitionFactory {
type: inputType,
};
}
private generateFields(
objectMetadata: ObjectMetadataInterface,
kind: InputTypeDefinitionKind,
options: WorkspaceBuildSchemaOptions,
): GraphQLInputFieldConfigMap {
const fields: GraphQLInputFieldConfigMap = {};
for (const fieldMetadata of objectMetadata.fields) {
// Relation field types are generated during extension of object type definition
if (isRelationFieldMetadataType(fieldMetadata.type)) {
continue;
}
const target = isCompositeFieldMetadataType(fieldMetadata.type)
? fieldMetadata.type.toString()
: fieldMetadata.id;
const isIdField = fieldMetadata.name === 'id';
const type = this.inputTypeFactory.create(
target,
fieldMetadata.type,
kind,
options,
{
nullable: fieldMetadata.isNullable,
defaultValue: fieldMetadata.defaultValue,
isArray: fieldMetadata.type === FieldMetadataType.MULTI_SELECT,
settings: fieldMetadata.settings,
isIdField,
},
);
fields[fieldMetadata.name] = {
type,
description: fieldMetadata.description,
};
}
return fields;
}
}

View File

@ -1,14 +1,12 @@
import { Injectable } from '@nestjs/common';
import { GraphQLFieldConfigMap, GraphQLObjectType } from 'graphql';
import { 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 { generateFields } from 'src/engine/api/graphql/workspace-schema-builder/utils/generate-fields.utils';
import { pascalCase } from 'src/utils/pascal-case';
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { OutputTypeFactory } from './output-type.factory';
@ -39,48 +37,13 @@ export class ObjectTypeDefinitionFactory {
type: new GraphQLObjectType({
name: `${pascalCase(objectMetadata.nameSingular)}${kind.toString()}`,
description: objectMetadata.description,
fields: this.generateFields(objectMetadata, kind, options),
fields: generateFields(
objectMetadata,
kind,
options,
this.outputTypeFactory,
),
}),
};
}
private generateFields(
objectMetadata: ObjectMetadataInterface,
kind: ObjectTypeDefinitionKind,
options: WorkspaceBuildSchemaOptions,
): GraphQLFieldConfigMap<any, any> {
const fields: GraphQLFieldConfigMap<any, any> = {};
for (const fieldMetadata of objectMetadata.fields) {
// Relation field types are generated during extension of object type definition
if (isRelationFieldMetadataType(fieldMetadata.type)) {
continue;
}
const target = isCompositeFieldMetadataType(fieldMetadata.type)
? fieldMetadata.type.toString()
: fieldMetadata.id;
const type = this.outputTypeFactory.create(
target,
fieldMetadata.type,
kind,
options,
{
nullable: fieldMetadata.isNullable,
isArray: fieldMetadata.type === FieldMetadataType.MULTI_SELECT,
settings: fieldMetadata.settings,
// Scalar type is already defined in the entity itself.
isIdField: false,
},
);
fields[fieldMetadata.name] = {
type,
description: fieldMetadata.description,
};
}
return fields;
}
}

View File

@ -74,9 +74,7 @@ export class RootTypeFactory {
const args = getResolverArgs(methodName);
const objectType = this.typeDefinitionsStorage.getObjectTypeByKey(
objectMetadata.id,
['findMany', 'findDuplicates'].includes(methodName)
? ObjectTypeDefinitionKind.Connection
: ObjectTypeDefinitionKind.Plain,
this.getObjectTypeDefinitionKindByMethodName(methodName),
);
const argsType = this.argsFactory.create(
{
@ -124,4 +122,17 @@ export class RootTypeFactory {
return fieldConfigMap;
}
private getObjectTypeDefinitionKindByMethodName(
methodName: WorkspaceResolverBuilderMethodNames,
): ObjectTypeDefinitionKind {
switch (methodName) {
case 'findMany':
case 'findDuplicates':
case 'search':
return ObjectTypeDefinitionKind.Connection;
default:
return ObjectTypeDefinitionKind.Plain;
}
}
}

View File

@ -0,0 +1,100 @@
import {
GraphQLFieldConfigMap,
GraphQLInputFieldConfigMap,
GraphQLInputType,
GraphQLOutputType,
} 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 { InputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory';
import { ObjectTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/object-type-definition.factory';
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
type TypeFactory<T extends InputTypeDefinitionKind | ObjectTypeDefinitionKind> =
{
create: (
target: string,
fieldType: FieldMetadataType,
kind: T,
options: WorkspaceBuildSchemaOptions,
additionalOptions: {
nullable?: boolean;
defaultValue?: any;
isArray: boolean;
settings: any;
isIdField: boolean;
},
) => T extends InputTypeDefinitionKind
? GraphQLInputType
: GraphQLOutputType;
};
export const generateFields = <
T extends InputTypeDefinitionKind | ObjectTypeDefinitionKind,
>(
objectMetadata: ObjectMetadataInterface,
kind: T,
options: WorkspaceBuildSchemaOptions,
typeFactory: TypeFactory<T>,
): T extends InputTypeDefinitionKind
? GraphQLInputFieldConfigMap
: GraphQLFieldConfigMap<any, any> => {
const fields = {};
for (const fieldMetadata of objectMetadata.fields) {
if (
isRelationFieldMetadataType(fieldMetadata.type) ||
fieldMetadata.type === FieldMetadataType.TS_VECTOR
) {
continue;
}
const target = isCompositeFieldMetadataType(fieldMetadata.type)
? fieldMetadata.type.toString()
: fieldMetadata.id;
const typeFactoryOptions = isInputTypeDefinitionKind(kind)
? {
nullable: fieldMetadata.isNullable,
defaultValue: fieldMetadata.defaultValue,
isArray: fieldMetadata.type === FieldMetadataType.MULTI_SELECT,
settings: fieldMetadata.settings,
isIdField: fieldMetadata.name === 'id',
}
: {
nullable: fieldMetadata.isNullable,
isArray: fieldMetadata.type === FieldMetadataType.MULTI_SELECT,
settings: fieldMetadata.settings,
// Scalar type is already defined in the entity itself.
isIdField: false,
};
const type = typeFactory.create(
target,
fieldMetadata.type,
kind,
options,
typeFactoryOptions,
);
fields[fieldMetadata.name] = {
type,
description: fieldMetadata.description,
};
}
return fields;
};
// Type guard
const isInputTypeDefinitionKind = (
kind: InputTypeDefinitionKind | ObjectTypeDefinitionKind,
): kind is InputTypeDefinitionKind => {
return Object.values(InputTypeDefinitionKind).includes(
kind as InputTypeDefinitionKind,
);
};

View File

@ -137,6 +137,17 @@ export const getResolverArgs = (
isNullable: false,
},
};
case 'search':
return {
searchInput: {
type: GraphQLString,
isNullable: true,
},
limit: {
type: GraphQLInt,
isNullable: true,
},
};
default:
throw new Error(`Unknown resolver type: ${type}`);
}