From 70745c573e5c943fdabd419a172da71b0ed2f821 Mon Sep 17 00:00:00 2001 From: Dakota Wandro Date: Mon, 3 Nov 2025 09:59:14 -0600 Subject: [PATCH 1/2] fix(catalog-backend-incremental-ingestion): correctly handle count queries returning strings Signed-off-by: Dakota Wandro --- .changeset/warm-shrimps-clap.md | 5 +++ ...ncrementalIngestionDatabaseManager.test.ts | 36 +++++++++++++++++++ .../IncrementalIngestionDatabaseManager.ts | 2 +- 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 .changeset/warm-shrimps-clap.md diff --git a/.changeset/warm-shrimps-clap.md b/.changeset/warm-shrimps-clap.md new file mode 100644 index 0000000000..0386df05de --- /dev/null +++ b/.changeset/warm-shrimps-clap.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch +--- + +Correctly handle entity removal computation when DB count query returns string diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/database/IncrementalIngestionDatabaseManager.test.ts b/plugins/catalog-backend-module-incremental-ingestion/src/database/IncrementalIngestionDatabaseManager.test.ts index 2523720a31..b744726b36 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/src/database/IncrementalIngestionDatabaseManager.test.ts +++ b/plugins/catalog-backend-module-incremental-ingestion/src/database/IncrementalIngestionDatabaseManager.test.ts @@ -76,4 +76,40 @@ describe('IncrementalIngestionDatabaseManager', () => { ]); }, ); + + it.each(databases.eachSupportedId())( + 'computeRemoved correctly sums total count from count query, %p', + async databaseId => { + const knex = await databases.init(databaseId); + await knex.migrate.latest({ directory: migrationsDir }); + + const manager = new IncrementalIngestionDatabaseManager({ client: knex }); + const { ingestionId } = (await manager.createProviderIngestionRecord( + 'testProvider', + ))!; + + const markId = uuid(); + await manager.createMark({ + record: { + id: markId, + ingestion_id: ingestionId, + sequence: 1, + cursor: { data: 1 }, + }, + }); + + // Create multiple mark entities + await manager.createMarkEntities(markId, [ + { entity: { kind: 'Component', namespace: 'default', name: 'comp1' } }, + { entity: { kind: 'Component', namespace: 'default', name: 'comp2' } }, + { entity: { kind: 'Component', namespace: 'default', name: 'comp3' } }, + ]); + + const result = await manager.computeRemoved('testProvider', ingestionId); + + // On PostgreSQL, count queries return strings, so total should be 3 not NaN or string concatenation + expect(result.total).toBe(3); + expect(typeof result.total).toBe('number'); + }, + ); }); diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/database/IncrementalIngestionDatabaseManager.ts b/plugins/catalog-backend-module-incremental-ingestion/src/database/IncrementalIngestionDatabaseManager.ts index 818a93fc6d..d5f6487c5d 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/src/database/IncrementalIngestionDatabaseManager.ts +++ b/plugins/catalog-backend-module-incremental-ingestion/src/database/IncrementalIngestionDatabaseManager.ts @@ -322,7 +322,7 @@ export class IncrementalIngestionDatabaseManager { .join('ingestions', 'ingestions.id', 'ingestion_marks.ingestion_id') .where('ingestions.id', ingestionId); - const total = count.reduce((acc, cur) => acc + (cur.total as number), 0); + const total = count.reduce((acc, cur) => acc + Number(cur.total), 0); const removed: { entityRef: string }[] = []; if (previousIngestion) { From 37fc9fa0ad612a1fd5b0767b1773e836ac40708d Mon Sep 17 00:00:00 2001 From: Dakota Wandro Date: Mon, 3 Nov 2025 11:26:42 -0600 Subject: [PATCH 2/2] test(catalog-backend-incremental-ingestion): update entites to match DeferredEntity type Signed-off-by: Dakota Wandro --- .../IncrementalIngestionDatabaseManager.test.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/database/IncrementalIngestionDatabaseManager.test.ts b/plugins/catalog-backend-module-incremental-ingestion/src/database/IncrementalIngestionDatabaseManager.test.ts index b744726b36..03a0488cb0 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/src/database/IncrementalIngestionDatabaseManager.test.ts +++ b/plugins/catalog-backend-module-incremental-ingestion/src/database/IncrementalIngestionDatabaseManager.test.ts @@ -17,6 +17,7 @@ import { TestDatabases } from '@backstage/backend-test-utils'; import { IncrementalIngestionDatabaseManager } from './IncrementalIngestionDatabaseManager'; import { v4 as uuid } from 'uuid'; +import { DeferredEntity } from '@backstage/plugin-catalog-node'; const migrationsDir = `${__dirname}/../../migrations`; @@ -98,11 +99,19 @@ describe('IncrementalIngestionDatabaseManager', () => { }, }); + const makeEntity = (name: string): DeferredEntity => ({ + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { namespace: 'default', name }, + }, + }); + // Create multiple mark entities await manager.createMarkEntities(markId, [ - { entity: { kind: 'Component', namespace: 'default', name: 'comp1' } }, - { entity: { kind: 'Component', namespace: 'default', name: 'comp2' } }, - { entity: { kind: 'Component', namespace: 'default', name: 'comp3' } }, + makeEntity('comp1'), + makeEntity('comp2'), + makeEntity('comp3'), ]); const result = await manager.computeRemoved('testProvider', ingestionId);