Openstatus www.openstatus.dev

chore: include incident to public status (#484)

authored by

Maximilian Kaske and committed by
GitHub
b999dd04 5696d454

+58 -9
+10
apps/docs/getting-started/status-widget.mdx
··· 32 32 MajorOutage = "major_outage", 33 33 UnderMaintenance = "under_maintenance", // currently not in use 34 34 Unknown = "unknown", 35 + Incident = "incident", 35 36 } 36 37 ``` 37 38 ··· 53 54 ``` 54 55 55 56 We are caching the result for `30 seconds` to reduce the load on our database. 57 + 58 + The `Status.Incident` will always be returned when then status of any incident 59 + on your page is **not** _"monitoring"_ or _"resolved"_. You can attach an 60 + incident to a monitor (implicit) or a page (explicit). 56 61 57 62 > If you have a doubt about the above calculation, feel free to contact us via 58 63 > [ping@openstatus.dev](mailto:ping@openstatus.dev) or ··· 82 87 "major_outage", 83 88 "under_maintenance", 84 89 "unknown", 90 + "incident", 85 91 ]); 86 92 87 93 const statusSchema = z.object({ status: statusEnum }); ··· 106 112 unknown: { 107 113 label: "Unknown", 108 114 color: "bg-gray-500", 115 + }, 116 + incident: { 117 + label: "Incident", 118 + color: "bg-yellow-500", 109 119 }, 110 120 under_maintenance: { 111 121 label: "Under Maintenance",
+31 -2
apps/server/src/public/status.ts
··· 2 2 import { endTime, setMetric, startTime } from "hono/timing"; 3 3 4 4 import { db, eq } from "@openstatus/db"; 5 - import { monitor, monitorsToPages, page } from "@openstatus/db/src/schema"; 5 + import { 6 + incident, 7 + monitor, 8 + monitorsToIncidents, 9 + monitorsToPages, 10 + page, 11 + pagesToIncidents, 12 + } from "@openstatus/db/src/schema"; 6 13 import { getMonitorList, Tinybird } from "@openstatus/tinybird"; 7 14 import { Redis } from "@openstatus/upstash"; 8 15 ··· 20 27 MajorOutage = "major_outage", 21 28 UnderMaintenance = "under_maintenance", 22 29 Unknown = "unknown", 30 + Incident = "incident", 23 31 } 24 32 25 33 export const status = new Hono(); ··· 33 41 34 42 return c.json({ status: cache }); 35 43 } 44 + 36 45 startTime(c, "database"); 37 46 // { monitors, pages, monitors_to_pages } 38 47 const monitorData = await db 39 48 .select() 40 49 .from(monitorsToPages) 41 50 .leftJoin(monitor, eq(monitorsToPages.monitorId, monitor.id)) 51 + .leftJoin( 52 + monitorsToIncidents, 53 + eq(monitor.id, monitorsToIncidents.monitorId), 54 + ) 55 + .leftJoin(incident, eq(monitorsToIncidents.incidentId, incident.id)) 42 56 .leftJoin(page, eq(monitorsToPages.pageId, page.id)) 43 57 .where(eq(page.slug, slug)) 44 58 .all(); 59 + 60 + const pageIncidentData = await db 61 + .select() 62 + .from(pagesToIncidents) 63 + .leftJoin(incident, eq(pagesToIncidents.incidentId, incident.id)) 64 + .leftJoin(page, eq(pagesToIncidents.pageId, page.id)) 65 + .where(eq(page.slug, slug)) 66 + .all(); 67 + 45 68 endTime(c, "database"); 69 + 70 + const isIncident = [...pageIncidentData, ...monitorData].some((data) => { 71 + if (!data.incident) return false; 72 + return !["monitoring", "resolved"].includes(data.incident.status); 73 + }); 46 74 47 75 startTime(c, "clickhouse"); 48 76 // { data: [{ ok, count }] } ··· 77 105 78 106 const ratio = data.ok / data.count; 79 107 80 - const status = getStatus(ratio); 108 + const status: Status = isIncident ? Status.Incident : getStatus(ratio); 109 + 81 110 await redis.set(slug, status, { ex: 30 }); 82 111 83 112 return c.json({ status });
+2 -1
packages/react/README.md
··· 85 85 | "partial_outage" 86 86 | "major_outage" 87 87 | "under_maintenance" 88 - | "unknown"; 88 + | "unknown" 89 + | "incident"; 89 90 ``` 90 91 91 92 Learn more in the [docs](https://docs.openstatus.dev/packages/react).
+10 -1
packages/react/src/utils.ts
··· 1 - export const statusDictionary = { 1 + import type { Status } from "./widget"; 2 + 3 + export const statusDictionary: Record< 4 + Status, 5 + { label: string; color: string } 6 + > = { 2 7 operational: { 3 8 label: "Operational", 4 9 color: "bg-green-500", ··· 18 23 unknown: { 19 24 label: "Unknown", 20 25 color: "bg-gray-500", 26 + }, 27 + incident: { 28 + label: "Incident", 29 + color: "bg-yellow-500", 21 30 }, 22 31 under_maintenance: { 23 32 label: "Under Maintenance",
+5 -5
packages/react/src/widget.tsx
··· 6 6 | "partial_outage" 7 7 | "major_outage" 8 8 | "under_maintenance" 9 - | "unknown"; 9 + | "unknown" 10 + | "incident"; 10 11 11 12 export type StatusResponse = { status: Status }; 12 13 ··· 29 30 }; 30 31 31 32 export async function StatusWidget({ slug, href }: StatusWidgetProps) { 32 - const data = await getStatus(slug); 33 + const { status } = await getStatus(slug); 33 34 34 - const key = data.status; 35 - const { label, color } = statusDictionary[key]; 35 + const { label, color } = statusDictionary[status]; 36 36 37 37 return ( 38 38 <a ··· 43 43 > 44 44 {label} 45 45 <span className="relative flex h-2 w-2"> 46 - {data.status === "operational" ? ( 46 + {status === "operational" ? ( 47 47 <span 48 48 className={`absolute inline-flex h-full w-full animate-ping rounded-full ${color} opacity-75 duration-1000`} 49 49 />