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:
@ -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, '\\$&');
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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}`);
|
||||
|
||||
Reference in New Issue
Block a user