diff --git a/.changeset/sixty-sheep-drive.md b/.changeset/sixty-sheep-drive.md new file mode 100644 index 0000000000..2e947b30f9 --- /dev/null +++ b/.changeset/sixty-sheep-drive.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Fixed bug when searching an entity by `spec.profile.displayName` in the catalog on the frontend. Text filter fields were not applied correctly to the database query resulting in empty results. diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts index d2ac0871a9..91406a9b08 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts @@ -1183,7 +1183,6 @@ describe('DefaultEntitiesCatalog', () => { const request: QueryEntitiesInitialRequest = { filter, limit: 100, - orderFields: [{ field: 'metadata.name', order: 'asc' }], fullTextFilter: { term: 'cAt ' }, credentials: mockCredentials.none(), @@ -1200,6 +1199,60 @@ describe('DefaultEntitiesCatalog', () => { }, ); + it.each(databases.eachSupportedId())( + 'should filter the results when query is provided with fullTextFilter for camelCase fields, %p', + async databaseId => { + await createDatabase(databaseId); + + const entities: Entity[] = [ + { + apiVersion: 'a', + kind: 'k', + metadata: { + name: 'camelCase', + }, + spec: { + shouldSearchCamelCase: 'searched', + }, + }, + ]; + + const notFoundEntities: Entity[] = [ + { + apiVersion: 'a', + kind: 'k', + metadata: { name: 'something' }, + spec: {}, + }, + ]; + + await Promise.all( + entities.concat(notFoundEntities).map(e => addEntityToSearch(e)), + ); + + const catalog = new DefaultEntitiesCatalog({ + database: knex, + logger: mockServices.logger.mock(), + stitcher, + }); + + const request: QueryEntitiesInitialRequest = { + limit: 100, + orderFields: [{ field: 'metadata.name', order: 'asc' }], + fullTextFilter: { + term: 'sear', + fields: ['spec.shouldSearchCamelCase'], + }, + credentials: mockCredentials.none(), + }; + const response = await catalog.queryEntities(request); + expect(response.items).toEqual(entities); + expect(response.pageInfo.nextCursor).toBeUndefined(); + expect(response.pageInfo.prevCursor).toBeUndefined(); + expect(response.totalItems).toBe(1); + }, + ); + it.each(databases.eachSupportedId())( 'should filter the text results when sortOrder is not provided, %p', async databaseId => { diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts index d1cabbf575..e4b4fb001d 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts @@ -411,7 +411,11 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog { } else { const matchQuery = db('search') .select('search.entity_id') - .whereIn('key', textFilterFields) + // textFilterFields must be lowercased to match searchable keys in database, i.e. spec.profile.displayName -> spec.profile.displayname + .whereIn( + 'key', + textFilterFields.map(field => field.toLocaleLowerCase('en-US')), + ) .andWhere(function keyFilter() { this.andWhereRaw( 'value like ?',