Merge pull request #3734 from bluewave-labs/fix/maintenance-window-exit

fix: change status on exit maintenance window
This commit is contained in:
Alexander Holliday authored and GitHub committed 2026-06-19 09:54:05 -07:00
commit 16036f421b
4 files changed
+54 -4

No files matched your search

+2 -2
View File
@@ -232,10 +232,10 @@ export class StatusService implements IStatusService {
// Build the status patch — computed against the projected window
const patch: Partial<Monitor> = {};
// Resolve "initializing" up-front, incidents should be created on initialization && down
// Resolve "initializing" and "maintenance" up front, incidents should be created on initialization && down
let newStatus: MonitorStatus = monitor.status;
let statusChanged = false;
if (monitor.status === "initializing") {
if (monitor.status === "initializing" || monitor.status === "maintenance") {
newStatus = status === true ? "up" : "down";
patch.status = newStatus;
statusChanged = newStatus === "down";
+2 -1
View File
@@ -49,7 +49,8 @@ export class CheckProducer implements ICheckProducer {
method: "produce",
});
if (monitor.status !== "maintenance") {
await this.monitorsRepository.updateById(monitor.id, monitor.teamId, { status: "maintenance" });
// Clear the status window to avoid incidents being created on next check
await this.monitorsRepository.updateById(monitor.id, monitor.teamId, { status: "maintenance", statusWindow: [] });
}
return null;
}
@@ -67,7 +67,7 @@ describe("CheckPipeline", () => {
expect(result).toBeNull();
expect(defaults.networkService.requestStatus).not.toHaveBeenCalled();
expect(defaults.monitorsRepository.updateById).toHaveBeenCalledWith("m1", "team", { status: "maintenance" });
expect(defaults.monitorsRepository.updateById).toHaveBeenCalledWith("m1", "team", { status: "maintenance", statusWindow: [] });
});
it("does not re-write status when already in maintenance", async () => {
@@ -557,6 +557,55 @@ describe("StatusService", () => {
expect(result.prevStatus).toBe("initializing");
});
it("leaves 'maintenance' for 'up' on a passing check after the window closes (empty statusWindow)", async () => {
// On entry to a maintenance window the producer clears statusWindow, so the first
// check after the window closes sees an empty window and lands in the warmup
// early-return branch. A passing check must move the monitor out of 'maintenance'
// to 'up' without flagging a status change (no incident).
const monitor = makeMonitor({ statusWindow: [], statusWindowSize: 5, status: "maintenance" });
const { service, monitorsRepository } = createService();
(monitorsRepository.findById as jest.Mock).mockResolvedValue(monitor);
const result = await service.updateMonitorStatus(makeStatusResponse({ status: true }), makeCheck({ status: true }));
expect(result.statusChanged).toBe(false);
expect(result.monitor.status).toBe("up");
expect(result.prevStatus).toBe("maintenance");
});
it("leaves 'maintenance' for 'down' on a failing check after the window closes (empty statusWindow)", async () => {
// Mirror of the recovery case: a failing first check after the window closes must
// flip the monitor straight to 'down' and surface statusChanged so an incident opens,
// rather than leave it stuck in 'maintenance'.
const monitor = makeMonitor({ statusWindow: [], statusWindowSize: 5, status: "maintenance" });
const { service, monitorsRepository } = createService();
(monitorsRepository.findById as jest.Mock).mockResolvedValue(monitor);
const result = await service.updateMonitorStatus(makeStatusResponse({ status: false }), makeCheck({ status: false }));
expect(result.statusChanged).toBe(true);
expect(result.monitor.status).toBe("down");
expect(result.prevStatus).toBe("maintenance");
});
it("unsticks from 'maintenance' to 'up' on a passing check even with a full window (regression: stuck in maintenance)", async () => {
// Guards the up-front override directly: even if a full window survives (e.g. the
// entry-clear is bypassed), computeReachability only transitions out to 'up' from
// 'down', so without the override the monitor would stay stuck in 'maintenance'.
const monitor = makeMonitor({
statusWindow: [true, true, true, true, true],
statusWindowSize: 5,
statusWindowThreshold: 80,
status: "maintenance",
});
const { service, monitorsRepository } = createService();
(monitorsRepository.findById as jest.Mock).mockResolvedValue(monitor);
const result = await service.updateMonitorStatus(makeStatusResponse({ status: true }), makeCheck({ status: true }));
expect(result.monitor.status).toBe("up");
});
it("does not flip status on subsequent checks during warmup once 'initializing' has been left (regression: down→up incident resolves once window fills)", async () => {
// After a 'initializing'→'down' transition on check #1, monitor.status is now 'down'
// and statusWindow has one false. Subsequent up checks during warmup must leave