From ab31d8a7fdbd7bf173410d414cc33fc8771764fa Mon Sep 17 00:00:00 2001 From: Aramis Sennyey Date: Thu, 16 Mar 2023 17:17:41 -0400 Subject: [PATCH] Support full text searches across multiple search keys, not just the sort order key. Signed-off-by: Aramis Sennyey --- .../service/DefaultEntitiesCatalog.test.ts | 58 +++++++++++++++++++ .../src/service/DefaultEntitiesCatalog.ts | 16 +++-- 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts index 275bd1c42c..b178a591de 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts @@ -1119,6 +1119,64 @@ describe('DefaultEntitiesCatalog', () => { }, ); + it.each(databases.eachSupportedId())( + 'should filter the text results when sortOrder is not provided, %p', + async databaseId => { + await createDatabase(databaseId); + + const names = ['lion', 'cat', 'atcatss', 'dog', 'dogcat', 'aa', 's']; + const entities: Entity[] = names.map(name => + // Need a stable search since default filtering is by uid, and those get generated on the fly + // during the test case. + entityFrom(name, { uid: name }), + ); + + const notFoundEntities: Entity[] = [ + { + apiVersion: 'a', + kind: 'k', + metadata: { name: 'something' }, + spec: {}, + }, + { + apiVersion: 'a', + kind: 'k', + metadata: { name: 'something else' }, + spec: {}, + }, + ]; + + await Promise.all( + entities.concat(notFoundEntities).map(e => addEntityToSearch(e)), + ); + + const catalog = new DefaultEntitiesCatalog({ + database: knex, + logger: getVoidLogger(), + stitcher, + }); + + const filter = { + key: 'spec.should_include_this', + }; + + const request: QueryEntitiesInitialRequest = { + filter, + limit: 100, + fullTextFilter: { term: 'cAt ', fields: ['metadata.name'] }, + }; + const response = await catalog.queryEntities(request); + expect(response.items).toEqual([ + entityFrom('atcatss', { uid: 'atcatss' }), + entityFrom('cat', { uid: 'cat' }), + entityFrom('dogcat', { uid: 'dogcat' }), + ]); + expect(response.pageInfo.nextCursor).toBeUndefined(); + expect(response.pageInfo.prevCursor).toBeUndefined(); + expect(response.totalItems).toBe(3); + }, + ); + it.each(databases.eachSupportedId())( 'should include totalItems and empty entities in the response in case limit is zero, %p', async databaseId => { diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts index 4e6109423e..b1c42ab353 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts @@ -51,6 +51,7 @@ import { } from '../database/tables'; import { Stitcher } from '../stitching/Stitcher'; +import { parseFullTextFilterFields } from './request/parseFullTextFilterFields'; import { isQueryEntitiesCursorRequest, @@ -382,11 +383,18 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog { } const normalizedFullTextFilterTerm = cursor.fullTextFilter?.term?.trim(); + const textFilterFields = cursor.fullTextFilter?.fields ?? [sortField.field]; if (normalizedFullTextFilterTerm) { - dbQuery.andWhereRaw( - 'value like ?', - `%${normalizedFullTextFilterTerm.toLocaleLowerCase('en-US')}%`, - ); + const matchQuery = db('search') + .select('search.entity_id') + .whereIn('key', textFilterFields) + .andWhere(function keyFilter() { + this.andWhereRaw( + 'value like ?', + `%${normalizedFullTextFilterTerm.toLocaleLowerCase('en-US')}%`, + ); + }); + dbQuery.andWhere('search.entity_id', 'in', matchQuery); } const countQuery = dbQuery.clone();