From 940e6c024fefe5f7c09776f92aa8d92baeef845c Mon Sep 17 00:00:00 2001
From: Maximilian Kaske <56969857+mxkaske@users.noreply.github.com>
Date: Thu, 23 Jul 2026 22:00:32 +0200
Subject: [PATCH] chore: planned maintenance label (#2421)
* chore: planned maintenance label
* fix: ts
* test: status label
---
.../[locale]/(public)/feed/[type]/route.ts | 15 ++++---------
packages/emails/emails/status-report.tsx | 10 +++------
packages/emails/package.json | 1 +
.../emails/src/client.integration.test.tsx | 22 +++++++++----------
packages/emails/src/client.tsx | 8 ++++---
.../src/channels/slack-blocks.ts | 13 +++--------
.../subscriptions/src/channels/webhook.ts | 6 ++---
packages/utils/package.json | 3 ++-
packages/utils/src/index.ts | 1 +
packages/utils/src/status.test.ts | 20 +++++++++++++++++
packages/utils/src/status.ts | 18 +++++++++++++++
pnpm-lock.yaml | 3 +++
12 files changed, 74 insertions(+), 46 deletions(-)
create mode 100644 packages/utils/src/status.test.ts
create mode 100644 packages/utils/src/status.ts
diff --git a/apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/feed/[type]/route.ts b/apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/feed/[type]/route.ts
index f8ea2279c..4208f5333 100644
--- a/apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/feed/[type]/route.ts
+++ b/apps/status-page/src/app/(status-page)/[domain]/[locale]/(public)/feed/[type]/route.ts
@@ -1,3 +1,4 @@
+import { statusLabel } from "@openstatus/utils";
import { Feed } from "feed";
import { notFound, unauthorized } from "next/navigation";
@@ -5,14 +6,6 @@ import { auth } from "../../../../../../../lib/auth";
import { getBaseUrl } from "../../../../../../../lib/base-url";
import { getQueryClient, trpc } from "../../../../../../../lib/trpc/server";
-const STATUS_LABELS = {
- investigating: "Investigating",
- identified: "Identified",
- monitoring: "Monitoring",
- resolved: "Resolved",
- maintenance: "Maintenance",
-} as const;
-
export const revalidate = 60;
export async function GET(
@@ -90,7 +83,7 @@ export async function GET(
const maintenanceUrl = `${baseUrl}/events/maintenance/${maintenance.id}`;
feed.addItem({
id: maintenanceUrl,
- title: `Maintenance - ${maintenance.title}`,
+ title: `${statusLabel("maintenance")} - ${maintenance.title}`,
link: maintenanceUrl,
description: maintenance.message,
date: maintenance.updatedAt ?? maintenance.createdAt ?? new Date(),
@@ -99,10 +92,10 @@ export async function GET(
for (const statusReport of page.statusReports ?? []) {
const statusReportUrl = `${baseUrl}/events/report/${statusReport.id}`;
- const status = STATUS_LABELS[statusReport.status] ?? statusReport.status;
+ const status = statusLabel(statusReport.status);
const statusReportUpdates = (statusReport.statusReportUpdates ?? [])
.map((update) => {
- const updateStatus = STATUS_LABELS[update.status] ?? update.status;
+ const updateStatus = statusLabel(update.status);
return `${updateStatus}: ${update.message}.`;
})
.join("\n\n");
diff --git a/packages/emails/emails/status-report.tsx b/packages/emails/emails/status-report.tsx
index 0fe8ad192..91b8a2893 100644
--- a/packages/emails/emails/status-report.tsx
+++ b/packages/emails/emails/status-report.tsx
@@ -1,5 +1,6 @@
/** @jsxRuntime automatic @jsxImportSource react */
+import { statusLabel } from "@openstatus/utils";
import {
Body,
Column,
@@ -76,13 +77,8 @@ function StatusReportEmail({
{pageTitle}
-
- {status}
+
+ {statusLabel(status)}
diff --git a/packages/emails/package.json b/packages/emails/package.json
index 229b1c93a..fa7798ea9 100644
--- a/packages/emails/package.json
+++ b/packages/emails/package.json
@@ -17,6 +17,7 @@
"test": "deno test --parallel -A --no-check --sloppy-imports"
},
"dependencies": {
+ "@openstatus/utils": "workspace:*",
"@t3-oss/env-core": "catalog:",
"effect": "catalog:",
"react": "catalog:",
diff --git a/packages/emails/src/client.integration.test.tsx b/packages/emails/src/client.integration.test.tsx
index fde8e72d2..8829ab403 100644
--- a/packages/emails/src/client.integration.test.tsx
+++ b/packages/emails/src/client.integration.test.tsx
@@ -1,5 +1,6 @@
/** @jsxRuntime automatic @jsxImportSource react */
+import { statusLabel } from "@openstatus/utils";
import { expect } from "@std/expect";
import { describe, test } from "@std/testing/bdd";
import { render } from "react-email";
@@ -94,20 +95,21 @@ describe("Status Report Email - Subject Line", () => {
);
});
- test('does not prepend "RESOLVED:" for any non-resolved status', () => {
- const nonResolved = [
- "investigating",
- "identified",
- "monitoring",
- "maintenance",
- ] as const;
+ test("uses the bare report title for ongoing report statuses", () => {
+ const ongoing = ["investigating", "identified", "monitoring"] as const;
- for (const status of nonResolved) {
+ for (const status of ongoing) {
const subject = statusReportSubject({ status, reportTitle });
expect(subject).toBe(reportTitle);
expect(subject).not.toContain("RESOLVED:");
}
});
+
+ test('prepends "Planned Maintenance:" when status is "maintenance"', () => {
+ expect(statusReportSubject({ status: "maintenance", reportTitle })).toBe(
+ `Planned Maintenance: ${reportTitle}`,
+ );
+ });
});
describe("Status Report Email - Email Content Validation", () => {
@@ -156,9 +158,7 @@ describe("Status Report Email - Email Content Validation", () => {
/>,
);
- // Should render without errors and contain the status
- // Note: status is rendered lowercase in HTML with text-transform: uppercase CSS
- expect(html).toContain(status);
+ expect(html).toContain(statusLabel(status));
}
});
});
diff --git a/packages/emails/src/client.tsx b/packages/emails/src/client.tsx
index d14664815..074709ec6 100644
--- a/packages/emails/src/client.tsx
+++ b/packages/emails/src/client.tsx
@@ -1,5 +1,6 @@
/** @jsxRuntime automatic @jsxImportSource react */
+import { statusLabel } from "@openstatus/utils";
import { type Duration, Effect, Schedule } from "effect";
import { render } from "react-email";
import { Resend } from "resend";
@@ -23,9 +24,10 @@ export function statusReportSubject(req: {
status: StatusReportProps["status"];
reportTitle: string;
}): string {
- return req.status === "resolved"
- ? `RESOLVED: ${req.reportTitle}`
- : req.reportTitle;
+ if (req.status === "resolved") return `RESOLVED: ${req.reportTitle}`;
+ if (req.status === "maintenance")
+ return `${statusLabel("maintenance")}: ${req.reportTitle}`;
+ return req.reportTitle;
}
// Deterministic Resend rejections: retrying the identical request can never
diff --git a/packages/subscriptions/src/channels/slack-blocks.ts b/packages/subscriptions/src/channels/slack-blocks.ts
index be418d372..ad843de8d 100644
--- a/packages/subscriptions/src/channels/slack-blocks.ts
+++ b/packages/subscriptions/src/channels/slack-blocks.ts
@@ -1,4 +1,5 @@
import { COLORS } from "@openstatus/notification-base";
+import { statusLabel } from "@openstatus/utils";
import type { KnownBlock, MessageAttachment } from "@slack/web-api";
import type { PageUpdate, Subscription } from "../types";
@@ -13,14 +14,6 @@ const STATUS_EMOJI: Record = {
maintenance: "🔧",
};
-const STATUS_LABEL: Record = {
- investigating: "Investigating",
- identified: "Identified",
- monitoring: "Monitoring",
- resolved: "Resolved",
- maintenance: "Maintenance",
-};
-
function statusColor(status: PageUpdate["status"]): StatusColor {
switch (status) {
case "investigating":
@@ -61,7 +54,7 @@ export function buildRootMessage(
subscription: Subscription,
): SlackRootMessage {
const emoji = STATUS_EMOJI[pageUpdate.status];
- const label = STATUS_LABEL[pageUpdate.status];
+ const label = statusLabel(pageUpdate.status);
const origin = pageOrigin(subscription);
const blocks: KnownBlock[] = [
@@ -130,7 +123,7 @@ export function buildRootMessage(
export function buildReplyMessage(pageUpdate: PageUpdate): SlackReplyMessage {
const emoji = STATUS_EMOJI[pageUpdate.status];
- const label = STATUS_LABEL[pageUpdate.status];
+ const label = statusLabel(pageUpdate.status);
const heading = `${emoji} *${label}* · ${pageUpdate.date}`;
return {
diff --git a/packages/subscriptions/src/channels/webhook.ts b/packages/subscriptions/src/channels/webhook.ts
index c09212776..13cb72e52 100644
--- a/packages/subscriptions/src/channels/webhook.ts
+++ b/packages/subscriptions/src/channels/webhook.ts
@@ -1,5 +1,5 @@
import { COLORS, COLOR_DECIMALS } from "@openstatus/notification-base";
-import { assertSafeUrl } from "@openstatus/utils";
+import { assertSafeUrl, statusLabel } from "@openstatus/utils";
import { z } from "zod";
import { WEBHOOK_PAYLOAD_VERSION } from "../payload";
@@ -148,7 +148,7 @@ function buildSlackPayload(
fields: [
{
type: "mrkdwn",
- text: `*Status*\n${pageUpdate.status}`,
+ text: `*Status*\n${statusLabel(pageUpdate.status)}`,
},
{
type: "mrkdwn",
@@ -242,7 +242,7 @@ function buildDiscordPayload(
fields: [
{
name: "Status",
- value: pageUpdate.status,
+ value: statusLabel(pageUpdate.status),
inline: true,
},
{
diff --git a/packages/utils/package.json b/packages/utils/package.json
index 23419d760..639732845 100644
--- a/packages/utils/package.json
+++ b/packages/utils/package.json
@@ -7,7 +7,8 @@
"author": "",
"main": "src/index.ts",
"scripts": {
- "check": "deno check --sloppy-imports ."
+ "check": "deno check --sloppy-imports .",
+ "test": "deno test --parallel -A --no-check --sloppy-imports"
},
"dependencies": {
"@openstatus/assertions": "workspace:*",
diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts
index 381c101d3..65ae7343a 100644
--- a/packages/utils/src/index.ts
+++ b/packages/utils/src/index.ts
@@ -13,6 +13,7 @@ export {
MONITOR_JOB_TYPES,
} from "./constants";
export { yieldMany, iteratorToStream } from "./stream";
+export { statusLabel, type PageUpdateStatus } from "./status";
export function transformHeaders(headers: { key: string; value: string }[]) {
return headers.length > 0
diff --git a/packages/utils/src/status.test.ts b/packages/utils/src/status.test.ts
new file mode 100644
index 000000000..7e1eae0b9
--- /dev/null
+++ b/packages/utils/src/status.test.ts
@@ -0,0 +1,20 @@
+import { expect } from "@std/expect";
+import { describe, it } from "@std/testing/bdd";
+
+import { type PageUpdateStatus, statusLabel } from "./status";
+
+describe("statusLabel", () => {
+ const labels: Record = {
+ investigating: "Investigating",
+ identified: "Identified",
+ monitoring: "Monitoring",
+ resolved: "Resolved",
+ maintenance: "Planned Maintenance",
+ };
+
+ for (const [status, label] of Object.entries(labels)) {
+ it(`labels ${status} as "${label}"`, () => {
+ expect(statusLabel(status as PageUpdateStatus)).toBe(label);
+ });
+ }
+});
diff --git a/packages/utils/src/status.ts b/packages/utils/src/status.ts
new file mode 100644
index 000000000..21b8a18e9
--- /dev/null
+++ b/packages/utils/src/status.ts
@@ -0,0 +1,18 @@
+export type PageUpdateStatus =
+ | "investigating"
+ | "identified"
+ | "monitoring"
+ | "resolved"
+ | "maintenance";
+
+const STATUS_LABEL: Record = {
+ investigating: "Investigating",
+ identified: "Identified",
+ monitoring: "Monitoring",
+ resolved: "Resolved",
+ maintenance: "Planned Maintenance",
+};
+
+export function statusLabel(status: PageUpdateStatus): string {
+ return STATUS_LABEL[status];
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 846825f83..a6804644b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1993,6 +1993,9 @@ importers:
packages/emails:
dependencies:
+ '@openstatus/utils':
+ specifier: workspace:*
+ version: link:../utils
'@t3-oss/env-core':
specifier: 'catalog:'
version: 0.13.11(typescript@5.9.3)(zod@4.1.13)
--
2.51.2