diff --git a/.changeset/slow-pens-compare.md b/.changeset/slow-pens-compare.md new file mode 100644 index 0000000000..158cb6f760 --- /dev/null +++ b/.changeset/slow-pens-compare.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Fix a bug where the batch fetch by ref endpoint did not work in conjunction with filtering (e.g. if authorization was enabled). diff --git a/plugins/catalog-backend/src/catalog/types.ts b/plugins/catalog-backend/src/catalog/types.ts index 62c8fad605..404b816d4f 100644 --- a/plugins/catalog-backend/src/catalog/types.ts +++ b/plugins/catalog-backend/src/catalog/types.ts @@ -98,7 +98,9 @@ export interface EntitiesBatchRequest { */ entityRefs: string[]; /** - * Any additional filters to apply in the selection of the entities. + * Any additional filters to apply in the selection of the entities. Entities + * that do not match the filter result in a null entry in the response, as if + * they did not exist. */ filter?: EntityFilter; /** diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts index 740f5b2558..275bd1c42c 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts @@ -87,6 +87,9 @@ describe('DefaultEntitiesCatalog', () => { }); } + const search = await buildEntitySearch(id, entity); + await knex('search').insert(search); + return id; } @@ -721,6 +724,51 @@ describe('DefaultEntitiesCatalog', () => { }, 60_000, ); + + it.each(databases.eachSupportedId())( + 'queries for entities by ref, including filtering, %p', + async databaseId => { + await createDatabase(databaseId); + + await addEntity( + { + apiVersion: 'a', + kind: 'k', + metadata: { name: 'one' }, + spec: {}, + relations: [], + }, + [], + ); + await addEntity( + { + apiVersion: 'a', + kind: 'k', + metadata: { name: 'two' }, + spec: { owner: 'me' }, + relations: [], + }, + [], + ); + + const catalog = new DefaultEntitiesCatalog({ + database: knex, + logger: getVoidLogger(), + stitcher, + }); + + const { items } = await catalog.entitiesBatch({ + entityRefs: ['k:default/two', 'k:default/one'], + filter: { key: 'spec.owner', values: ['me'] }, + }); + + expect(items.map(e => e && stringifyEntityRef(e))).toEqual([ + 'k:default/two', + null, + ]); + }, + 60_000, + ); }); describe('queryEntities', () => { diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts index f983d7496b..4e6109423e 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts @@ -309,17 +309,27 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog { for (const chunk of lodashChunk(request.entityRefs, 200)) { let query = this.database('final_entities') - .innerJoin('refresh_state', { - 'refresh_state.entity_id': 'final_entities.entity_id', - }) + .innerJoin( + 'refresh_state', + 'refresh_state.entity_id', + 'final_entities.entity_id', + ) .select({ entityRef: 'refresh_state.entity_ref', entity: 'final_entities.final_entity', }) .whereIn('refresh_state.entity_ref', chunk); + if (request?.filter) { - query = parseFilter(request.filter, query, this.database); + query = parseFilter( + request.filter, + query, + this.database, + false, + 'refresh_state.entity_id', + ); } + for (const row of await query) { lookup.set(row.entityRef, row.entity ? JSON.parse(row.entity) : null); }