-
-
Notifications
You must be signed in to change notification settings - Fork 280
/
incidents.jsx
150 lines (140 loc) · 4.79 KB
/
incidents.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
import Link from "@docusaurus/Link";
import moment from "moment";
import { Octokit } from "octokit";
import React, { useEffect, useState } from "react";
import Markdown from "react-markdown";
import styles from "./styles.module.css";
import remarkGfm from 'remark-gfm'
// This label indicates warning.
const DEGRADED = "degraded performance";
// This label indicates danger.
const MAJOR = "major outage";
// Incident labels we care about.
const BAD_LABELS = new Set(["investigating", DEGRADED, MAJOR, "maintenance"]);
// Date format string.
const DATE = "YYYY/M/D HH:mm:ss";
// Time period we care about: 90 days – in milliseconds.
const PERIOD = 90 * 24 * 60 * 60 * 1000;
// The GitHub repository with relevant issues.
const REPO = { owner: "conda-forge", repo: "status" };
// The badge color for each severity level.
const SEVERITY = {
"investigating": "info",
[DEGRADED]: "warning",
[MAJOR]: "danger",
"maintenance": "info"
};
export default function Incidents({ ongoing, onLoad, ...props }) {
const [{ closed, current, open }, setState] = useState(() => {
const { current, open } = props;
return { closed: [], current: current ?? new Set(), open: open ?? [] }
});
useEffect(() => {
void (async (initialized = current.size && ongoing && open.length) => {
// If everything we need came from the props, bail.
if (initialized) return;
const octokit = new Octokit({});
// If we only want ongoing incidents, set the era in the future.
const era = ongoing ? Date.now() + PERIOD : Date.now() - PERIOD;
const open = [];
const closed = [];
let current = new Set();
try {
const issues = await octokit.rest.issues.listForRepo({
...REPO, per_page: 100, state: "all"
});
for (const issue of issues.data) {
const labels = new Set(issue.labels.map(({ name }) => name));
const incident = intersection(labels, BAD_LABELS);
if (!incident.size) continue; // Bail if the issue is not an incident.
if (typeof issue === "undefined") debugger;
const severity = incident.keys().next().value;
if (issue.state === "open") {
open.push({ ...issue, severity });
current = new Set([...current, ...incident]);
} else if (era < new Date(issue.closed_at).getTime()) {
closed.push({ ...issue, severity });
}
}
setState({ closed, current, open });
} catch (error) {
console.warn(`error loading github issues`, error);
}
onLoad?.(current.size ? { current, ongoing: true, open } : undefined);
})();
}, []);
return (
<div className="card margin-top--xs">
<div className="card__header">
<h3>
Incidents
{" "}
{!!current.size && (
<span className={
`badge badge--${
current.has(MAJOR) ? "danger" : current.has(DEGRADED) ? "warning" : "info"
}`}>
{
current.has(MAJOR) ? MAJOR :
current.has(DEGRADED) ? DEGRADED :
current.values().next().value}
</span>
)}
</h3>
</div>
<div className={`card__body ${styles.incidents}`}>
{open.map((issue, i) => <Incident key={i}>{issue}</Incident>)}
{closed.length ? (
<details>
<summary>See previous incidents</summary>
{closed.map((issue, i) => <Incident key={i}>{issue}</Incident>)}
</details>
) : null}
</div>
</div>
);
}
function Incident({ children }) {
const issue = children;
const open = issue.state === "open";
const date = moment(issue.created_at);
return (
<div className={styles.incident}>
<div>
<span className={
`badge badge--${open ? SEVERITY[issue.severity] : "success"}
`}>
{open ? "ongoing" : "resolved"}
</span>
{" "}
<span className={`badge badge--${SEVERITY[issue.severity]}`}>
{issue.severity}
</span>
<em className={styles.incident_date}>{date.format(DATE)} UTC</em>
</div>
<Link className={styles.incident_link} to={issue.html_url}>
<Markdown
components={{
p(props) {
const { children } = props
return <>{children}</>
}
}}>{issue.title}
</Markdown> (#{issue.number})
</Link>
<div className={styles.incident_body}>
<Markdown
remarkPlugins={[remarkGfm]}
>
{issue.body}
</Markdown>
</div>
</div>
);
}
const intersection = (one, two) => {
const intersection = new Set();
const [bigger, smaller] = one.size >= two.size ? [one, two] : [two, one];
for (const item of smaller) if (bigger.has(item)) intersection.add(item);
return intersection;
}