@incident-io/backstage 0.0.15 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -1
- package/config.d.ts +11 -5
- package/dist/alpha.d.ts +5 -0
- package/dist/alpha.esm.js +91 -0
- package/dist/alpha.esm.js.map +1 -0
- package/dist/api/client.esm.js +34 -0
- package/dist/api/client.esm.js.map +1 -0
- package/dist/components/AlertListItem/index.esm.js +68 -0
- package/dist/components/AlertListItem/index.esm.js.map +1 -0
- package/dist/components/EntityAlertCard/index.esm.js +113 -0
- package/dist/components/EntityAlertCard/index.esm.js.map +1 -0
- package/dist/components/EntityIncidentCard/index.esm.js +125 -0
- package/dist/components/EntityIncidentCard/index.esm.js.map +1 -0
- package/dist/components/EntityOnCallCard/index.esm.js +198 -0
- package/dist/components/EntityOnCallCard/index.esm.js.map +1 -0
- package/dist/components/HomePageAlertCard/Content.esm.js +76 -0
- package/dist/components/HomePageAlertCard/Content.esm.js.map +1 -0
- package/dist/components/HomePageAlertCard/index.esm.js +2 -0
- package/dist/components/HomePageAlertCard/index.esm.js.map +1 -0
- package/dist/components/HomePageIncidentCard/Content.esm.js +54 -0
- package/dist/components/HomePageIncidentCard/Content.esm.js.map +1 -0
- package/dist/components/HomePageIncidentCard/Context.esm.js +33 -0
- package/dist/components/HomePageIncidentCard/Context.esm.js.map +1 -0
- package/dist/components/HomePageIncidentCard/index.esm.js +3 -0
- package/dist/components/HomePageIncidentCard/index.esm.js.map +1 -0
- package/dist/components/HomePageOnCallCard/Content.esm.js +38 -0
- package/dist/components/HomePageOnCallCard/Content.esm.js.map +1 -0
- package/dist/components/HomePageOnCallCard/index.esm.js +6 -0
- package/dist/components/HomePageOnCallCard/index.esm.js.map +1 -0
- package/dist/components/IncidentListItem/index.esm.js +68 -0
- package/dist/components/IncidentListItem/index.esm.js.map +1 -0
- package/dist/components/styles.esm.js +34 -0
- package/dist/components/styles.esm.js.map +1 -0
- package/dist/components/utils.esm.js +19 -0
- package/dist/components/utils.esm.js.map +1 -0
- package/dist/hooks/useIncidentRequest.esm.js +65 -0
- package/dist/hooks/useIncidentRequest.esm.js.map +1 -0
- package/dist/hooks/useOnCallRequest.esm.js +116 -0
- package/dist/hooks/useOnCallRequest.esm.js.map +1 -0
- package/dist/index.d.ts +9 -6
- package/dist/index.esm.js +1 -3
- package/dist/index.esm.js.map +1 -1
- package/dist/plugin.esm.js +99 -0
- package/dist/plugin.esm.js.map +1 -0
- package/package.json +64 -31
- package/src/alpha.test.ts +9 -0
- package/src/alpha.tsx +111 -0
- package/src/api/client.test.ts +43 -0
- package/src/api/types.test.ts +15 -0
- package/src/api/types.ts +49796 -11325
- package/src/components/AlertListItem/index.tsx +82 -0
- package/src/components/EntityAlertCard/index.test.tsx +242 -0
- package/src/components/EntityAlertCard/index.tsx +168 -0
- package/src/components/EntityIncidentCard/index.test.tsx +135 -0
- package/src/components/EntityIncidentCard/index.tsx +3 -23
- package/src/components/EntityOnCallCard/index.test.tsx +134 -0
- package/src/components/EntityOnCallCard/index.tsx +301 -0
- package/src/components/HomePageAlertCard/Content.test.tsx +56 -0
- package/src/components/HomePageAlertCard/Content.tsx +85 -0
- package/src/components/HomePageAlertCard/index.tsx +1 -0
- package/src/components/HomePageIncidentCard/Content.test.tsx +4 -3
- package/src/components/HomePageIncidentCard/Content.tsx +2 -2
- package/src/components/HomePageIncidentCard/Context.tsx +2 -2
- package/src/components/HomePageOnCallCard/Content.test.tsx +90 -0
- package/src/components/HomePageOnCallCard/Content.tsx +58 -0
- package/src/components/HomePageOnCallCard/index.ts +3 -0
- package/src/components/IncidentListItem/index.tsx +3 -26
- package/src/components/styles.tsx +30 -0
- package/src/components/utils.tsx +24 -0
- package/src/hooks/useIncidentRequest.test.ts +189 -0
- package/src/hooks/useIncidentRequest.ts +56 -3
- package/src/hooks/useOnCallRequest.test.ts +52 -0
- package/src/hooks/useOnCallRequest.ts +141 -0
- package/src/index.ts +4 -0
- package/src/plugin.ts +45 -1
- package/src/setupTests.ts +2 -2
- package/dist/esm/index-3220e1e0.esm.js +0 -96
- package/dist/esm/index-3220e1e0.esm.js.map +0 -1
- package/dist/esm/index-5cb52808.esm.js +0 -73
- package/dist/esm/index-5cb52808.esm.js.map +0 -1
- package/dist/esm/index-632bbd58.esm.js +0 -117
- package/dist/esm/index-632bbd58.esm.js.map +0 -1
- package/dist/esm/index-8f5c4c5f.esm.js +0 -101
- package/dist/esm/index-8f5c4c5f.esm.js.map +0 -1
package/README.md
CHANGED
|
@@ -36,13 +36,30 @@ First, install the incident plugin via a CLI:
|
|
|
36
36
|
yarn --cwd packages/app add @incident-io/backstage
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
### Using the new extension-based architecture
|
|
40
|
+
|
|
41
|
+
Next, enable the plugin's extensions in `app-config.yaml`. No other code changes are needed:
|
|
42
|
+
|
|
43
|
+
```yaml
|
|
44
|
+
app:
|
|
45
|
+
...
|
|
46
|
+
extensions:
|
|
47
|
+
- entity-card:incident-io/EntityIncidentCard: true
|
|
48
|
+
- entity-card:incident-io/EntityAlertCard: true
|
|
49
|
+
- entity-card:incident-io/EntityOnCallCard: true
|
|
50
|
+
...
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
### Using the legacy Backstage system
|
|
55
|
+
|
|
39
56
|
Next, add the plugin to `EntityPage.tsx` in
|
|
40
57
|
`packages/app/src/components/catalog` by adding the following code snippets.
|
|
41
58
|
|
|
42
59
|
Add the following imports to the top of the file:
|
|
43
60
|
|
|
44
61
|
```ts
|
|
45
|
-
import { EntityIncidentCard } from "@incident-io/backstage";
|
|
62
|
+
import { EntityIncidentCard, EntityAlertCard, EntityOnCallCard } from "@incident-io/backstage";
|
|
46
63
|
```
|
|
47
64
|
|
|
48
65
|
Find `const overviewContent` in `EntityPage.tsx`, and add the following snippet
|
|
@@ -53,6 +70,12 @@ tag:
|
|
|
53
70
|
<Grid item md={6}>
|
|
54
71
|
<EntityIncidentCard />
|
|
55
72
|
</Grid>
|
|
73
|
+
<Grid item md={6}>
|
|
74
|
+
<EntityAlertCard />
|
|
75
|
+
</Grid>
|
|
76
|
+
<Grid item md={6}>
|
|
77
|
+
<EntityOnCallCard />
|
|
78
|
+
</Grid>
|
|
56
79
|
```
|
|
57
80
|
|
|
58
81
|
If you want to include the list of incidents in places like the page for a
|
|
@@ -96,3 +119,14 @@ If you don't have a custom field set up for one of these entities, then you
|
|
|
96
119
|
can omit that field completely. If you try and include the `EntityIncidentCard`
|
|
97
120
|
on the page for an entity which doesn't have the configuration, we'll show you
|
|
98
121
|
an error that directs you to update your config.
|
|
122
|
+
|
|
123
|
+
If you are using the on-call card, you must also provide the ID of the
|
|
124
|
+
incident.io catalog type that maps to your Backstage components. You can find
|
|
125
|
+
this in the URL when viewing the type
|
|
126
|
+
(e.g. `https://app.incident.io/~/catalog/01ABC...`):
|
|
127
|
+
|
|
128
|
+
```yaml
|
|
129
|
+
incident:
|
|
130
|
+
onCall:
|
|
131
|
+
catalogTypeId: "<id-of-backstage-component-catalog-type>"
|
|
132
|
+
```
|
package/config.d.ts
CHANGED
|
@@ -18,11 +18,6 @@ export interface Config {
|
|
|
18
18
|
* @visibility frontend
|
|
19
19
|
*/
|
|
20
20
|
incident?: {
|
|
21
|
-
/**
|
|
22
|
-
* The API key that provides access to the incident.io API.
|
|
23
|
-
* @see https://app.incident.io/settings/api-keys
|
|
24
|
-
*/
|
|
25
|
-
apiKey: string;
|
|
26
21
|
|
|
27
22
|
/**
|
|
28
23
|
* The base URL of the incident dashboard, only useful in development.
|
|
@@ -38,6 +33,17 @@ export interface Config {
|
|
|
38
33
|
/*
|
|
39
34
|
* @visibility frontend
|
|
40
35
|
*/
|
|
36
|
+
/*
|
|
37
|
+
* @visibility frontend
|
|
38
|
+
*/
|
|
39
|
+
onCall?: {
|
|
40
|
+
/**
|
|
41
|
+
* The catalog type ID used to look up on-call information for entities.
|
|
42
|
+
* @visibility frontend
|
|
43
|
+
*/
|
|
44
|
+
catalogTypeId?: string;
|
|
45
|
+
};
|
|
46
|
+
|
|
41
47
|
fields: {
|
|
42
48
|
/**
|
|
43
49
|
* The custom field ID that associated API entities to incidents.
|
package/dist/alpha.d.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { ApiBlueprint, createFrontendPlugin } from '@backstage/frontend-plugin-api';
|
|
3
|
+
import { fetchApiRef, discoveryApiRef } from '@backstage/core-plugin-api';
|
|
4
|
+
import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha';
|
|
5
|
+
import { HomePageWidgetBlueprint } from '@backstage/plugin-home-react/alpha';
|
|
6
|
+
import { IncidentApiRef, IncidentApi } from './api/client.esm.js';
|
|
7
|
+
|
|
8
|
+
const incidentApi = ApiBlueprint.make({
|
|
9
|
+
params: (defineParams) => defineParams({
|
|
10
|
+
api: IncidentApiRef,
|
|
11
|
+
deps: {
|
|
12
|
+
discoveryApi: discoveryApiRef,
|
|
13
|
+
fetchApi: fetchApiRef
|
|
14
|
+
},
|
|
15
|
+
factory: ({ discoveryApi, fetchApi }) => {
|
|
16
|
+
return new IncidentApi({
|
|
17
|
+
discoveryApi,
|
|
18
|
+
fetchApi
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
});
|
|
23
|
+
const entityIncidentCard = EntityCardBlueprint.make({
|
|
24
|
+
name: "EntityIncidentCard",
|
|
25
|
+
params: {
|
|
26
|
+
loader: async () => import('./components/EntityIncidentCard/index.esm.js').then((m) => /* @__PURE__ */ jsx(m.EntityIncidentCard, {}))
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
const entityAlertCard = EntityCardBlueprint.make({
|
|
30
|
+
name: "EntityAlertCard",
|
|
31
|
+
params: {
|
|
32
|
+
loader: async () => import('./components/EntityAlertCard/index.esm.js').then((m) => /* @__PURE__ */ jsx(m.EntityAlertCard, {}))
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
const entityOnCallCard = EntityCardBlueprint.make({
|
|
36
|
+
name: "EntityOnCallCard",
|
|
37
|
+
params: {
|
|
38
|
+
loader: async () => import('./components/EntityOnCallCard/index.esm.js').then((m) => /* @__PURE__ */ jsx(m.EntityOnCallCard, {}))
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
const homePageIncidentCard = HomePageWidgetBlueprint.make({
|
|
42
|
+
name: "HomePageIncidentCard",
|
|
43
|
+
params: {
|
|
44
|
+
title: "Ongoing Incidents",
|
|
45
|
+
components: () => import('./components/HomePageIncidentCard/index.esm.js'),
|
|
46
|
+
settings: {
|
|
47
|
+
schema: {
|
|
48
|
+
type: "object",
|
|
49
|
+
properties: {
|
|
50
|
+
filterType: {
|
|
51
|
+
type: "string",
|
|
52
|
+
title: "Filter Type",
|
|
53
|
+
description: "Whether to filter on status category or status",
|
|
54
|
+
oneOf: [
|
|
55
|
+
{ enum: ["status_category"], title: "Status Category" },
|
|
56
|
+
{ enum: ["status"], title: "Status" }
|
|
57
|
+
],
|
|
58
|
+
default: "status_category"
|
|
59
|
+
},
|
|
60
|
+
filter: {
|
|
61
|
+
type: "string",
|
|
62
|
+
title: "Filter",
|
|
63
|
+
description: "The filter to use. This is a string that will be passed to the API.",
|
|
64
|
+
default: "active"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
const homePageAlertCard = HomePageWidgetBlueprint.make({
|
|
72
|
+
name: "HomePageAlertCard",
|
|
73
|
+
params: {
|
|
74
|
+
title: "Ongoing Alerts",
|
|
75
|
+
components: () => import('./components/HomePageAlertCard/index.esm.js')
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
const homePageOnCallCard = HomePageWidgetBlueprint.make({
|
|
79
|
+
name: "HomePageOnCallCard",
|
|
80
|
+
params: {
|
|
81
|
+
title: "On-call",
|
|
82
|
+
components: () => import('./components/HomePageOnCallCard/index.esm.js')
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
const plugin = createFrontendPlugin({
|
|
86
|
+
pluginId: "incident",
|
|
87
|
+
extensions: [incidentApi, entityIncidentCard, entityAlertCard, homePageIncidentCard, homePageAlertCard, homePageOnCallCard, entityOnCallCard]
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
export { plugin as default };
|
|
91
|
+
//# sourceMappingURL=alpha.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alpha.esm.js","sources":["../src/alpha.tsx"],"sourcesContent":["import {\n ApiBlueprint,\n createFrontendPlugin,\n FrontendPlugin,\n} from \"@backstage/frontend-plugin-api\";\nimport {\n discoveryApiRef,\n fetchApiRef,\n} from \"@backstage/core-plugin-api\";\nimport { EntityCardBlueprint } from \"@backstage/plugin-catalog-react/alpha\";\nimport { HomePageWidgetBlueprint } from \"@backstage/plugin-home-react/alpha\";\nimport { IncidentApi, IncidentApiRef } from \"./api/client\";\n\nconst incidentApi = ApiBlueprint.make({\n params: defineParams =>\n defineParams({\n api: IncidentApiRef,\n deps: {\n discoveryApi: discoveryApiRef,\n fetchApi: fetchApiRef,\n },\n factory: ({ discoveryApi, fetchApi }) =>{ \n return new IncidentApi({ \n discoveryApi: discoveryApi, \n fetchApi: fetchApi \n });\n },\n }),\n});\n\nconst entityIncidentCard = EntityCardBlueprint.make({\n name: \"EntityIncidentCard\",\n params: {\n loader: async () => \n import(\"./components/EntityIncidentCard\").then(m=><m.EntityIncidentCard />),\n },\n});\n\nconst entityAlertCard = EntityCardBlueprint.make({\n name: \"EntityAlertCard\",\n params: {\n loader: async () => \n import(\"./components/EntityAlertCard\").then(m=><m.EntityAlertCard />),\n }\n});\n\nconst entityOnCallCard = EntityCardBlueprint.make({\n name: \"EntityOnCallCard\",\n params: {\n loader: async () => \n import(\"./components/EntityOnCallCard\").then(m=><m.EntityOnCallCard />),\n },\n});\n\nconst homePageIncidentCard = HomePageWidgetBlueprint.make({\n name: \"HomePageIncidentCard\",\n params: {\n title: \"Ongoing Incidents\", \n components: () => import(\"./components/HomePageIncidentCard\"),\n settings: {\n schema: {\n type: \"object\",\n properties: {\n filterType: {\n type: \"string\",\n title: \"Filter Type\",\n description: \"Whether to filter on status category or status\",\n oneOf: [\n { enum: [\"status_category\"], title: \"Status Category\" },\n { enum: [\"status\"], title: \"Status\" },\n ],\n default: \"status_category\",\n },\n filter: {\n type: \"string\",\n title: \"Filter\",\n description:\n \"The filter to use. This is a string that will be passed to the API.\",\n default: \"active\",\n },\n },\n },\n },\n },\n});\n\n\nconst homePageAlertCard = HomePageWidgetBlueprint.make({\n name: \"HomePageAlertCard\",\n params: {\n title: \"Ongoing Alerts\", \n components: () => import(\"./components/HomePageAlertCard\"),\n },\n});\n\n\n \nconst homePageOnCallCard = HomePageWidgetBlueprint.make({\n name: \"HomePageOnCallCard\",\n params: {\n title: \"On-call\",\n components: () => import(\"./components/HomePageOnCallCard\"),\n },\n});\n\nconst plugin: FrontendPlugin = createFrontendPlugin({\n pluginId: \"incident\",\n extensions: [incidentApi, entityIncidentCard, entityAlertCard, homePageIncidentCard, homePageAlertCard, homePageOnCallCard, entityOnCallCard], \n });\n\nexport default plugin;"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createApiRef } from '@backstage/core-plugin-api';
|
|
2
|
+
|
|
3
|
+
const IncidentApiRef = createApiRef({
|
|
4
|
+
id: "plugin.incident.service"
|
|
5
|
+
});
|
|
6
|
+
const DEFAULT_PROXY_PATH = "/incident/api";
|
|
7
|
+
class IncidentApi {
|
|
8
|
+
discoveryApi;
|
|
9
|
+
fetchApi;
|
|
10
|
+
proxyPath;
|
|
11
|
+
constructor(opts) {
|
|
12
|
+
this.discoveryApi = opts.discoveryApi;
|
|
13
|
+
this.fetchApi = opts.fetchApi;
|
|
14
|
+
this.proxyPath = opts.proxyPath ?? DEFAULT_PROXY_PATH;
|
|
15
|
+
}
|
|
16
|
+
async request({
|
|
17
|
+
path,
|
|
18
|
+
method = "GET",
|
|
19
|
+
body
|
|
20
|
+
}) {
|
|
21
|
+
const apiUrl = await this.discoveryApi.getBaseUrl("proxy") + this.proxyPath;
|
|
22
|
+
const resp = await this.fetchApi.fetch(`${apiUrl}${path}`, {
|
|
23
|
+
method,
|
|
24
|
+
body
|
|
25
|
+
});
|
|
26
|
+
if (!resp.ok) {
|
|
27
|
+
throw new Error(`${resp.status} ${resp.statusText}`);
|
|
28
|
+
}
|
|
29
|
+
return await resp.json();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { IncidentApi, IncidentApiRef };
|
|
34
|
+
//# sourceMappingURL=client.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.esm.js","sources":["../../src/api/client.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n DiscoveryApi,\n FetchApi,\n createApiRef,\n} from \"@backstage/core-plugin-api\";\n\nexport const IncidentApiRef = createApiRef<Incident>({\n id: \"plugin.incident.service\",\n});\n\ntype HTTPMethods = \"GET\" | \"PUT\" | \"POST\" | \"PATCH\" | \"DELETE\";\n\nexport interface Incident {\n request<T>({\n method,\n path,\n body,\n }: {\n method?: HTTPMethods;\n path: string;\n body?: string;\n }): Promise<T>;\n}\n\nconst DEFAULT_PROXY_PATH = \"/incident/api\";\n\ntype Options = {\n discoveryApi: DiscoveryApi;\n fetchApi: FetchApi;\n proxyPath?: string;\n};\n\nexport class IncidentApi implements Incident {\n private readonly discoveryApi: DiscoveryApi;\n private readonly fetchApi: FetchApi;\n private readonly proxyPath: string;\n\n constructor(opts: Options) {\n this.discoveryApi = opts.discoveryApi;\n this.fetchApi = opts.fetchApi;\n this.proxyPath = opts.proxyPath ?? DEFAULT_PROXY_PATH;\n }\n\n async request<T = any>({\n path,\n method = \"GET\",\n body,\n }: {\n path: string;\n method?: HTTPMethods;\n body?: string;\n }): Promise<T> {\n const apiUrl =\n (await this.discoveryApi.getBaseUrl(\"proxy\")) + this.proxyPath;\n\n const resp = await this.fetchApi.fetch(`${apiUrl}${path}`, {\n method: method,\n body: body,\n });\n if (!resp.ok) {\n throw new Error(`${resp.status} ${resp.statusText}`);\n }\n\n return await resp.json();\n }\n}\n"],"names":[],"mappings":";;AAqBO,MAAM,iBAAiB,YAAA,CAAuB;AAAA,EACnD,EAAA,EAAI;AACN,CAAC;AAgBD,MAAM,kBAAA,GAAqB,eAAA;AAQpB,MAAM,WAAA,CAAgC;AAAA,EAC1B,YAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA,EAEjB,YAAY,IAAA,EAAe;AACzB,IAAA,IAAA,CAAK,eAAe,IAAA,CAAK,YAAA;AACzB,IAAA,IAAA,CAAK,WAAW,IAAA,CAAK,QAAA;AACrB,IAAA,IAAA,CAAK,SAAA,GAAY,KAAK,SAAA,IAAa,kBAAA;AAAA,EACrC;AAAA,EAEA,MAAM,OAAA,CAAiB;AAAA,IACrB,IAAA;AAAA,IACA,MAAA,GAAS,KAAA;AAAA,IACT;AAAA,GACF,EAIe;AACb,IAAA,MAAM,SACH,MAAM,IAAA,CAAK,aAAa,UAAA,CAAW,OAAO,IAAK,IAAA,CAAK,SAAA;AAEvD,IAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,QAAA,CAAS,MAAM,CAAA,EAAG,MAAM,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI;AAAA,MACzD,MAAA;AAAA,MACA;AAAA,KACD,CAAA;AACD,IAAA,IAAI,CAAC,KAAK,EAAA,EAAI;AACZ,MAAA,MAAM,IAAI,MAAM,CAAA,EAAG,IAAA,CAAK,MAAM,CAAA,CAAA,EAAI,IAAA,CAAK,UAAU,CAAA,CAAE,CAAA;AAAA,IACrD;AAEA,IAAA,OAAO,MAAM,KAAK,IAAA,EAAK;AAAA,EACzB;AACF;;;;"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
2
|
+
import { DateTime } from 'luxon';
|
|
3
|
+
import { ListItem, ListItemText, Typography, Chip, ListItemSecondaryAction, Tooltip, IconButton } from '@material-ui/core';
|
|
4
|
+
import OpenInBrowserIcon from '@material-ui/icons/OpenInBrowser';
|
|
5
|
+
import { useStyles } from '../styles.esm.js';
|
|
6
|
+
|
|
7
|
+
const AlertListItem = ({
|
|
8
|
+
baseUrl,
|
|
9
|
+
alert,
|
|
10
|
+
source,
|
|
11
|
+
priority
|
|
12
|
+
}) => {
|
|
13
|
+
const classes = useStyles();
|
|
14
|
+
const sinceCreatedLabel = DateTime.fromISO(alert.created_at).toRelative({ base: DateTime.now() });
|
|
15
|
+
return /* @__PURE__ */ jsxs(ListItem, { dense: true, children: [
|
|
16
|
+
/* @__PURE__ */ jsx(
|
|
17
|
+
ListItemText,
|
|
18
|
+
{
|
|
19
|
+
primary: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
20
|
+
/* @__PURE__ */ jsx(
|
|
21
|
+
Chip,
|
|
22
|
+
{
|
|
23
|
+
"data-testid": `chip-${alert.status}`,
|
|
24
|
+
label: alert.status,
|
|
25
|
+
size: "small",
|
|
26
|
+
variant: "outlined",
|
|
27
|
+
className: ["firing"].includes(alert.status) ? classes.error : classes.success
|
|
28
|
+
}
|
|
29
|
+
),
|
|
30
|
+
alert.title,
|
|
31
|
+
priority && /* @__PURE__ */ jsx(
|
|
32
|
+
Chip,
|
|
33
|
+
{
|
|
34
|
+
"data-testid": `chip-${priority}`,
|
|
35
|
+
label: priority,
|
|
36
|
+
size: "small",
|
|
37
|
+
variant: "outlined"
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
] }),
|
|
41
|
+
primaryTypographyProps: {
|
|
42
|
+
variant: "body1",
|
|
43
|
+
className: classes.listItemPrimary
|
|
44
|
+
},
|
|
45
|
+
secondary: /* @__PURE__ */ jsxs(Typography, { noWrap: true, variant: "body2", color: "textSecondary", children: [
|
|
46
|
+
"Created ",
|
|
47
|
+
sinceCreatedLabel,
|
|
48
|
+
" from ",
|
|
49
|
+
source,
|
|
50
|
+
"."
|
|
51
|
+
] })
|
|
52
|
+
}
|
|
53
|
+
),
|
|
54
|
+
/* @__PURE__ */ jsx(ListItemSecondaryAction, { children: /* @__PURE__ */ jsx(Tooltip, { title: "View in incident.io", placement: "top", children: /* @__PURE__ */ jsx(
|
|
55
|
+
IconButton,
|
|
56
|
+
{
|
|
57
|
+
href: `${baseUrl}/on-call/alerts/${alert.id}/details`,
|
|
58
|
+
target: "_blank",
|
|
59
|
+
rel: "noopener noreferrer",
|
|
60
|
+
color: "primary",
|
|
61
|
+
children: /* @__PURE__ */ jsx(OpenInBrowserIcon, {})
|
|
62
|
+
}
|
|
63
|
+
) }) })
|
|
64
|
+
] }, alert.id);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export { AlertListItem };
|
|
68
|
+
//# sourceMappingURL=index.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":["../../../src/components/AlertListItem/index.tsx"],"sourcesContent":["import { DateTime } from \"luxon\";\nimport {\n Chip,\n IconButton,\n ListItem,\n ListItemSecondaryAction,\n ListItemText,\n Tooltip,\n Typography,\n} from \"@material-ui/core\";\nimport OpenInBrowserIcon from \"@material-ui/icons/OpenInBrowser\";\nimport { components } from \"../../api/types\";\nimport { useStyles } from \"../styles\";\n\n// Single item in the list of on-going alerts.\nexport const AlertListItem = ({\n baseUrl,\n alert,\n source,\n priority\n}: {\n baseUrl: string;\n alert: components[\"schemas\"][\"AlertV2\"];\n source: string;\n priority?: string; \n}) => {\n const classes = useStyles();\n\n const sinceCreatedLabel = DateTime.fromISO(alert.created_at).toRelative({ base: DateTime.now() });\n\n return (\n <ListItem dense key={alert.id}>\n <ListItemText\n primary={\n <>\n <Chip\n data-testid={`chip-${alert.status}`}\n label={alert.status}\n size=\"small\"\n variant=\"outlined\"\n className={\n [\"firing\"].includes(alert.status)\n ? classes.error\n : classes.success\n }\n />\n {alert.title}\n {priority && (\n <Chip\n data-testid={`chip-${priority}`}\n label={priority}\n size=\"small\"\n variant=\"outlined\"\n />\n )}\n </>\n }\n primaryTypographyProps={{\n variant: \"body1\",\n className: classes.listItemPrimary,\n }}\n secondary={\n <Typography noWrap variant=\"body2\" color=\"textSecondary\">\n Created {sinceCreatedLabel} from {source}.\n </Typography>\n }\n />\n <ListItemSecondaryAction>\n <Tooltip title=\"View in incident.io\" placement=\"top\">\n <IconButton\n href={`${baseUrl}/on-call/alerts/${alert.id}/details`}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n color=\"primary\"\n >\n <OpenInBrowserIcon />\n </IconButton>\n </Tooltip>\n </ListItemSecondaryAction>\n </ListItem>\n );\n};\n"],"names":[],"mappings":";;;;;;AAeO,MAAM,gBAAgB,CAAC;AAAA,EAC5B,OAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAA,KAKM;AACJ,EAAA,MAAM,UAAU,SAAA,EAAU;AAE1B,EAAA,MAAM,iBAAA,GAAoB,QAAA,CAAS,OAAA,CAAQ,KAAA,CAAM,UAAU,CAAA,CAAE,UAAA,CAAW,EAAE,IAAA,EAAM,QAAA,CAAS,GAAA,EAAI,EAAG,CAAA;AAEhG,EAAA,uBACE,IAAA,CAAC,QAAA,EAAA,EAAS,KAAA,EAAK,IAAA,EACb,QAAA,EAAA;AAAA,oBAAA,GAAA;AAAA,MAAC,YAAA;AAAA,MAAA;AAAA,QACC,yBACE,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,0BAAA,GAAA;AAAA,YAAC,IAAA;AAAA,YAAA;AAAA,cACC,aAAA,EAAa,CAAA,KAAA,EAAQ,KAAA,CAAM,MAAM,CAAA,CAAA;AAAA,cACjC,OAAO,KAAA,CAAM,MAAA;AAAA,cACb,IAAA,EAAK,OAAA;AAAA,cACL,OAAA,EAAQ,UAAA;AAAA,cACR,SAAA,EACE,CAAC,QAAQ,CAAA,CAAE,QAAA,CAAS,MAAM,MAAM,CAAA,GAC5B,OAAA,CAAQ,KAAA,GACR,OAAA,CAAQ;AAAA;AAAA,WAEhB;AAAA,UACC,KAAA,CAAM,KAAA;AAAA,UACN,QAAA,oBACC,GAAA;AAAA,YAAC,IAAA;AAAA,YAAA;AAAA,cACC,aAAA,EAAa,QAAQ,QAAQ,CAAA,CAAA;AAAA,cAC7B,KAAA,EAAO,QAAA;AAAA,cACP,IAAA,EAAK,OAAA;AAAA,cACL,OAAA,EAAQ;AAAA;AAAA;AACV,SAAA,EAEJ,CAAA;AAAA,QAEF,sBAAA,EAAwB;AAAA,UACtB,OAAA,EAAS,OAAA;AAAA,UACT,WAAW,OAAA,CAAQ;AAAA,SACrB;AAAA,QACA,SAAA,uBACG,UAAA,EAAA,EAAW,MAAA,EAAM,MAAC,OAAA,EAAQ,OAAA,EAAQ,OAAM,eAAA,EAAgB,QAAA,EAAA;AAAA,UAAA,UAAA;AAAA,UAC9C,iBAAA;AAAA,UAAkB,QAAA;AAAA,UAAO,MAAA;AAAA,UAAO;AAAA,SAAA,EAC3C;AAAA;AAAA,KAEJ;AAAA,wBACC,uBAAA,EAAA,EACC,QAAA,kBAAA,GAAA,CAAC,WAAQ,KAAA,EAAM,qBAAA,EAAsB,WAAU,KAAA,EAC7C,QAAA,kBAAA,GAAA;AAAA,MAAC,UAAA;AAAA,MAAA;AAAA,QACC,IAAA,EAAM,CAAA,EAAG,OAAO,CAAA,gBAAA,EAAmB,MAAM,EAAE,CAAA,QAAA,CAAA;AAAA,QAC3C,MAAA,EAAO,QAAA;AAAA,QACP,GAAA,EAAI,qBAAA;AAAA,QACJ,KAAA,EAAM,SAAA;AAAA,QAEN,8BAAC,iBAAA,EAAA,EAAkB;AAAA;AAAA,OAEvB,CAAA,EACF;AAAA,GAAA,EAAA,EA/CmB,MAAM,EAgD3B,CAAA;AAEJ;;;;"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
+
import { Progress } from '@backstage/core-components';
|
|
3
|
+
import { useApi, configApiRef } from '@backstage/core-plugin-api';
|
|
4
|
+
import { useEntity } from '@backstage/plugin-catalog-react';
|
|
5
|
+
import { Card, CardHeader, IconButton, Divider, Tabs, Tab, CardContent, Typography, Box, List } from '@material-ui/core';
|
|
6
|
+
import Link from '@material-ui/core/Link';
|
|
7
|
+
import { Alert } from '@material-ui/lab';
|
|
8
|
+
import CachedIcon from '@material-ui/icons/Cached';
|
|
9
|
+
import { useState } from 'react';
|
|
10
|
+
import { useIncidentList, useIncidentAlertList, useAlertList, useAlertSourceList, useIdentity } from '../../hooks/useIncidentRequest.esm.js';
|
|
11
|
+
import { getEntityFieldID } from '../utils.esm.js';
|
|
12
|
+
import { AlertListItem } from '../AlertListItem/index.esm.js';
|
|
13
|
+
|
|
14
|
+
const STATUS_TABS = [
|
|
15
|
+
{ label: "Firing", value: "firing" },
|
|
16
|
+
{ label: "Resolved", value: "resolved" },
|
|
17
|
+
{ label: "All", value: void 0 }
|
|
18
|
+
];
|
|
19
|
+
const IncorrectConfigCard = () => /* @__PURE__ */ jsxs(Card, { children: [
|
|
20
|
+
/* @__PURE__ */ jsx(CardHeader, { title: "Alerts" }),
|
|
21
|
+
/* @__PURE__ */ jsx(Divider, {}),
|
|
22
|
+
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx(Typography, { variant: "subtitle1", children: "No custom field configuration was found. In order to display alerts, this entity must be mapped to an incident.io custom field ID in Backstage's app-config.yaml." }) })
|
|
23
|
+
] });
|
|
24
|
+
const EntityAlertCard = () => {
|
|
25
|
+
const [statusFilter, setStatusFilter] = useState("firing");
|
|
26
|
+
const [reload, setReload] = useState(false);
|
|
27
|
+
const config = useApi(configApiRef);
|
|
28
|
+
const { entity } = useEntity();
|
|
29
|
+
const entityFieldID = getEntityFieldID(config, entity);
|
|
30
|
+
const entityID = `${entity.metadata.namespace}/${entity.metadata.name}`;
|
|
31
|
+
const incidentQuery = new URLSearchParams();
|
|
32
|
+
incidentQuery.set(`custom_field[${entityFieldID}][one_of]`, entityID);
|
|
33
|
+
const { value: incidentsResponse, loading: incidentsLoading } = useIncidentList(incidentQuery, [reload]);
|
|
34
|
+
const incidentIds = (incidentsResponse?.incidents ?? []).map((i) => i.id);
|
|
35
|
+
const { value: incidentAlertsResponse, loading: incidentAlertsLoading } = useIncidentAlertList(incidentIds, [reload]);
|
|
36
|
+
const linkedAlertIds = new Set(
|
|
37
|
+
(incidentAlertsResponse?.incident_alerts ?? []).map((ia) => ia.alert.id)
|
|
38
|
+
);
|
|
39
|
+
const { value: alertsResponse, loading: alertsLoading, error } = useAlertList(statusFilter, [reload]);
|
|
40
|
+
const { value: sourcesResponse } = useAlertSourceList();
|
|
41
|
+
const { value: identityResponse } = useIdentity();
|
|
42
|
+
const allAlerts = alertsResponse?.alerts ?? [];
|
|
43
|
+
const alerts = incidentIds.length > 0 ? allAlerts.filter((a) => linkedAlertIds.has(a.id)) : [];
|
|
44
|
+
const baseUrl = identityResponse?.identity.dashboard_url ?? "";
|
|
45
|
+
const sourceById = Object.fromEntries(
|
|
46
|
+
(sourcesResponse?.alert_sources ?? []).map((s) => [s.id, s])
|
|
47
|
+
);
|
|
48
|
+
const currentTabIndex = STATUS_TABS.findIndex((t) => t.value === statusFilter);
|
|
49
|
+
if (!entityFieldID) {
|
|
50
|
+
return /* @__PURE__ */ jsx(IncorrectConfigCard, {});
|
|
51
|
+
}
|
|
52
|
+
if (incidentsLoading || incidentAlertsLoading || alertsLoading) {
|
|
53
|
+
return /* @__PURE__ */ jsx(Progress, {});
|
|
54
|
+
}
|
|
55
|
+
return /* @__PURE__ */ jsxs(Card, { children: [
|
|
56
|
+
/* @__PURE__ */ jsx(
|
|
57
|
+
CardHeader,
|
|
58
|
+
{
|
|
59
|
+
title: "incident.io Alerts",
|
|
60
|
+
action: /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
|
|
61
|
+
IconButton,
|
|
62
|
+
{
|
|
63
|
+
component: Link,
|
|
64
|
+
"aria-label": "Refresh",
|
|
65
|
+
disabled: false,
|
|
66
|
+
title: "Refresh",
|
|
67
|
+
onClick: () => setReload(!reload),
|
|
68
|
+
children: /* @__PURE__ */ jsx(CachedIcon, {})
|
|
69
|
+
}
|
|
70
|
+
) })
|
|
71
|
+
}
|
|
72
|
+
),
|
|
73
|
+
/* @__PURE__ */ jsx(Divider, {}),
|
|
74
|
+
/* @__PURE__ */ jsx(
|
|
75
|
+
Tabs,
|
|
76
|
+
{
|
|
77
|
+
value: currentTabIndex,
|
|
78
|
+
onChange: (_, idx) => setStatusFilter(STATUS_TABS[idx].value),
|
|
79
|
+
indicatorColor: "primary",
|
|
80
|
+
textColor: "primary",
|
|
81
|
+
children: STATUS_TABS.map((tab) => /* @__PURE__ */ jsx(Tab, { label: tab.label }, tab.label))
|
|
82
|
+
}
|
|
83
|
+
),
|
|
84
|
+
/* @__PURE__ */ jsx(Divider, {}),
|
|
85
|
+
/* @__PURE__ */ jsxs(CardContent, { children: [
|
|
86
|
+
error && /* @__PURE__ */ jsx(Alert, { severity: "error", children: error.message }),
|
|
87
|
+
!error && alerts.length === 0 && /* @__PURE__ */ jsx(Typography, { variant: "subtitle1", children: "No alerts." }),
|
|
88
|
+
!error && alerts.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
89
|
+
/* @__PURE__ */ jsxs(Typography, { variant: "subtitle1", children: [
|
|
90
|
+
"There are ",
|
|
91
|
+
/* @__PURE__ */ jsx("strong", { children: alerts.length }),
|
|
92
|
+
" ",
|
|
93
|
+
statusFilter ?? "",
|
|
94
|
+
" alerts."
|
|
95
|
+
] }),
|
|
96
|
+
/* @__PURE__ */ jsx(Box, { style: { maxHeight: 400, overflowY: "auto" }, children: /* @__PURE__ */ jsx(List, { dense: true, children: alerts.map((alert) => /* @__PURE__ */ jsx(
|
|
97
|
+
AlertListItem,
|
|
98
|
+
{
|
|
99
|
+
alert,
|
|
100
|
+
baseUrl,
|
|
101
|
+
source: sourceById[alert.alert_source_id]?.name ?? "-",
|
|
102
|
+
priority: alert.attributes.find((a) => a.attribute.name === "Priority")?.value?.label
|
|
103
|
+
},
|
|
104
|
+
alert.id
|
|
105
|
+
)) }) })
|
|
106
|
+
] }),
|
|
107
|
+
baseUrl && /* @__PURE__ */ jsx(Typography, { variant: "subtitle1", children: /* @__PURE__ */ jsx(Link, { target: "_blank", href: `${baseUrl}/on-call/alerts`, children: "View all alerts" }) })
|
|
108
|
+
] })
|
|
109
|
+
] });
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export { EntityAlertCard };
|
|
113
|
+
//# sourceMappingURL=index.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":["../../../src/components/EntityAlertCard/index.tsx"],"sourcesContent":["import { Progress } from \"@backstage/core-components\";\nimport { configApiRef, useApi } from \"@backstage/core-plugin-api\";\nimport { useEntity } from \"@backstage/plugin-catalog-react\";\nimport {\n Box,\n Card,\n CardContent,\n CardHeader,\n Divider,\n IconButton,\n List,\n Tab,\n Tabs,\n Typography,\n} from \"@material-ui/core\";\nimport Link from \"@material-ui/core/Link\";\nimport { Alert } from \"@material-ui/lab\";\nimport CachedIcon from \"@material-ui/icons/Cached\";\nimport { useState } from \"react\";\nimport {\n useAlertList,\n useAlertSourceList,\n useIdentity,\n useIncidentAlertList,\n useIncidentList,\n} from \"../../hooks/useIncidentRequest\";\nimport { getEntityFieldID } from \"../utils\";\nimport { AlertListItem } from \"../AlertListItem\";\n\ntype StatusFilter = \"firing\" | \"resolved\" | undefined;\n\nconst STATUS_TABS: { label: string; value: StatusFilter }[] = [\n { label: \"Firing\", value: \"firing\" },\n { label: \"Resolved\", value: \"resolved\" },\n { label: \"All\", value: undefined },\n];\n\nconst IncorrectConfigCard = () => (\n <Card>\n <CardHeader title=\"Alerts\" />\n <Divider />\n <CardContent>\n <Typography variant=\"subtitle1\">\n No custom field configuration was found. In order to display alerts,\n this entity must be mapped to an incident.io custom field ID in\n Backstage's app-config.yaml.\n </Typography>\n </CardContent>\n </Card>\n);\n\nexport const EntityAlertCard = () => {\n const [statusFilter, setStatusFilter] = useState<StatusFilter>(\"firing\");\n const [reload, setReload] = useState(false);\n\n const config = useApi(configApiRef);\n const { entity } = useEntity();\n const entityFieldID = getEntityFieldID(config, entity);\n const entityID = `${entity.metadata.namespace}/${entity.metadata.name}`;\n\n // query for incidents associated with this entity\n const incidentQuery = new URLSearchParams();\n incidentQuery.set(`custom_field[${entityFieldID}][one_of]`, entityID);\n\n const { value: incidentsResponse, loading: incidentsLoading } =\n useIncidentList(incidentQuery, [reload]);\n const incidentIds = (incidentsResponse?.incidents ?? []).map(i => i.id);\n\n // query for alerts linked to those incidents\n const { value: incidentAlertsResponse, loading: incidentAlertsLoading } =\n useIncidentAlertList(incidentIds, [reload]);\n const linkedAlertIds = new Set(\n (incidentAlertsResponse?.incident_alerts ?? []).map(ia => ia.alert.id),\n );\n\n const { value: alertsResponse, loading: alertsLoading, error } =\n useAlertList(statusFilter, [reload]);\n const { value: sourcesResponse } = useAlertSourceList();\n const { value: identityResponse } = useIdentity();\n\n // get alerts for this entity's incidents\n const allAlerts = alertsResponse?.alerts ?? [];\n const alerts = incidentIds.length > 0\n ? allAlerts.filter(a => linkedAlertIds.has(a.id))\n : [];\n\n const baseUrl = identityResponse?.identity.dashboard_url ?? \"\";\n\n const sourceById = Object.fromEntries(\n (sourcesResponse?.alert_sources ?? []).map(s => [s.id, s]),\n );\n\n const currentTabIndex = STATUS_TABS.findIndex(t => t.value === statusFilter);\n if (!entityFieldID) {\n return <IncorrectConfigCard />;\n }\n\n if (incidentsLoading || incidentAlertsLoading || alertsLoading) {\n return <Progress />;\n }\n\n return (\n <Card>\n <CardHeader\n title=\"incident.io Alerts\"\n action={\n <>\n <IconButton\n component={Link}\n aria-label=\"Refresh\"\n disabled={false}\n title=\"Refresh\"\n onClick={() => setReload(!reload)}\n >\n <CachedIcon />\n </IconButton>\n </>\n }\n />\n <Divider />\n <Tabs\n value={currentTabIndex}\n onChange={(_, idx) => setStatusFilter(STATUS_TABS[idx].value)}\n indicatorColor=\"primary\"\n textColor=\"primary\"\n >\n {STATUS_TABS.map(tab => (\n <Tab key={tab.label} label={tab.label} />\n ))}\n </Tabs>\n <Divider />\n <CardContent>\n {error && <Alert severity=\"error\">{error.message}</Alert>}\n {!error && alerts.length === 0 && (\n <Typography variant=\"subtitle1\">No alerts.</Typography>\n )}\n {!error && alerts.length > 0 && (\n <>\n <Typography variant=\"subtitle1\">\n There are <strong>{alerts.length}</strong>{\" \"}\n {statusFilter ?? \"\"} alerts.\n </Typography>\n <Box style={{ maxHeight: 400, overflowY: \"auto\" }}>\n <List dense>\n {alerts.map(alert => (\n <AlertListItem\n key={alert.id}\n alert={alert}\n baseUrl={baseUrl}\n source={sourceById[alert.alert_source_id]?.name ?? \"-\"}\n priority={alert.attributes.find(a => a.attribute.name === \"Priority\")?.value?.label}\n />\n ))}\n </List>\n </Box>\n </>\n )}\n {baseUrl && (\n <Typography variant=\"subtitle1\">\n <Link target=\"_blank\" href={`${baseUrl}/on-call/alerts`}>\n View all alerts\n </Link>\n </Typography>\n )}\n </CardContent>\n </Card>\n );\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
+
import { Progress, HeaderIconLinkRow } from '@backstage/core-components';
|
|
3
|
+
import { useApi, configApiRef } from '@backstage/core-plugin-api';
|
|
4
|
+
import { useEntity } from '@backstage/plugin-catalog-react';
|
|
5
|
+
import { Card, CardHeader, IconButton, Divider, CardContent, Typography, List } from '@material-ui/core';
|
|
6
|
+
import Link from '@material-ui/core/Link';
|
|
7
|
+
import CachedIcon from '@material-ui/icons/Cached';
|
|
8
|
+
import HistoryIcon from '@material-ui/icons/History';
|
|
9
|
+
import WhatshotIcon from '@material-ui/icons/Whatshot';
|
|
10
|
+
import { Alert } from '@material-ui/lab';
|
|
11
|
+
import { useState } from 'react';
|
|
12
|
+
import { useIdentity, useIncidentList } from '../../hooks/useIncidentRequest.esm.js';
|
|
13
|
+
import { IncidentListItem } from '../IncidentListItem/index.esm.js';
|
|
14
|
+
import { getEntityFieldID } from '../utils.esm.js';
|
|
15
|
+
|
|
16
|
+
const IncorrectConfigCard = () => {
|
|
17
|
+
return /* @__PURE__ */ jsxs(Card, { children: [
|
|
18
|
+
/* @__PURE__ */ jsx(CardHeader, { title: "incident.io" }),
|
|
19
|
+
/* @__PURE__ */ jsx(Divider, {}),
|
|
20
|
+
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx(Typography, { variant: "subtitle1", children: "No custom field configuration was found. In order to display incidents, this entity must be mapped to an incident.io custom field ID in Backstage's app-config.yaml." }) })
|
|
21
|
+
] });
|
|
22
|
+
};
|
|
23
|
+
const EntityIncidentCard = ({
|
|
24
|
+
maxIncidents = 2
|
|
25
|
+
}) => {
|
|
26
|
+
const config = useApi(configApiRef);
|
|
27
|
+
const { entity } = useEntity();
|
|
28
|
+
const {
|
|
29
|
+
value: identityResponse,
|
|
30
|
+
loading: identityResponseLoading,
|
|
31
|
+
error: identityResponseError
|
|
32
|
+
} = useIdentity();
|
|
33
|
+
const [reload, setReload] = useState(false);
|
|
34
|
+
const entityFieldID = getEntityFieldID(config, entity);
|
|
35
|
+
const entityID = `${entity.metadata.namespace}/${entity.metadata.name}`;
|
|
36
|
+
const query = new URLSearchParams();
|
|
37
|
+
query.set(`custom_field[${entityFieldID}][one_of]`, entityID);
|
|
38
|
+
const queryLive = new URLSearchParams(query);
|
|
39
|
+
queryLive.set(`status_category[one_of]`, "active");
|
|
40
|
+
const {
|
|
41
|
+
value: incidentsResponse,
|
|
42
|
+
loading: incidentsLoading,
|
|
43
|
+
error: incidentsError
|
|
44
|
+
} = useIncidentList(queryLive, [reload]);
|
|
45
|
+
const incidents = incidentsResponse?.incidents;
|
|
46
|
+
if (!entityFieldID) {
|
|
47
|
+
return /* @__PURE__ */ jsx(IncorrectConfigCard, {});
|
|
48
|
+
}
|
|
49
|
+
if (incidentsLoading || identityResponseLoading || !identityResponse) {
|
|
50
|
+
return /* @__PURE__ */ jsx(Progress, {});
|
|
51
|
+
}
|
|
52
|
+
const baseUrl = identityResponse.identity.dashboard_url;
|
|
53
|
+
const createIncidentLink = {
|
|
54
|
+
label: "Declare incident",
|
|
55
|
+
disabled: false,
|
|
56
|
+
icon: /* @__PURE__ */ jsx(WhatshotIcon, {}),
|
|
57
|
+
href: `${baseUrl}/incidents/create`
|
|
58
|
+
};
|
|
59
|
+
const viewIncidentsLink = {
|
|
60
|
+
label: "View past incidents",
|
|
61
|
+
disabled: false,
|
|
62
|
+
icon: /* @__PURE__ */ jsx(HistoryIcon, {}),
|
|
63
|
+
href: `${baseUrl}/incidents?${query.toString()}`
|
|
64
|
+
};
|
|
65
|
+
return /* @__PURE__ */ jsxs(Card, { children: [
|
|
66
|
+
/* @__PURE__ */ jsx(
|
|
67
|
+
CardHeader,
|
|
68
|
+
{
|
|
69
|
+
title: "incident.io",
|
|
70
|
+
action: /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
|
|
71
|
+
IconButton,
|
|
72
|
+
{
|
|
73
|
+
component: Link,
|
|
74
|
+
"aria-label": "Refresh",
|
|
75
|
+
disabled: false,
|
|
76
|
+
title: "Refresh",
|
|
77
|
+
onClick: () => setReload(!reload),
|
|
78
|
+
children: /* @__PURE__ */ jsx(CachedIcon, {})
|
|
79
|
+
}
|
|
80
|
+
) }),
|
|
81
|
+
subheader: /* @__PURE__ */ jsx(HeaderIconLinkRow, { links: [createIncidentLink, viewIncidentsLink] })
|
|
82
|
+
}
|
|
83
|
+
),
|
|
84
|
+
/* @__PURE__ */ jsx(Divider, {}),
|
|
85
|
+
/* @__PURE__ */ jsxs(CardContent, { children: [
|
|
86
|
+
incidentsError && /* @__PURE__ */ jsx(Alert, { severity: "error", children: incidentsError.message }),
|
|
87
|
+
identityResponseError && /* @__PURE__ */ jsx(Alert, { severity: "error", children: identityResponseError.message }),
|
|
88
|
+
!incidentsLoading && !incidentsError && incidents && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
89
|
+
incidents && incidents.length > 0 && /* @__PURE__ */ jsxs(Typography, { variant: "subtitle1", children: [
|
|
90
|
+
"There are ",
|
|
91
|
+
/* @__PURE__ */ jsx("strong", { children: incidents.length }),
|
|
92
|
+
" ongoing incidents involving ",
|
|
93
|
+
/* @__PURE__ */ jsx("strong", { children: entity.metadata.name }),
|
|
94
|
+
"."
|
|
95
|
+
] }),
|
|
96
|
+
incidents && incidents.length === 0 && /* @__PURE__ */ jsx(Typography, { variant: "subtitle1", children: "No ongoing incidents." }),
|
|
97
|
+
/* @__PURE__ */ jsx(List, { dense: true, children: incidents?.slice(0, maxIncidents)?.map((incident) => {
|
|
98
|
+
return /* @__PURE__ */ jsx(
|
|
99
|
+
IncidentListItem,
|
|
100
|
+
{
|
|
101
|
+
incident,
|
|
102
|
+
baseUrl
|
|
103
|
+
},
|
|
104
|
+
incident.id
|
|
105
|
+
);
|
|
106
|
+
}) }),
|
|
107
|
+
/* @__PURE__ */ jsxs(Typography, { variant: "subtitle1", children: [
|
|
108
|
+
"Click to",
|
|
109
|
+
" ",
|
|
110
|
+
/* @__PURE__ */ jsx(
|
|
111
|
+
Link,
|
|
112
|
+
{
|
|
113
|
+
target: "_blank",
|
|
114
|
+
href: `${baseUrl}/incidents?${queryLive.toString()}`,
|
|
115
|
+
children: "see more."
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
] })
|
|
119
|
+
] })
|
|
120
|
+
] })
|
|
121
|
+
] });
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export { EntityIncidentCard };
|
|
125
|
+
//# sourceMappingURL=index.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":["../../../src/components/EntityIncidentCard/index.tsx"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n HeaderIconLinkRow,\n IconLinkVerticalProps,\n Progress,\n} from \"@backstage/core-components\";\nimport { configApiRef, useApi } from \"@backstage/core-plugin-api\";\nimport { useEntity } from \"@backstage/plugin-catalog-react\";\nimport {\n Card,\n CardContent,\n CardHeader,\n Divider,\n IconButton,\n List,\n Typography,\n} from \"@material-ui/core\";\nimport Link from \"@material-ui/core/Link\";\nimport CachedIcon from \"@material-ui/icons/Cached\";\nimport HistoryIcon from \"@material-ui/icons/History\";\nimport WhatshotIcon from \"@material-ui/icons/Whatshot\";\nimport { Alert } from \"@material-ui/lab\";\nimport { useState } from \"react\";\nimport { useIncidentList, useIdentity } from \"../../hooks/useIncidentRequest\";\nimport { IncidentListItem } from \"../IncidentListItem\";\nimport { getEntityFieldID } from \"../utils\";\n\nconst IncorrectConfigCard = () => {\n return (\n <Card>\n <CardHeader title=\"incident.io\" />\n <Divider />\n <CardContent>\n <Typography variant=\"subtitle1\">\n No custom field configuration was found. In order to display\n incidents, this entity must be mapped to an incident.io custom field\n ID in Backstage's app-config.yaml.\n </Typography>\n </CardContent>\n </Card>\n );\n};\n\n// The card displayed on the entity page showing a handful of the most recent\n// incidents that are on-going for that component.\nexport const EntityIncidentCard = ({\n maxIncidents = 2,\n}: {\n maxIncidents?: number;\n}) => {\n const config = useApi(configApiRef);\n const { entity } = useEntity();\n const {\n value: identityResponse,\n loading: identityResponseLoading,\n error: identityResponseError,\n } = useIdentity();\n\n const [reload, setReload] = useState(false);\n\n const entityFieldID = getEntityFieldID(config, entity);\n const entityID = `${entity.metadata.namespace}/${entity.metadata.name}`;\n\n // This query filters incidents for those that are associated with this\n // entity.\n const query = new URLSearchParams();\n query.set(`custom_field[${entityFieldID}][one_of]`, entityID);\n\n // This restricts the previous filter to focus only on live incidents.\n const queryLive = new URLSearchParams(query);\n queryLive.set(`status_category[one_of]`, \"active\");\n\n const {\n value: incidentsResponse,\n loading: incidentsLoading,\n error: incidentsError,\n } = useIncidentList(queryLive, [reload]);\n\n const incidents = incidentsResponse?.incidents;\n\n if (!entityFieldID) {\n return <IncorrectConfigCard />;\n }\n\n if (incidentsLoading || identityResponseLoading || !identityResponse) {\n return <Progress />;\n }\n\n const baseUrl = identityResponse.identity.dashboard_url;\n\n const createIncidentLink: IconLinkVerticalProps = {\n label: \"Declare incident\",\n disabled: false,\n icon: <WhatshotIcon />,\n href: `${baseUrl}/incidents/create`,\n };\n\n const viewIncidentsLink: IconLinkVerticalProps = {\n label: \"View past incidents\",\n disabled: false,\n icon: <HistoryIcon />,\n href: `${baseUrl}/incidents?${query.toString()}`,\n };\n\n return (\n <Card>\n <CardHeader\n title=\"incident.io\"\n action={\n <>\n <IconButton\n component={Link}\n aria-label=\"Refresh\"\n disabled={false}\n title=\"Refresh\"\n onClick={() => setReload(!reload)}\n >\n <CachedIcon />\n </IconButton>\n </>\n }\n subheader={\n <HeaderIconLinkRow links={[createIncidentLink, viewIncidentsLink]} />\n }\n />\n <Divider />\n <CardContent>\n {incidentsError && (\n <Alert severity=\"error\">{incidentsError.message}</Alert>\n )}\n {identityResponseError && (\n <Alert severity=\"error\">{identityResponseError.message}</Alert>\n )}\n {!incidentsLoading && !incidentsError && incidents && (\n <>\n {incidents && incidents.length > 0 && (\n <Typography variant=\"subtitle1\">\n There are <strong>{incidents.length}</strong> ongoing incidents\n involving <strong>{entity.metadata.name}</strong>.\n </Typography>\n )}\n {incidents && incidents.length === 0 && (\n <Typography variant=\"subtitle1\">No ongoing incidents.</Typography>\n )}\n <List dense>\n {incidents?.slice(0, maxIncidents)?.map((incident) => {\n return (\n <IncidentListItem\n key={incident.id}\n incident={incident}\n baseUrl={baseUrl}\n />\n );\n })}\n </List>\n <Typography variant=\"subtitle1\">\n Click to{\" \"}\n <Link\n target=\"_blank\"\n href={`${baseUrl}/incidents?${queryLive.toString()}`}\n >\n see more.\n </Link>\n </Typography>\n </>\n )}\n </CardContent>\n </Card>\n );\n};\n\n"],"names":[],"mappings":"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"}
|