Openstatus www.openstatus.dev
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

Added Incidents (#604)

* Added incidents endpoint

* added

* 🚧 wip

* Added incidents tests

* modified tests

* fixed

* added endpoint

* changes

* mods

* 🔥

* 🔥

* 🔥

---------

Co-authored-by: Thibault Le Ouay <thibaultleouay@gmail.Com>

authored by

Kushagra Sharma
Thibault Le Ouay
and committed by
GitHub
0da38c3e fa43864d

+206 -4
+4
apps/docs/api-reference/incident/get-incident.mdx
··· 1 + --- 2 + title: Get all incidents 3 + openapi: get /incident/ 4 + ---
+4
apps/docs/api-reference/incident/get-incident:id.mdx
··· 1 + --- 2 + title: Get an incident 3 + openapi: get /incident/:id 4 + ---
+7 -3
apps/docs/mint.json
··· 74 74 }, 75 75 { 76 76 "group": "Features", 77 - 78 77 "pages": [ 79 78 "features/incident", 80 79 "features/heartbeat", 81 80 "features/monitor", 82 - 83 81 { 84 82 "group": "Notifications", 85 83 "pages": [ ··· 105 103 "group": "Developer Tools", 106 104 "pages": ["packages/react", "packages/status-widget"] 107 105 }, 108 - 109 106 { 110 107 "group": "Contributor Guides", 111 108 "pages": [ ··· 120 117 "icon": "code", 121 118 "pages": [ 122 119 "api-reference/auth", 120 + { 121 + "group": "Incident", 122 + "pages": [ 123 + "api-reference/incident/get-incident:id", 124 + "api-reference/incident/get-incident" 125 + ] 126 + }, 123 127 { 124 128 "group": "Monitor", 125 129 "pages": [
+17
apps/server/src/v1/incidents.test.ts
··· 1 + import { expect, test } from "bun:test"; 2 + 3 + import { api } from "."; 4 + 5 + test("GET one Incident", async () => { 6 + const res = await api.request("/incident/1", { 7 + headers: { 8 + "x-openstatus-key": "1", 9 + }, 10 + }); 11 + expect(res.status).toBe(200); 12 + expect(await res.json()).toMatchObject({ 13 + id: 1, 14 + startedAt: expect.any(String), 15 + monitorId: 1, 16 + }); 17 + });
+162
apps/server/src/v1/incidents.ts
··· 1 + import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi"; 2 + 3 + import { and, db, eq, isNotNull } from "@openstatus/db"; 4 + import { incidentTable } from "@openstatus/db/src/schema/incidents"; 5 + 6 + import type { Variables } from "./index"; 7 + import { ErrorSchema } from "./shared"; 8 + 9 + const incidentsApi = new OpenAPIHono<{ Variables: Variables }>(); 10 + 11 + const ParamsSchema = z.object({ 12 + id: z 13 + .string() 14 + .min(1) 15 + .openapi({ 16 + param: { 17 + name: "id", 18 + in: "path", 19 + }, 20 + description: "The id of the Incident", 21 + example: "1", 22 + }), 23 + }); 24 + 25 + const IncidentSchema = z.object({ 26 + id: z.number().openapi({ 27 + description: "The id of the incident", 28 + example: 1, 29 + }), 30 + startedAt: z 31 + .preprocess((val) => String(val), z.string()) 32 + .openapi({ 33 + description: "The date the incident started", 34 + }), 35 + 36 + monitorId: z 37 + .number() 38 + .openapi({ 39 + description: "The id of the monitor associated with the incident", 40 + example: 1, 41 + }) 42 + .nullable(), 43 + 44 + acknowledgedAt: z 45 + .preprocess((val) => String(val), z.string()) 46 + .openapi({ 47 + description: "The date the incident was acknowledged", 48 + }) 49 + .optional(), 50 + 51 + acknowledgedBy: z 52 + .number() 53 + .openapi({ 54 + description: "The user who acknowledged the incident", 55 + }) 56 + .nullable(), 57 + 58 + resolvedAt: z 59 + .preprocess((val) => String(val), z.string()) 60 + .openapi({ 61 + description: "The date the incident was resolved", 62 + }) 63 + .optional(), 64 + resolvedBy: z 65 + .number() 66 + .openapi({ 67 + description: "The user who resolved the incident", 68 + }) 69 + .nullable(), 70 + }); 71 + 72 + const getAllRoute = createRoute({ 73 + method: "get", 74 + tags: ["incident"], 75 + description: "Get all incidents", 76 + path: "/", 77 + request: {}, 78 + responses: { 79 + 200: { 80 + content: { 81 + "application/json": { 82 + schema: z.array(IncidentSchema), 83 + }, 84 + }, 85 + description: "Get all incidents", 86 + }, 87 + 400: { 88 + content: { 89 + "application/json": { 90 + schema: ErrorSchema, 91 + }, 92 + }, 93 + description: "Returns an error", 94 + }, 95 + }, 96 + }); 97 + 98 + incidentsApi.openapi(getAllRoute, async (c) => { 99 + const workspaceId = Number(c.get("workspaceId")); 100 + const result = await db 101 + .select() 102 + .from(incidentTable) 103 + .where(eq(incidentTable.workspaceId, workspaceId)) 104 + .all(); 105 + 106 + if (!result) return c.jsonT({ code: 404, message: "Not Found" }); 107 + 108 + const data = z.array(IncidentSchema).parse(result); 109 + return c.jsonT(data); 110 + }); 111 + 112 + const getRoute = createRoute({ 113 + method: "get", 114 + tags: ["incident"], 115 + description: "Get an incident", 116 + path: "/:id", 117 + request: { 118 + params: ParamsSchema, 119 + }, 120 + responses: { 121 + 200: { 122 + content: { 123 + "application/json": { 124 + schema: IncidentSchema, 125 + }, 126 + }, 127 + description: "Get an incident", 128 + }, 129 + 400: { 130 + content: { 131 + "application/json": { 132 + schema: ErrorSchema, 133 + }, 134 + }, 135 + description: "Returns an error", 136 + }, 137 + }, 138 + }); 139 + 140 + incidentsApi.openapi(getRoute, async (c) => { 141 + const workspaceId = Number(c.get("workspaceId")); 142 + const { id } = c.req.valid("param"); 143 + 144 + const incidentId = Number(id); 145 + const result = await db 146 + .select() 147 + .from(incidentTable) 148 + .where( 149 + and( 150 + eq(incidentTable.workspaceId, workspaceId), 151 + eq(incidentTable.id, incidentId), 152 + ), 153 + ) 154 + .get(); 155 + 156 + if (!result) return c.jsonT({ code: 404, message: "Not Found" }); 157 + const data = IncidentSchema.parse(result); 158 + 159 + return c.jsonT(data); 160 + }); 161 + 162 + export { incidentsApi };
+2 -1
apps/server/src/v1/index.ts
··· 3 3 4 4 import type { Limits } from "@openstatus/plans/src/types"; 5 5 6 + import { incidentsApi } from "./incidents"; 6 7 import { middleware } from "./middleware"; 7 8 import { monitorApi } from "./monitor"; 8 9 import { statusReportApi } from "./statusReport"; ··· 35 36 api.use("/*", logger()); 36 37 api.route("/monitor", monitorApi); 37 38 api.route("/status_report_update", statusReportUpdateApi); 38 - 39 + api.route("/incident", incidentsApi); 39 40 api.route("/status_report", statusReportApi);
+10
packages/db/src/seed.mts
··· 11 11 notificationsToMonitors, 12 12 page, 13 13 statusReport, 14 + incidentTable, 14 15 statusReportUpdate, 15 16 user, 16 17 usersToWorkspaces, ··· 129 130 title: "Test Status Report", 130 131 status: "investigating", 131 132 updatedAt: new Date(), 133 + }) 134 + .run(); 135 + 136 + await db 137 + .insert(incidentTable) 138 + .values({ 139 + id: 1, 140 + workspaceId: 1, 141 + monitorId: 1, 132 142 }) 133 143 .run(); 134 144