From 6a566c3693b82a499e4ed53ca4ff2166c5b9824b Mon Sep 17 00:00:00 2001 From: Rabson Date: Sun, 7 Jun 2026 08:53:56 +0800 Subject: [PATCH] fix(diagnostics): derive Mongo totalSize when dbStats omits it MongoDB Atlas shared/free tiers return a legacy dbStats shape that omits `totalSize` (while still returning storageSize/indexSize). diagnosticService passed that undefined straight through, so the Diagnostics UI crashed with "Expected a finite number, got undefined". Derive totalSize (storageSize + indexSize) at the DB and per-collection level so it always satisfies the non-optional `totalSize: number` type. Confirmed it is not a version removal (stock mongo:8 returns totalSize) nor a permissions issue (missing even with atlasAdmin). Adds unit tests for the derivation. --- .../src/service/business/diagnosticService.ts | 8 ++--- .../unit/services/diagnosticService.test.ts | 36 +++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/server/src/service/business/diagnosticService.ts b/server/src/service/business/diagnosticService.ts index 2d9968240..c5debf0e1 100644 --- a/server/src/service/business/diagnosticService.ts +++ b/server/src/service/business/diagnosticService.ts @@ -87,9 +87,9 @@ export class DiagnosticService implements IDiagnosticService { const entry: CollectionDiagnostics = { name: collection.collectionName, documentCount, - storageSize: stats.storageSize, - totalIndexSize: stats.totalIndexSize, - totalSize: stats.totalSize, + storageSize: stats.storageSize ?? 0, + totalIndexSize: stats.totalIndexSize ?? 0, + totalSize: stats.totalSize ?? (stats.storageSize ?? 0) + (stats.totalIndexSize ?? 0), }; if (stats.timeseries?.bucketCount !== undefined) { entry.bucketCount = stats.timeseries.bucketCount; @@ -103,7 +103,7 @@ export class DiagnosticService implements IDiagnosticService { host: mongo.connection.host, port: mongo.connection.port, dbName: mongo.connection.name, - totalSize: dbStats.totalSize, + totalSize: dbStats.totalSize ?? (dbStats.storageSize ?? 0) + (dbStats.indexSize ?? 0), collections, }; }; diff --git a/server/test/unit/services/diagnosticService.test.ts b/server/test/unit/services/diagnosticService.test.ts index ae57613a1..402c0c527 100644 --- a/server/test/unit/services/diagnosticService.test.ts +++ b/server/test/unit/services/diagnosticService.test.ts @@ -147,6 +147,42 @@ describe("DiagnosticService", () => { }); }); + it("derives mongoStats.totalSize from storageSize + indexSize when the server omits it (e.g. Atlas shared tier)", async () => { + const db = makeDb({ + collections: [{ collectionName: "monitors" }], + dbStats: { storageSize: 376832, indexSize: 1155072 }, + }); + const service = new DiagnosticService(db); + const promise = service.getSystemStats(); + jest.advanceTimersByTime(1000); + await jest.advanceTimersByTimeAsync(10); + const result = await promise; + + expect(result.mongoStats.totalSize).toBe(376832 + 1155072); + expect(Number.isFinite(result.mongoStats.totalSize)).toBe(true); + }); + + it("derives collection totalSize and defaults missing size fields to 0 when collStats omits them", async () => { + const db = makeDb({ + collections: [{ collectionName: "checks" }], + collStats: { storageSize: 200 }, + }); + const service = new DiagnosticService(db); + const promise = service.getSystemStats(); + jest.advanceTimersByTime(1000); + await jest.advanceTimersByTimeAsync(10); + const result = await promise; + + const coll = result.mongoStats.collections[0]; + expect(coll).toMatchObject({ + name: "checks", + storageSize: 200, + totalIndexSize: 0, + totalSize: 200, // storageSize (200) + totalIndexSize (0) + }); + expect(Number.isFinite(coll?.totalSize)).toBe(true); + }); + it("includes bucketCount on timeseries collections, omits it elsewhere", async () => { const db = makeDb({ collections: [{ collectionName: "checks" }],