Openstatus www.openstatus.dev
at main 122 lines 3.9 kB view raw
1import type { Incident } from "@openstatus/db/src/schema"; 2import { getRegionInfo } from "@openstatus/regions"; 3import type { FormattedMessageData, NotificationContext } from "../types"; 4import { getIncidentDuration } from "./incident"; 5import { formatTimestamp } from "./timestamp"; 6 7/** 8 * Common HTTP status descriptions 9 */ 10const statusDescriptions: Record<number, string> = { 11 200: "OK", 12 201: "Created", 13 204: "No Content", 14 301: "Moved Permanently", 15 302: "Found", 16 304: "Not Modified", 17 400: "Bad Request", 18 401: "Unauthorized", 19 403: "Forbidden", 20 404: "Not Found", 21 405: "Method Not Allowed", 22 408: "Request Timeout", 23 429: "Too Many Requests", 24 500: "Internal Server Error", 25 502: "Bad Gateway", 26 503: "Service Unavailable", 27 504: "Gateway Timeout", 28}; 29 30/** 31 * Format status code for display with human-readable description 32 * 33 * @example 34 * formatStatusCode(503) // "503 Service Unavailable" 35 * formatStatusCode(404) // "404 Not Found" 36 * formatStatusCode(418) // "418" (no description available) 37 * formatStatusCode(undefined) // "Unknown" 38 * 39 * @param statusCode - HTTP status code 40 * @returns Formatted status code string 41 */ 42export function formatStatusCode(statusCode?: number): string { 43 if (!statusCode) { 44 return "Unknown"; 45 } 46 47 const description = statusDescriptions[statusCode]; 48 return description ? `${statusCode} ${description}` : `${statusCode}`; 49} 50 51/** 52 * Build common formatted message data from notification context 53 * 54 * Centralizes formatting logic used by all providers to ensure consistency. 55 * 56 * @example 57 * const data = buildCommonMessageData(context); 58 * // Returns: { 59 * // monitorName: "My API", 60 * // monitorUrl: "https://api.example.com", 61 * // monitorMethod: "GET", 62 * // monitorJobType: "http", 63 * // statusCodeFormatted: "503 Service Unavailable", 64 * // errorMessage: "Connection timeout", 65 * // timestampFormatted: "Jan 22, 2026 at 14:30 UTC", 66 * // regionsDisplay: "ams, fra, syd", 67 * // latencyDisplay: "2,450ms", 68 * // dashboardUrl: "https://app.openstatus.dev/monitors/123", 69 * // incidentDuration: undefined 70 * // } 71 * 72 * @param context - Notification context with monitor and event data 73 * @param options - Optional configuration 74 * @param options.incident - Include incident data for duration calculation 75 * @returns Formatted message data ready for rendering 76 */ 77export function buildCommonMessageData( 78 context: NotificationContext, 79 options?: { 80 incident?: Incident; 81 }, 82): FormattedMessageData { 83 const { monitor, statusCode, message, cronTimestamp, regions, latency } = 84 context; 85 86 // Format multiple regions as comma-separated list 87 let regionsDisplay = "Unknown"; 88 if (regions && regions.length > 0) { 89 if (regions.length === 1) { 90 // Single region: show code and location 91 const regionInfo = getRegionInfo(regions[0]); 92 regionsDisplay = regionInfo 93 ? `${regionInfo.code} (${regionInfo.location})` 94 : regions[0]; 95 } else { 96 // Multiple regions: show comma-separated codes 97 regionsDisplay = regions.join(", "); 98 } 99 } 100 101 // Calculate incident duration only if incident is resolved 102 let incidentDuration: string | undefined; 103 if (options?.incident?.resolvedAt) { 104 const duration = getIncidentDuration(options.incident); 105 incidentDuration = duration ?? undefined; 106 } 107 108 return { 109 monitorName: monitor.name, 110 monitorUrl: monitor.url, 111 monitorMethod: monitor.method ?? undefined, 112 monitorJobType: monitor.jobType, 113 statusCodeFormatted: formatStatusCode(statusCode), 114 errorMessage: message || "No error message available", 115 timestampFormatted: formatTimestamp(cronTimestamp), 116 regionsDisplay, 117 latencyDisplay: 118 typeof latency === "number" ? `${latency.toLocaleString()}ms` : "N/A", 119 dashboardUrl: `https://app.openstatus.dev/monitors/${monitor.id}`, 120 incidentDuration, 121 }; 122}