@incident-io/backstage 0.1.0 → 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 (83) hide show
  1. package/README.md +22 -3
  2. package/config.d.ts +11 -5
  3. package/dist/alpha.esm.js +32 -6
  4. package/dist/alpha.esm.js.map +1 -1
  5. package/dist/{esm/client-646572ea.esm.js → api/client.esm.js} +6 -13
  6. package/dist/api/client.esm.js.map +1 -0
  7. package/dist/components/AlertListItem/index.esm.js +68 -0
  8. package/dist/components/AlertListItem/index.esm.js.map +1 -0
  9. package/dist/components/EntityAlertCard/index.esm.js +113 -0
  10. package/dist/components/EntityAlertCard/index.esm.js.map +1 -0
  11. package/dist/components/EntityIncidentCard/index.esm.js +125 -0
  12. package/dist/components/EntityIncidentCard/index.esm.js.map +1 -0
  13. package/dist/components/EntityOnCallCard/index.esm.js +198 -0
  14. package/dist/components/EntityOnCallCard/index.esm.js.map +1 -0
  15. package/dist/components/HomePageAlertCard/Content.esm.js +76 -0
  16. package/dist/components/HomePageAlertCard/Content.esm.js.map +1 -0
  17. package/dist/components/HomePageAlertCard/index.esm.js +2 -0
  18. package/dist/components/HomePageAlertCard/index.esm.js.map +1 -0
  19. package/dist/components/HomePageIncidentCard/Content.esm.js +54 -0
  20. package/dist/components/HomePageIncidentCard/Content.esm.js.map +1 -0
  21. package/dist/components/HomePageIncidentCard/Context.esm.js +33 -0
  22. package/dist/components/HomePageIncidentCard/Context.esm.js.map +1 -0
  23. package/dist/components/HomePageIncidentCard/index.esm.js +3 -0
  24. package/dist/components/HomePageIncidentCard/index.esm.js.map +1 -0
  25. package/dist/components/HomePageOnCallCard/Content.esm.js +38 -0
  26. package/dist/components/HomePageOnCallCard/Content.esm.js.map +1 -0
  27. package/dist/components/HomePageOnCallCard/index.esm.js +6 -0
  28. package/dist/components/HomePageOnCallCard/index.esm.js.map +1 -0
  29. package/dist/components/IncidentListItem/index.esm.js +68 -0
  30. package/dist/components/IncidentListItem/index.esm.js.map +1 -0
  31. package/dist/components/styles.esm.js +34 -0
  32. package/dist/components/styles.esm.js.map +1 -0
  33. package/dist/components/utils.esm.js +19 -0
  34. package/dist/components/utils.esm.js.map +1 -0
  35. package/dist/hooks/useIncidentRequest.esm.js +65 -0
  36. package/dist/hooks/useIncidentRequest.esm.js.map +1 -0
  37. package/dist/hooks/useOnCallRequest.esm.js +116 -0
  38. package/dist/hooks/useOnCallRequest.esm.js.map +1 -0
  39. package/dist/index.d.ts +9 -6
  40. package/dist/index.esm.js +1 -64
  41. package/dist/index.esm.js.map +1 -1
  42. package/dist/plugin.esm.js +99 -0
  43. package/dist/plugin.esm.js.map +1 -0
  44. package/package.json +44 -23
  45. package/src/alpha.test.ts +9 -0
  46. package/src/alpha.tsx +38 -4
  47. package/src/api/client.test.ts +43 -0
  48. package/src/api/types.test.ts +15 -0
  49. package/src/api/types.ts +49796 -11325
  50. package/src/components/AlertListItem/index.tsx +82 -0
  51. package/src/components/EntityAlertCard/index.test.tsx +242 -0
  52. package/src/components/EntityAlertCard/index.tsx +168 -0
  53. package/src/components/EntityIncidentCard/index.test.tsx +135 -0
  54. package/src/components/EntityIncidentCard/index.tsx +3 -23
  55. package/src/components/EntityOnCallCard/index.test.tsx +134 -0
  56. package/src/components/EntityOnCallCard/index.tsx +301 -0
  57. package/src/components/HomePageAlertCard/Content.test.tsx +56 -0
  58. package/src/components/HomePageAlertCard/Content.tsx +85 -0
  59. package/src/components/HomePageAlertCard/index.tsx +1 -0
  60. package/src/components/HomePageIncidentCard/Content.test.tsx +4 -3
  61. package/src/components/HomePageIncidentCard/Content.tsx +2 -2
  62. package/src/components/HomePageIncidentCard/Context.tsx +2 -2
  63. package/src/components/HomePageOnCallCard/Content.test.tsx +90 -0
  64. package/src/components/HomePageOnCallCard/Content.tsx +58 -0
  65. package/src/components/HomePageOnCallCard/index.ts +3 -0
  66. package/src/components/IncidentListItem/index.tsx +3 -26
  67. package/src/components/styles.tsx +30 -0
  68. package/src/components/utils.tsx +24 -0
  69. package/src/hooks/useIncidentRequest.test.ts +189 -0
  70. package/src/hooks/useIncidentRequest.ts +56 -3
  71. package/src/hooks/useOnCallRequest.test.ts +52 -0
  72. package/src/hooks/useOnCallRequest.ts +141 -0
  73. package/src/index.ts +4 -0
  74. package/src/plugin.ts +45 -1
  75. package/src/setupTests.ts +2 -2
  76. package/alpha/package.json +0 -7
  77. package/dist/esm/client-646572ea.esm.js.map +0 -1
  78. package/dist/esm/index-55bf4982.esm.js +0 -72
  79. package/dist/esm/index-55bf4982.esm.js.map +0 -1
  80. package/dist/esm/index-633a0241.esm.js +0 -96
  81. package/dist/esm/index-633a0241.esm.js.map +0 -1
  82. package/dist/esm/index-a220a8f7.esm.js +0 -116
  83. package/dist/esm/index-a220a8f7.esm.js.map +0 -1
package/README.md CHANGED
@@ -44,11 +44,13 @@ Next, enable the plugin's extensions in `app-config.yaml`. No other code changes
44
44
  app:
45
45
  ...
46
46
  extensions:
47
- - entity-card:incident-io/incident
48
- - api:incident-io/incident
47
+ - entity-card:incident-io/EntityIncidentCard: true
48
+ - entity-card:incident-io/EntityAlertCard: true
49
+ - entity-card:incident-io/EntityOnCallCard: true
49
50
  ...
50
51
  ```
51
52
 
53
+
52
54
  ### Using the legacy Backstage system
53
55
 
54
56
  Next, add the plugin to `EntityPage.tsx` in
@@ -57,7 +59,7 @@ Next, add the plugin to `EntityPage.tsx` in
57
59
  Add the following imports to the top of the file:
58
60
 
59
61
  ```ts
60
- import { EntityIncidentCard } from "@incident-io/backstage";
62
+ import { EntityIncidentCard, EntityAlertCard, EntityOnCallCard } from "@incident-io/backstage";
61
63
  ```
62
64
 
63
65
  Find `const overviewContent` in `EntityPage.tsx`, and add the following snippet
@@ -68,6 +70,12 @@ tag:
68
70
  <Grid item md={6}>
69
71
  <EntityIncidentCard />
70
72
  </Grid>
73
+ <Grid item md={6}>
74
+ <EntityAlertCard />
75
+ </Grid>
76
+ <Grid item md={6}>
77
+ <EntityOnCallCard />
78
+ </Grid>
71
79
  ```
72
80
 
73
81
  If you want to include the list of incidents in places like the page for a
@@ -111,3 +119,14 @@ If you don't have a custom field set up for one of these entities, then you
111
119
  can omit that field completely. If you try and include the `EntityIncidentCard`
112
120
  on the page for an entity which doesn't have the configuration, we'll show you
113
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.esm.js CHANGED
@@ -1,9 +1,9 @@
1
- import React from 'react';
1
+ import { jsx } from 'react/jsx-runtime';
2
2
  import { ApiBlueprint, createFrontendPlugin } from '@backstage/frontend-plugin-api';
3
- import { discoveryApiRef, fetchApiRef } from '@backstage/core-plugin-api';
3
+ import { fetchApiRef, discoveryApiRef } from '@backstage/core-plugin-api';
4
4
  import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha';
5
5
  import { HomePageWidgetBlueprint } from '@backstage/plugin-home-react/alpha';
6
- import { I as IncidentApiRef, a as IncidentApi } from './esm/client-646572ea.esm.js';
6
+ import { IncidentApiRef, IncidentApi } from './api/client.esm.js';
7
7
 
8
8
  const incidentApi = ApiBlueprint.make({
9
9
  params: (defineParams) => defineParams({
@@ -23,14 +23,26 @@ const incidentApi = ApiBlueprint.make({
23
23
  const entityIncidentCard = EntityCardBlueprint.make({
24
24
  name: "EntityIncidentCard",
25
25
  params: {
26
- loader: async () => import('./esm/index-a220a8f7.esm.js').then((m) => /* @__PURE__ */ React.createElement(m.EntityIncidentCard, null))
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, {}))
27
39
  }
28
40
  });
29
41
  const homePageIncidentCard = HomePageWidgetBlueprint.make({
30
42
  name: "HomePageIncidentCard",
31
43
  params: {
32
44
  title: "Ongoing Incidents",
33
- components: () => import('./esm/index-55bf4982.esm.js'),
45
+ components: () => import('./components/HomePageIncidentCard/index.esm.js'),
34
46
  settings: {
35
47
  schema: {
36
48
  type: "object",
@@ -56,9 +68,23 @@ const homePageIncidentCard = HomePageWidgetBlueprint.make({
56
68
  }
57
69
  }
58
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
+ });
59
85
  const plugin = createFrontendPlugin({
60
86
  pluginId: "incident",
61
- extensions: [incidentApi, entityIncidentCard, homePageIncidentCard]
87
+ extensions: [incidentApi, entityIncidentCard, entityAlertCard, homePageIncidentCard, homePageAlertCard, homePageOnCallCard, entityOnCallCard]
62
88
  });
63
89
 
64
90
  export { plugin as default };
@@ -1 +1 @@
1
- {"version":3,"file":"alpha.esm.js","sources":["../src/alpha.tsx"],"sourcesContent":["import React from \"react\";\nimport {\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 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 \nconst plugin: FrontendPlugin = createFrontendPlugin({ \n pluginId: \"incident\",\n extensions: [incidentApi, entityIncidentCard, homePageIncidentCard], \n }); \n\nexport default plugin;"],"names":[],"mappings":";;;;;;;AAcA,MAAM,WAAA,GAAc,aAAa,IAAK,CAAA;AAAA,EACpC,MAAA,EAAQ,kBACN,YAAa,CAAA;AAAA,IACX,GAAK,EAAA,cAAA;AAAA,IACL,IAAM,EAAA;AAAA,MACJ,YAAc,EAAA,eAAA;AAAA,MACd,QAAU,EAAA,WAAA;AAAA,KACZ;AAAA,IACA,OAAS,EAAA,CAAC,EAAE,YAAA,EAAc,UAAc,KAAA;AACtC,MAAA,OAAO,IAAI,WAAY,CAAA;AAAA,QACrB,YAAA;AAAA,QACA,QAAA;AAAA,OACD,CAAA,CAAA;AAAA,KACH;AAAA,GACD,CAAA;AACL,CAAC,CAAA,CAAA;AAED,MAAM,kBAAA,GAAqB,oBAAoB,IAAK,CAAA;AAAA,EAClD,IAAM,EAAA,oBAAA;AAAA,EACN,MAAQ,EAAA;AAAA,IACN,MAAA,EAAQ,YACN,OAAO,6BAAiC,CAAA,CAAE,IAAK,CAAA,CAAA,CAAA,qBAAI,KAAA,CAAA,aAAA,CAAA,CAAA,CAAE,kBAAF,EAAA,IAAqB,CAAE,CAAA;AAAA,GAC9E;AACF,CAAC,CAAA,CAAA;AAED,MAAM,oBAAA,GAAuB,wBAAwB,IAAK,CAAA;AAAA,EACxD,IAAM,EAAA,sBAAA;AAAA,EACN,MAAQ,EAAA;AAAA,IACN,KAAO,EAAA,mBAAA;AAAA,IACP,UAAA,EAAY,MAAM,OAAO,6BAAmC,CAAA;AAAA,IAC5D,QAAU,EAAA;AAAA,MACR,MAAQ,EAAA;AAAA,QACN,IAAM,EAAA,QAAA;AAAA,QACN,UAAY,EAAA;AAAA,UACV,UAAY,EAAA;AAAA,YACV,IAAM,EAAA,QAAA;AAAA,YACN,KAAO,EAAA,aAAA;AAAA,YACP,WAAa,EAAA,gDAAA;AAAA,YACb,KAAO,EAAA;AAAA,cACL,EAAE,IAAM,EAAA,CAAC,iBAAiB,CAAA,EAAG,OAAO,iBAAkB,EAAA;AAAA,cACtD,EAAE,IAAM,EAAA,CAAC,QAAQ,CAAA,EAAG,OAAO,QAAS,EAAA;AAAA,aACtC;AAAA,YACA,OAAS,EAAA,iBAAA;AAAA,WACX;AAAA,UACA,MAAQ,EAAA;AAAA,YACN,IAAM,EAAA,QAAA;AAAA,YACN,KAAO,EAAA,QAAA;AAAA,YACP,WACE,EAAA,qEAAA;AAAA,YACF,OAAS,EAAA,QAAA;AAAA,WACX;AAAA,SACF;AAAA,OACF;AAAA,KACF;AAAA,GACF;AACF,CAAC,CAAA,CAAA;AAED,MAAM,SAAyB,oBAAqB,CAAA;AAAA,EAChD,QAAU,EAAA,UAAA;AAAA,EACV,UAAY,EAAA,CAAC,WAAa,EAAA,kBAAA,EAAoB,oBAAoB,CAAA;AACpE,CAAC;;;;"}
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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"}
@@ -1,24 +1,17 @@
1
1
  import { createApiRef } from '@backstage/core-plugin-api';
2
2
 
3
- var __defProp = Object.defineProperty;
4
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5
- var __publicField = (obj, key, value) => {
6
- __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
7
- return value;
8
- };
9
3
  const IncidentApiRef = createApiRef({
10
4
  id: "plugin.incident.service"
11
5
  });
12
6
  const DEFAULT_PROXY_PATH = "/incident/api";
13
7
  class IncidentApi {
8
+ discoveryApi;
9
+ fetchApi;
10
+ proxyPath;
14
11
  constructor(opts) {
15
- __publicField(this, "discoveryApi");
16
- __publicField(this, "fetchApi");
17
- __publicField(this, "proxyPath");
18
- var _a;
19
12
  this.discoveryApi = opts.discoveryApi;
20
13
  this.fetchApi = opts.fetchApi;
21
- this.proxyPath = (_a = opts.proxyPath) != null ? _a : DEFAULT_PROXY_PATH;
14
+ this.proxyPath = opts.proxyPath ?? DEFAULT_PROXY_PATH;
22
15
  }
23
16
  async request({
24
17
  path,
@@ -37,5 +30,5 @@ class IncidentApi {
37
30
  }
38
31
  }
39
32
 
40
- export { IncidentApiRef as I, IncidentApi as a };
41
- //# sourceMappingURL=client-646572ea.esm.js.map
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":";;;;;;;;;;;;;AA+BA,MAAM,WAAA,GAAwD;AAAA,EAC5D,EAAE,KAAA,EAAO,QAAA,EAAU,KAAA,EAAO,QAAA,EAAS;AAAA,EACnC,EAAE,KAAA,EAAO,UAAA,EAAY,KAAA,EAAO,UAAA,EAAW;AAAA,EACvC,EAAE,KAAA,EAAO,KAAA,EAAO,KAAA,EAAO,MAAA;AACzB,CAAA;AAEA,MAAM,mBAAA,GAAsB,sBAC1B,IAAA,CAAC,IAAA,EAAA,EACC,QAAA,EAAA;AAAA,kBAAA,GAAA,CAAC,UAAA,EAAA,EAAW,OAAM,QAAA,EAAS,CAAA;AAAA,sBAC1B,OAAA,EAAA,EAAQ,CAAA;AAAA,sBACR,WAAA,EAAA,EACC,QAAA,kBAAA,GAAA,CAAC,cAAW,OAAA,EAAQ,WAAA,EAAY,+KAIhC,CAAA,EACF;AAAA,CAAA,EACF,CAAA;AAGK,MAAM,kBAAkB,MAAM;AACnC,EAAA,MAAM,CAAC,YAAA,EAAc,eAAe,CAAA,GAAI,SAAuB,QAAQ,CAAA;AACvE,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,SAAS,KAAK,CAAA;AAE1C,EAAA,MAAM,MAAA,GAAS,OAAO,YAAY,CAAA;AAClC,EAAA,MAAM,EAAE,MAAA,EAAO,GAAI,SAAA,EAAU;AAC7B,EAAA,MAAM,aAAA,GAAgB,gBAAA,CAAiB,MAAA,EAAQ,MAAM,CAAA;AACrD,EAAA,MAAM,QAAA,GAAW,GAAG,MAAA,CAAO,QAAA,CAAS,SAAS,CAAA,CAAA,EAAI,MAAA,CAAO,SAAS,IAAI,CAAA,CAAA;AAGrE,EAAA,MAAM,aAAA,GAAgB,IAAI,eAAA,EAAgB;AAC1C,EAAA,aAAA,CAAc,GAAA,CAAI,CAAA,aAAA,EAAgB,aAAa,CAAA,SAAA,CAAA,EAAa,QAAQ,CAAA;AAEpE,EAAA,MAAM,EAAE,KAAA,EAAO,iBAAA,EAAmB,OAAA,EAAS,gBAAA,KACzC,eAAA,CAAgB,aAAA,EAAe,CAAC,MAAM,CAAC,CAAA;AACzC,EAAA,MAAM,WAAA,GAAA,CAAe,mBAAmB,SAAA,IAAa,IAAI,GAAA,CAAI,CAAA,CAAA,KAAK,EAAE,EAAE,CAAA;AAGtE,EAAA,MAAM,EAAE,KAAA,EAAO,sBAAA,EAAwB,OAAA,EAAS,qBAAA,KAC9C,oBAAA,CAAqB,WAAA,EAAa,CAAC,MAAM,CAAC,CAAA;AAC5C,EAAA,MAAM,iBAAiB,IAAI,GAAA;AAAA,IAAA,CACxB,sBAAA,EAAwB,mBAAmB,EAAC,EAAG,IAAI,CAAA,EAAA,KAAM,EAAA,CAAG,MAAM,EAAE;AAAA,GACvE;AAEA,EAAA,MAAM,EAAE,KAAA,EAAO,cAAA,EAAgB,OAAA,EAAS,aAAA,EAAe,KAAA,EAAM,GAC3D,YAAA,CAAa,YAAA,EAAc,CAAC,MAAM,CAAC,CAAA;AACrC,EAAA,MAAM,EAAE,KAAA,EAAO,eAAA,EAAgB,GAAI,kBAAA,EAAmB;AACtD,EAAA,MAAM,EAAE,KAAA,EAAO,gBAAA,EAAiB,GAAI,WAAA,EAAY;AAGhD,EAAA,MAAM,SAAA,GAAY,cAAA,EAAgB,MAAA,IAAU,EAAC;AAC7C,EAAA,MAAM,MAAA,GAAS,WAAA,CAAY,MAAA,GAAS,CAAA,GAChC,SAAA,CAAU,MAAA,CAAO,CAAA,CAAA,KAAK,cAAA,CAAe,GAAA,CAAI,CAAA,CAAE,EAAE,CAAC,IAC9C,EAAC;AAEL,EAAA,MAAM,OAAA,GAAU,gBAAA,EAAkB,QAAA,CAAS,aAAA,IAAiB,EAAA;AAE5D,EAAA,MAAM,aAAa,MAAA,CAAO,WAAA;AAAA,IAAA,CACvB,eAAA,EAAiB,aAAA,IAAiB,EAAC,EAAG,GAAA,CAAI,OAAK,CAAC,CAAA,CAAE,EAAA,EAAI,CAAC,CAAC;AAAA,GAC3D;AAEA,EAAA,MAAM,kBAAkB,WAAA,CAAY,SAAA,CAAU,CAAA,CAAA,KAAK,CAAA,CAAE,UAAU,YAAY,CAAA;AAC3E,EAAA,IAAI,CAAC,aAAA,EAAe;AAClB,IAAA,2BAAQ,mBAAA,EAAA,EAAoB,CAAA;AAAA,EAC9B;AAEA,EAAA,IAAI,gBAAA,IAAoB,yBAAyB,aAAA,EAAe;AAC9D,IAAA,2BAAQ,QAAA,EAAA,EAAS,CAAA;AAAA,EACnB;AAEA,EAAA,4BACG,IAAA,EAAA,EACC,QAAA,EAAA;AAAA,oBAAA,GAAA;AAAA,MAAC,UAAA;AAAA,MAAA;AAAA,QACC,KAAA,EAAM,oBAAA;AAAA,QACN,wBACE,GAAA,CAAA,QAAA,EAAA,EACE,QAAA,kBAAA,GAAA;AAAA,UAAC,UAAA;AAAA,UAAA;AAAA,YACC,SAAA,EAAW,IAAA;AAAA,YACX,YAAA,EAAW,SAAA;AAAA,YACX,QAAA,EAAU,KAAA;AAAA,YACV,KAAA,EAAM,SAAA;AAAA,YACN,OAAA,EAAS,MAAM,SAAA,CAAU,CAAC,MAAM,CAAA;AAAA,YAEhC,8BAAC,UAAA,EAAA,EAAW;AAAA;AAAA,SACd,EACF;AAAA;AAAA,KAEJ;AAAA,wBACC,OAAA,EAAA,EAAQ,CAAA;AAAA,oBACT,GAAA;AAAA,MAAC,IAAA;AAAA,MAAA;AAAA,QACC,KAAA,EAAO,eAAA;AAAA,QACP,QAAA,EAAU,CAAC,CAAA,EAAG,GAAA,KAAQ,gBAAgB,WAAA,CAAY,GAAG,EAAE,KAAK,CAAA;AAAA,QAC5D,cAAA,EAAe,SAAA;AAAA,QACf,SAAA,EAAU,SAAA;AAAA,QAET,QAAA,EAAA,WAAA,CAAY,GAAA,CAAI,CAAA,GAAA,qBACf,GAAA,CAAC,GAAA,EAAA,EAAoB,OAAO,GAAA,CAAI,KAAA,EAAA,EAAtB,GAAA,CAAI,KAAyB,CACxC;AAAA;AAAA,KACH;AAAA,wBACC,OAAA,EAAA,EAAQ,CAAA;AAAA,yBACR,WAAA,EAAA,EACE,QAAA,EAAA;AAAA,MAAA,KAAA,oBAAS,GAAA,CAAC,KAAA,EAAA,EAAM,QAAA,EAAS,OAAA,EAAS,gBAAM,OAAA,EAAQ,CAAA;AAAA,MAChD,CAAC,SAAS,MAAA,CAAO,MAAA,KAAW,qBAC3B,GAAA,CAAC,UAAA,EAAA,EAAW,OAAA,EAAQ,WAAA,EAAY,QAAA,EAAA,YAAA,EAAU,CAAA;AAAA,MAE3C,CAAC,KAAA,IAAS,MAAA,CAAO,MAAA,GAAS,qBACzB,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,wBAAA,IAAA,CAAC,UAAA,EAAA,EAAW,SAAQ,WAAA,EAAY,QAAA,EAAA;AAAA,UAAA,YAAA;AAAA,0BACpB,GAAA,CAAC,QAAA,EAAA,EAAQ,QAAA,EAAA,MAAA,CAAO,MAAA,EAAO,CAAA;AAAA,UAAU,GAAA;AAAA,UAC1C,YAAA,IAAgB,EAAA;AAAA,UAAG;AAAA,SAAA,EACtB,CAAA;AAAA,wBACA,GAAA,CAAC,GAAA,EAAA,EAAI,KAAA,EAAO,EAAE,WAAW,GAAA,EAAK,SAAA,EAAW,MAAA,EAAO,EAC9C,8BAAC,IAAA,EAAA,EAAK,KAAA,EAAK,IAAA,EACR,QAAA,EAAA,MAAA,CAAO,IAAI,CAAA,KAAA,qBACV,GAAA;AAAA,UAAC,aAAA;AAAA,UAAA;AAAA,YAEC,KAAA;AAAA,YACA,OAAA;AAAA,YACA,MAAA,EAAQ,UAAA,CAAW,KAAA,CAAM,eAAe,GAAG,IAAA,IAAQ,GAAA;AAAA,YACnD,QAAA,EAAU,KAAA,CAAM,UAAA,CAAW,IAAA,CAAK,CAAA,CAAA,KAAK,EAAE,SAAA,CAAU,IAAA,KAAS,UAAU,CAAA,EAAG,KAAA,EAAO;AAAA,WAAA;AAAA,UAJzE,KAAA,CAAM;AAAA,SAMd,GACH,CAAA,EACF;AAAA,OAAA,EACF,CAAA;AAAA,MAED,OAAA,oBACC,GAAA,CAAC,UAAA,EAAA,EAAW,OAAA,EAAQ,aAClB,QAAA,kBAAA,GAAA,CAAC,IAAA,EAAA,EAAK,MAAA,EAAO,QAAA,EAAS,IAAA,EAAM,CAAA,EAAG,OAAO,CAAA,eAAA,CAAA,EAAmB,6BAEzD,CAAA,EACF;AAAA,KAAA,EAEJ;AAAA,GAAA,EACF,CAAA;AAEJ;;;;"}
@@ -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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"}