@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.
Files changed (84) hide show
  1. package/README.md +35 -1
  2. package/config.d.ts +11 -5
  3. package/dist/alpha.d.ts +5 -0
  4. package/dist/alpha.esm.js +91 -0
  5. package/dist/alpha.esm.js.map +1 -0
  6. package/dist/api/client.esm.js +34 -0
  7. package/dist/api/client.esm.js.map +1 -0
  8. package/dist/components/AlertListItem/index.esm.js +68 -0
  9. package/dist/components/AlertListItem/index.esm.js.map +1 -0
  10. package/dist/components/EntityAlertCard/index.esm.js +113 -0
  11. package/dist/components/EntityAlertCard/index.esm.js.map +1 -0
  12. package/dist/components/EntityIncidentCard/index.esm.js +125 -0
  13. package/dist/components/EntityIncidentCard/index.esm.js.map +1 -0
  14. package/dist/components/EntityOnCallCard/index.esm.js +198 -0
  15. package/dist/components/EntityOnCallCard/index.esm.js.map +1 -0
  16. package/dist/components/HomePageAlertCard/Content.esm.js +76 -0
  17. package/dist/components/HomePageAlertCard/Content.esm.js.map +1 -0
  18. package/dist/components/HomePageAlertCard/index.esm.js +2 -0
  19. package/dist/components/HomePageAlertCard/index.esm.js.map +1 -0
  20. package/dist/components/HomePageIncidentCard/Content.esm.js +54 -0
  21. package/dist/components/HomePageIncidentCard/Content.esm.js.map +1 -0
  22. package/dist/components/HomePageIncidentCard/Context.esm.js +33 -0
  23. package/dist/components/HomePageIncidentCard/Context.esm.js.map +1 -0
  24. package/dist/components/HomePageIncidentCard/index.esm.js +3 -0
  25. package/dist/components/HomePageIncidentCard/index.esm.js.map +1 -0
  26. package/dist/components/HomePageOnCallCard/Content.esm.js +38 -0
  27. package/dist/components/HomePageOnCallCard/Content.esm.js.map +1 -0
  28. package/dist/components/HomePageOnCallCard/index.esm.js +6 -0
  29. package/dist/components/HomePageOnCallCard/index.esm.js.map +1 -0
  30. package/dist/components/IncidentListItem/index.esm.js +68 -0
  31. package/dist/components/IncidentListItem/index.esm.js.map +1 -0
  32. package/dist/components/styles.esm.js +34 -0
  33. package/dist/components/styles.esm.js.map +1 -0
  34. package/dist/components/utils.esm.js +19 -0
  35. package/dist/components/utils.esm.js.map +1 -0
  36. package/dist/hooks/useIncidentRequest.esm.js +65 -0
  37. package/dist/hooks/useIncidentRequest.esm.js.map +1 -0
  38. package/dist/hooks/useOnCallRequest.esm.js +116 -0
  39. package/dist/hooks/useOnCallRequest.esm.js.map +1 -0
  40. package/dist/index.d.ts +9 -6
  41. package/dist/index.esm.js +1 -3
  42. package/dist/index.esm.js.map +1 -1
  43. package/dist/plugin.esm.js +99 -0
  44. package/dist/plugin.esm.js.map +1 -0
  45. package/package.json +64 -31
  46. package/src/alpha.test.ts +9 -0
  47. package/src/alpha.tsx +111 -0
  48. package/src/api/client.test.ts +43 -0
  49. package/src/api/types.test.ts +15 -0
  50. package/src/api/types.ts +49796 -11325
  51. package/src/components/AlertListItem/index.tsx +82 -0
  52. package/src/components/EntityAlertCard/index.test.tsx +242 -0
  53. package/src/components/EntityAlertCard/index.tsx +168 -0
  54. package/src/components/EntityIncidentCard/index.test.tsx +135 -0
  55. package/src/components/EntityIncidentCard/index.tsx +3 -23
  56. package/src/components/EntityOnCallCard/index.test.tsx +134 -0
  57. package/src/components/EntityOnCallCard/index.tsx +301 -0
  58. package/src/components/HomePageAlertCard/Content.test.tsx +56 -0
  59. package/src/components/HomePageAlertCard/Content.tsx +85 -0
  60. package/src/components/HomePageAlertCard/index.tsx +1 -0
  61. package/src/components/HomePageIncidentCard/Content.test.tsx +4 -3
  62. package/src/components/HomePageIncidentCard/Content.tsx +2 -2
  63. package/src/components/HomePageIncidentCard/Context.tsx +2 -2
  64. package/src/components/HomePageOnCallCard/Content.test.tsx +90 -0
  65. package/src/components/HomePageOnCallCard/Content.tsx +58 -0
  66. package/src/components/HomePageOnCallCard/index.ts +3 -0
  67. package/src/components/IncidentListItem/index.tsx +3 -26
  68. package/src/components/styles.tsx +30 -0
  69. package/src/components/utils.tsx +24 -0
  70. package/src/hooks/useIncidentRequest.test.ts +189 -0
  71. package/src/hooks/useIncidentRequest.ts +56 -3
  72. package/src/hooks/useOnCallRequest.test.ts +52 -0
  73. package/src/hooks/useOnCallRequest.ts +141 -0
  74. package/src/index.ts +4 -0
  75. package/src/plugin.ts +45 -1
  76. package/src/setupTests.ts +2 -2
  77. package/dist/esm/index-3220e1e0.esm.js +0 -96
  78. package/dist/esm/index-3220e1e0.esm.js.map +0 -1
  79. package/dist/esm/index-5cb52808.esm.js +0 -73
  80. package/dist/esm/index-5cb52808.esm.js.map +0 -1
  81. package/dist/esm/index-632bbd58.esm.js +0 -117
  82. package/dist/esm/index-632bbd58.esm.js.map +0 -1
  83. package/dist/esm/index-8f5c4c5f.esm.js +0 -101
  84. 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.
@@ -0,0 +1,5 @@
1
+ import { FrontendPlugin } from '@backstage/frontend-plugin-api';
2
+
3
+ declare const plugin: FrontendPlugin;
4
+
5
+ export { plugin as default };
@@ -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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"}