From ec8489b273b7e162ad934ebc33db4abc5ffaddbf Mon Sep 17 00:00:00 2001 From: Ephraim Duncan <55143799+ephraimduncan@users.noreply.github.com> Date: Thu, 10 Sep 2026 10:55:57 +0000 Subject: [PATCH] fix: count downtime from the incident start (#2668) --- .../status-timeline/__tests__/events.test.ts | 58 +++++++++++++++++++ .../services/src/status-timeline/events.ts | 9 +-- 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/packages/services/src/status-timeline/__tests__/events.test.ts b/packages/services/src/status-timeline/__tests__/events.test.ts index 9797f840..afe245ae 100644 --- a/packages/services/src/status-timeline/__tests__/events.test.ts +++ b/packages/services/src/status-timeline/__tests__/events.test.ts @@ -44,6 +44,64 @@ function makeBucket(overrides: Partial = {}): StatusData { }; } +describe("getEvents incident timestamps", () => { + const now = Date.now(); + const incident = { + id: 1, + title: "", + summary: "", + status: "resolved" as const, + monitorId: 1, + workspaceId: 1, + startedAt: new Date(now - 3_600_000), + acknowledgedAt: null, + acknowledgedBy: null, + resolvedAt: new Date(now), + resolvedBy: null, + incidentScreenshotUrl: null, + recoveryScreenshotUrl: null, + autoResolved: true, + createdAt: new Date(now - 1_800_000), + updatedAt: new Date(now), + }; + + test("counts the full outage when persistence is delayed or undated", () => { + for (const createdAt of [incident.createdAt, null]) { + const events = getEvents({ + maintenances: [], + incidents: [{ ...incident, createdAt }], + reports: [], + monitorId: 1, + }); + + expect(events[0]?.from).toEqual(incident.startedAt); + expect( + durationDowntimeMs(events, { + start: now - 86_400_000, + end: now, + now, + }), + ).toBe(3_600_000); + } + }); + + test("filters incidents resolved before the window even when persistence is recent", () => { + const events = getEvents({ + maintenances: [], + incidents: [ + { + ...incident, + startedAt: new Date(now - 47 * 86_400_000), + resolvedAt: new Date(now - 46 * 86_400_000), + }, + ], + reports: [], + }); + + expect(events).toEqual([]); + }); +}); + describe("getEvents lookback", () => { test("keeps overlapping intervals and clips incident downtime to the window", () => { const time = new FakeTime(day("2026-03-01")); diff --git a/packages/services/src/status-timeline/events.ts b/packages/services/src/status-timeline/events.ts index 2b0f0377..98e6a000 100644 --- a/packages/services/src/status-timeline/events.ts +++ b/packages/services/src/status-timeline/events.ts @@ -311,16 +311,11 @@ export function getEvents({ monitorId ? incident.monitorId === monitorId : true, ) .forEach((incident) => { - if ( - !incident.createdAt || - (incident.resolvedAt && incident.resolvedAt < pastThreshold) - ) { - return; - } + if (incident.resolvedAt && incident.resolvedAt < pastThreshold) return; events.push({ id: incident.id, name: "Downtime", - from: incident.createdAt, + from: incident.startedAt, to: incident.resolvedAt, type: "incident", status: "error" as const, -- 2.51.2