Skip to content

Commit d4d23cf

Browse files
authored
fix: orphaned monitor page components (#1911)
* fix: orphaned monitor page components * fix: transactional query * revert: pageComponent limit check
1 parent 7810906 commit d4d23cf

File tree

3 files changed

+109
-22
lines changed

3 files changed

+109
-22
lines changed

apps/server/src/routes/rpc/services/monitor/__tests__/monitor.test.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
22
import { db, eq } from "@openstatus/db";
3-
import { monitor } from "@openstatus/db/src/schema";
3+
import { monitor, pageComponent } from "@openstatus/db/src/schema";
44
import { monitorStatusTable } from "@openstatus/db/src/schema/monitor_status/monitor_status";
55

66
import { app } from "@/index";
@@ -468,6 +468,61 @@ describe("MonitorService.DeleteMonitor", () => {
468468
await db.delete(monitor).where(eq(monitor.id, otherWorkspaceMon.id));
469469
}
470470
});
471+
472+
test("deleting a monitor removes its page components", async () => {
473+
// Create a monitor
474+
const mon = await db
475+
.insert(monitor)
476+
.values({
477+
workspaceId: 1,
478+
name: `${TEST_PREFIX}-delete-with-component`,
479+
url: "https://delete-component.example.com",
480+
periodicity: "1m",
481+
active: true,
482+
regions: "ams",
483+
jobType: "http",
484+
})
485+
.returning()
486+
.get();
487+
488+
// Create a pageComponent referencing that monitor
489+
const component = await db
490+
.insert(pageComponent)
491+
.values({
492+
workspaceId: 1,
493+
pageId: 1,
494+
type: "monitor",
495+
monitorId: mon.id,
496+
name: `${TEST_PREFIX}-component`,
497+
order: 0,
498+
})
499+
.returning()
500+
.get();
501+
502+
// Verify the component exists
503+
const beforeDelete = await db
504+
.select()
505+
.from(pageComponent)
506+
.where(eq(pageComponent.id, component.id))
507+
.get();
508+
expect(beforeDelete).toBeDefined();
509+
510+
// Delete the monitor
511+
const res = await connectRequest(
512+
"DeleteMonitor",
513+
{ id: String(mon.id) },
514+
{ "x-openstatus-key": "1" },
515+
);
516+
expect(res.status).toBe(200);
517+
518+
// Verify the page component was removed
519+
const afterDelete = await db
520+
.select()
521+
.from(pageComponent)
522+
.where(eq(pageComponent.id, component.id))
523+
.get();
524+
expect(afterDelete).toBeUndefined();
525+
});
471526
});
472527

473528
describe("MonitorService.CreateHTTPMonitor", () => {

apps/server/src/routes/rpc/services/monitor/index.ts

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,16 @@ import { getCheckerPayload, getCheckerUrl } from "@/libs/checker";
33
import { tb } from "@/libs/clients";
44
import type { ServiceImpl } from "@connectrpc/connect";
55
import { and, db, eq, gte, inArray, isNull, sql } from "@openstatus/db";
6-
import { monitor, monitorRun } from "@openstatus/db/src/schema";
6+
import {
7+
maintenancesToMonitors,
8+
monitor,
9+
monitorRun,
10+
monitorTagsToMonitors,
11+
monitorsToPages,
12+
monitorsToStatusReport,
13+
notificationsToMonitors,
14+
pageComponent,
15+
} from "@openstatus/db/src/schema";
716
import { monitorStatusTable } from "@openstatus/db/src/schema/monitor_status/monitor_status";
817
import { selectMonitorSchema } from "@openstatus/db/src/schema/monitors/validation";
918
import type {
@@ -563,14 +572,34 @@ export const monitorServiceImpl: ServiceImpl<typeof MonitorService> = {
563572
throw monitorNotFoundError(req.id);
564573
}
565574

566-
// Soft delete
567-
await db
568-
.update(monitor)
569-
.set({
570-
active: false,
571-
deletedAt: new Date(),
572-
})
573-
.where(eq(monitor.id, dbMon.id));
575+
// Soft delete and clean up related rows atomically
576+
await db.transaction(async (tx) => {
577+
await tx
578+
.update(monitor)
579+
.set({
580+
active: false,
581+
deletedAt: new Date(),
582+
})
583+
.where(eq(monitor.id, dbMon.id));
584+
await tx
585+
.delete(monitorsToPages)
586+
.where(eq(monitorsToPages.monitorId, dbMon.id));
587+
await tx
588+
.delete(monitorTagsToMonitors)
589+
.where(eq(monitorTagsToMonitors.monitorId, dbMon.id));
590+
await tx
591+
.delete(monitorsToStatusReport)
592+
.where(eq(monitorsToStatusReport.monitorId, dbMon.id));
593+
await tx
594+
.delete(notificationsToMonitors)
595+
.where(eq(notificationsToMonitors.monitorId, dbMon.id));
596+
await tx
597+
.delete(maintenancesToMonitors)
598+
.where(eq(maintenancesToMonitors.monitorId, dbMon.id));
599+
await tx
600+
.delete(pageComponent)
601+
.where(eq(pageComponent.monitorId, dbMon.id));
602+
});
574603

575604
return { success: true };
576605
},

packages/api/src/router/monitor.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
monitorsToStatusReport,
2828
notification,
2929
notificationsToMonitors,
30+
pageComponent,
3031
privateLocation,
3132
privateLocationToMonitors,
3233
selectIncidentSchema,
@@ -63,13 +64,11 @@ export const monitorRouter = createTRPCRouter({
6364
.get();
6465
if (!monitorToDelete) return;
6566

66-
await opts.ctx.db
67-
.update(monitor)
68-
.set({ deletedAt: new Date(), active: false })
69-
.where(eq(monitor.id, monitorToDelete.id))
70-
.run();
71-
7267
await opts.ctx.db.transaction(async (tx) => {
68+
await tx
69+
.update(monitor)
70+
.set({ deletedAt: new Date(), active: false })
71+
.where(eq(monitor.id, monitorToDelete.id));
7372
await tx
7473
.delete(monitorsToPages)
7574
.where(eq(monitorsToPages.monitorId, monitorToDelete.id));
@@ -85,6 +84,9 @@ export const monitorRouter = createTRPCRouter({
8584
await tx
8685
.delete(maintenancesToMonitors)
8786
.where(eq(maintenancesToMonitors.monitorId, monitorToDelete.id));
87+
await tx
88+
.delete(pageComponent)
89+
.where(eq(pageComponent.monitorId, monitorToDelete.id));
8890
});
8991
}),
9092

@@ -109,13 +111,11 @@ export const monitorRouter = createTRPCRouter({
109111
});
110112
}
111113

112-
await opts.ctx.db
113-
.update(monitor)
114-
.set({ deletedAt: new Date(), active: false })
115-
.where(inArray(monitor.id, opts.input.ids))
116-
.run();
117-
118114
await opts.ctx.db.transaction(async (tx) => {
115+
await tx
116+
.update(monitor)
117+
.set({ deletedAt: new Date(), active: false })
118+
.where(inArray(monitor.id, opts.input.ids));
119119
await tx
120120
.delete(monitorsToPages)
121121
.where(inArray(monitorsToPages.monitorId, opts.input.ids));
@@ -131,6 +131,9 @@ export const monitorRouter = createTRPCRouter({
131131
await tx
132132
.delete(maintenancesToMonitors)
133133
.where(inArray(maintenancesToMonitors.monitorId, opts.input.ids));
134+
await tx
135+
.delete(pageComponent)
136+
.where(inArray(pageComponent.monitorId, opts.input.ids));
134137
});
135138
}),
136139

0 commit comments

Comments
 (0)