From e4aab10f8174151312cb195e084731e33a8b1e93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 2 Dec 2024 14:12:45 +0100 Subject: [PATCH] fix nulls in by-query MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/gold-stingrays-cross.md | 5 +++ .../service/DefaultEntitiesCatalog.test.ts | 45 +++++++++++++++++++ .../src/service/DefaultEntitiesCatalog.ts | 12 ++--- 3 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 .changeset/gold-stingrays-cross.md diff --git a/.changeset/gold-stingrays-cross.md b/.changeset/gold-stingrays-cross.md new file mode 100644 index 0000000000..f2a8da99b3 --- /dev/null +++ b/.changeset/gold-stingrays-cross.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Fix a bug where sometimes the `by-query` endpoint could return nulls for entities that were not yet stitched. diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts index 08ba11c59d..418f503739 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts @@ -1797,6 +1797,51 @@ describe('DefaultEntitiesCatalog', () => { ).resolves.toEqual(['BB', 'CC', 'AA']); // 'AA' has no title, ends up last }, ); + + it.each(databases.eachSupportedId())( + 'should silently skip over entities that are not yet stitched, %p', + async databaseId => { + await createDatabase(databaseId); + + const entity1 = entityFrom('AA', { uid: 'id1' }); + const entity2 = entityFrom('BB', { uid: 'id2' }); + await Promise.all([ + addEntityToSearch(entity1), + addEntityToSearch(entity2), + ]); + + const catalog = new DefaultEntitiesCatalog({ + database: knex, + logger: mockServices.logger.mock(), + stitcher, + }); + + await expect( + catalog + .queryEntities({ + orderFields: [{ field: 'metadata.uid', order: 'asc' }], + limit: 10, + credentials: mockCredentials.none(), + }) + .then(r => r.items.map(e => e.metadata.name)), + ).resolves.toEqual(['AA', 'BB']); + + // simulate a situation where stitching is not yet complete + await knex('final_entities') + .update({ final_entity: null }) + .where({ entity_ref: stringifyEntityRef(entity1) }); + + await expect( + catalog + .queryEntities({ + orderFields: [{ field: 'metadata.uid', order: 'asc' }], + limit: 10, + credentials: mockCredentials.none(), + }) + .then(r => r.items.map(e => e.metadata.name)), + ).resolves.toEqual(['BB']); + }, + ); }); describe('removeEntityByUid', () => { diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts index 74d08d3ad7..c8afe8124f 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts @@ -379,11 +379,13 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog { const [prevItemOrderFieldValue, prevItemUid] = cursor.orderFieldValues || []; - const dbQuery = db('final_entities').leftOuterJoin('search', qb => - qb - .on('search.entity_id', 'final_entities.entity_id') - .andOnVal('search.key', sortField.field), - ); + const dbQuery = db('final_entities') + .leftOuterJoin('search', qb => + qb + .on('search.entity_id', 'final_entities.entity_id') + .andOnVal('search.key', sortField.field), + ) + .whereNotNull('final_entities.final_entity'); if (cursor.filter) { parseFilter(