Use search instead of findMany in relation pickers (#7798)

First step of #https://github.com/twentyhq/twenty/issues/3298.
Here we update the search endpoint to allow for a filter argument, which
we currently use in the relation pickers to restrict or exclude ids from
search.
In a future PR we will try to simplify the search logic in the FE
This commit is contained in:
Marie
2024-10-18 14:50:04 +02:00
committed by GitHub
parent 8cadcdf577
commit 6fef125965
15 changed files with 123 additions and 125 deletions

View File

@ -4,16 +4,19 @@ import { ResolverService } from 'src/engine/api/graphql/graphql-query-runner/int
import {
Record as IRecord,
OrderByDirection,
RecordFilter,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/record.interface';
import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface';
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { SearchResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { QUERY_MAX_RECORDS } from 'src/engine/api/graphql/graphql-query-runner/constants/query-max-records.constant';
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/constants/search-vector-field.constants';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { isDefined } from 'src/utils/is-defined';
@Injectable()
export class GraphqlQuerySearchResolverService
@ -24,11 +27,19 @@ export class GraphqlQuerySearchResolverService
private readonly featureFlagService: FeatureFlagService,
) {}
async resolve<ObjectRecord extends IRecord = IRecord>(
async resolve<
ObjectRecord extends IRecord = IRecord,
Filter extends RecordFilter = RecordFilter,
>(
args: SearchResolverArgs,
options: WorkspaceQueryRunnerOptions,
): Promise<IConnection<ObjectRecord>> {
const { authContext, objectMetadataItem, objectMetadataMap } = options;
const {
authContext,
objectMetadataItem,
objectMetadataMapItem,
objectMetadataMap,
} = options;
const repository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
@ -39,7 +50,7 @@ export class GraphqlQuerySearchResolverService
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMap);
if (!args.searchInput) {
if (!isDefined(args.searchInput)) {
return typeORMObjectRecordsParser.createConnection({
objectRecords: [],
objectName: objectMetadataItem.nameSingular,
@ -54,11 +65,27 @@ export class GraphqlQuerySearchResolverService
const limit = args?.limit ?? QUERY_MAX_RECORDS;
const resultsWithTsVector = (await repository
.createQueryBuilder()
.where(`"${SEARCH_VECTOR_FIELD.name}" @@ to_tsquery(:searchTerms)`, {
searchTerms,
})
const queryBuilder = repository.createQueryBuilder(
objectMetadataItem.nameSingular,
);
const graphqlQueryParser = new GraphqlQueryParser(
objectMetadataMapItem.fields,
objectMetadataMap,
);
const queryBuilderWithFilter = graphqlQueryParser.applyFilterToBuilder(
queryBuilder,
objectMetadataMapItem.nameSingular,
args.filter ?? ({} as Filter),
);
const resultsWithTsVector = (await queryBuilderWithFilter
.andWhere(
searchTerms === ''
? `"${SEARCH_VECTOR_FIELD.name}" IS NOT NULL`
: `"${SEARCH_VECTOR_FIELD.name}" @@ to_tsquery(:searchTerms)`,
searchTerms === '' ? {} : { searchTerms },
)
.orderBy(
`ts_rank("${SEARCH_VECTOR_FIELD.name}", to_tsquery(:searchTerms))`,
'DESC',
@ -84,6 +111,9 @@ export class GraphqlQuerySearchResolverService
}
private formatSearchTerms(searchTerm: string) {
if (searchTerm === '') {
return '';
}
const words = searchTerm.trim().split(/\s+/);
const formattedWords = words.map((word) => {
const escapedWord = word.replace(/[\\:'&|!()]/g, '\\$&');

View File

@ -48,8 +48,11 @@ export interface FindDuplicatesResolverArgs<
data?: Data[];
}
export interface SearchResolverArgs {
export interface SearchResolverArgs<
Filter extends RecordFilter = RecordFilter,
> {
searchInput?: string;
filter?: Filter;
limit?: number;
}

View File

@ -147,6 +147,10 @@ export const getResolverArgs = (
type: GraphQLInt,
isNullable: true,
},
filter: {
kind: InputTypeDefinitionKind.Filter,
isNullable: true,
},
};
default:
throw new Error(`Unknown resolver type: ${type}`);