diff --git a/apps/dashboard/src/app/(dashboard)/status-pages/[id]/status-reports/(list)/page.tsx b/apps/dashboard/src/app/(dashboard)/status-pages/[id]/status-reports/(list)/page.tsx index 86c6689e3..4ce21a964 100644 --- a/apps/dashboard/src/app/(dashboard)/status-pages/[id]/status-reports/(list)/page.tsx +++ b/apps/dashboard/src/app/(dashboard)/status-pages/[id]/status-reports/(list)/page.tsx @@ -41,7 +41,7 @@ export default function Page() { onSuccess: (statusReport) => { if (statusReport.notifySubscribers) { sendStatusReportUpdateMutation.mutate({ - id: statusReport.id, + id: statusReport.initialUpdateId, }); } refetch(); diff --git a/apps/dashboard/src/components/forms/status-report/sheet-create.tsx b/apps/dashboard/src/components/forms/status-report/sheet-create.tsx index 993d9e4d5..5339c47bc 100644 --- a/apps/dashboard/src/components/forms/status-report/sheet-create.tsx +++ b/apps/dashboard/src/components/forms/status-report/sheet-create.tsx @@ -81,7 +81,9 @@ export function FormSheetStatusReportCreate({ trpc.statusReport.create.mutationOptions({ onSuccess: (statusReport) => { if (statusReport.notifySubscribers) { - sendStatusReportUpdateMutation.mutate({ id: statusReport.id }); + sendStatusReportUpdateMutation.mutate({ + id: statusReport.initialUpdateId, + }); } // no-input prefix key — matches every statusReport.list query queryClient.invalidateQueries({ diff --git a/packages/api/src/router/statusReport.test.ts b/packages/api/src/router/statusReport.test.ts index a0cb8c7b1..794086412 100644 --- a/packages/api/src/router/statusReport.test.ts +++ b/packages/api/src/router/statusReport.test.ts @@ -19,12 +19,9 @@ let otherWorkspaceReportId: number; let otherWorkspaceComponentId: number; // Track ids the service writes so afterAll can scrub the resulting -// audit rows. `caller.statusReport.create` returns the initial-update -// row (not the report), so the original cleanup-via-`result.id` never -// reached the parent — leaving an orphan report and an orphan audit row. -// SQLite recycles INTEGER PRIMARY KEY ids on delete, so a later test -// inserting a status_report could land on the orphan's id and inherit -// its `actor_type=user` attribution. See packages/services/AGENTS.md. +// audit rows. SQLite recycles INTEGER PRIMARY KEY ids on delete, so a +// later test inserting a status_report could land on an orphan's id and +// inherit its `actor_type=user` attribution. See packages/services/AGENTS.md. const createdReportIds: number[] = []; const updatedReportIds: number[] = []; @@ -174,20 +171,7 @@ test("statusReport.create succeeds for own workspace page", async () => { }); expect(result).toBeDefined(); - - // `result.id` is the initial status_report_update id, not the parent - // report — recover the report id via the FK so afterAll can scrub both - // it and its audit row. - if (result) { - const updateRow = await db - .select({ statusReportId: statusReportUpdate.statusReportId }) - .from(statusReportUpdate) - .where(eq(statusReportUpdate.id, result.id)) - .get(); - if (updateRow?.statusReportId != null) { - createdReportIds.push(updateRow.statusReportId); - } - } + if (result) createdReportIds.push(result.id); }); test("statusReport.updateStatus rejects report from another workspace", async () => { diff --git a/packages/api/src/router/statusReport.ts b/packages/api/src/router/statusReport.ts index b682b5008..a4d1d5491 100644 --- a/packages/api/src/router/statusReport.ts +++ b/packages/api/src/router/statusReport.ts @@ -69,23 +69,29 @@ export const statusReportRouter = createTRPCRouter({ .mutation(async ({ ctx, input }) => { try { const serviceCtx = toServiceCtx(ctx); - const { initialUpdate } = await createStatusReport({ - ctx: serviceCtx, - input: { - title: input.title, - status: input.status, - pageId: input.pageId, - pageComponentIds: input.pageComponents, - componentImpacts: input.componentImpacts ?? undefined, - date: input.date, - message: input.message, - incidentId: input.incidentId, - }, - }); + const { statusReport: report, initialUpdate } = + await createStatusReport({ + ctx: serviceCtx, + input: { + title: input.title, + status: input.status, + pageId: input.pageId, + pageComponentIds: input.pageComponents, + componentImpacts: input.componentImpacts ?? undefined, + date: input.date, + message: input.message, + incidentId: input.incidentId, + }, + }); // Notification is a separate, client-driven step // (subscriberNotification.statusReport) so creation and dispatch stay // split — dispatch must run on lambda, this router is Edge-served. - return { ...initialUpdate, notifySubscribers: input.notifySubscribers }; + // subscriberNotification.statusReport is keyed by update id. + return { + ...report, + initialUpdateId: initialUpdate.id, + notifySubscribers: input.notifySubscribers, + }; } catch (err) { toTRPCError(err); }