@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
@@ -0,0 +1,141 @@
1
+ import { useApi, configApiRef } from "@backstage/core-plugin-api";
2
+ import { useAsync } from "react-use";
3
+ import { IncidentApiRef } from "../api/client";
4
+ import { components } from "../api/types";
5
+ import { DependencyList } from "react";
6
+
7
+ export type EscalationPathNode = components["schemas"]["EscalationPathNodeV2"];
8
+ export type EscalationPathTarget = components["schemas"]["EscalationPathTargetV2"];
9
+ export type EscalationPathData = components["schemas"]["EscalationPathV2"];
10
+ export type ScheduleRotation = components["schemas"]["ScheduleRotationV2"];
11
+ export type ScheduleData = components["schemas"]["ScheduleV2"];
12
+
13
+ const collectSlackChannelIds = (nodes: EscalationPathNode[]): string[] => {
14
+ const ids: string[] = [];
15
+ for (const node of nodes) {
16
+ const targets = node.level?.targets ?? node.notify_channel?.targets ?? [];
17
+ for (const t of targets) {
18
+ if (t.type === "slack_channel") ids.push(t.id);
19
+ }
20
+ if (node.if_else) {
21
+ ids.push(...collectSlackChannelIds(node.if_else.then_path));
22
+ ids.push(...collectSlackChannelIds(node.if_else.else_path));
23
+ }
24
+ }
25
+ return [...new Set(ids)];
26
+ };
27
+
28
+ export const useEscalationPath = (escalationPathId: string | null, deps?: DependencyList) => {
29
+ const IncidentApi = useApi(IncidentApiRef);
30
+
31
+ return useAsync(async () => {
32
+ if (!escalationPathId) return null;
33
+ const response = await IncidentApi.request<components["schemas"]["EscalationsShowPathResultV2"]>({
34
+ path: `/v2/escalation_paths/${escalationPathId}`,
35
+ });
36
+ const ep = response.escalation_path;
37
+
38
+ const channelNames: Record<string, string> = {};
39
+ const channelIds = collectSlackChannelIds(ep.path);
40
+ if (channelIds.length > 0) {
41
+ const typesResponse = await IncidentApi.request<components["schemas"]["CatalogListTypesResultV3"]>({
42
+ path: `/v3/catalog_types`,
43
+ });
44
+ const slackChannelType = typesResponse.catalog_types.find(t => t.type_name === "SlackChannel");
45
+ if (slackChannelType) {
46
+ const entriesResponse = await IncidentApi.request<components["schemas"]["CatalogListEntriesResultV3"]>({
47
+ path: `/v3/catalog_entries?catalog_type_id=${slackChannelType.id}&page_size=250`,
48
+ });
49
+ for (const entry of entriesResponse.catalog_entries) {
50
+ for (const id of channelIds) {
51
+ if (entry.aliases.includes(id)) {
52
+ channelNames[id] = entry.name;
53
+ }
54
+ }
55
+ }
56
+ }
57
+ }
58
+
59
+ return { ep, channelNames };
60
+ }, deps);
61
+ };
62
+
63
+ export const useSchedule = (scheduleId: string | null, deps?: DependencyList) => {
64
+ const IncidentApi = useApi(IncidentApiRef);
65
+
66
+ return useAsync(async () => {
67
+ if (!scheduleId) return null;
68
+ const response = await IncidentApi.request<components["schemas"]["SchedulesShowResultV2"]>({
69
+ path: `/v2/schedules/${scheduleId}`,
70
+ });
71
+ return response.schedule;
72
+ }, deps);
73
+ };
74
+
75
+
76
+ export const useOnCallData = (entityExternalId: string, deps?: DependencyList) => {
77
+ const IncidentApi = useApi(IncidentApiRef);
78
+ const config = useApi(configApiRef);
79
+
80
+ return useAsync(async () => {
81
+ const catalogTypeId = config.getString("incident.onCall.catalogTypeId");
82
+
83
+ const typeResponse = await IncidentApi.request<components["schemas"]["CatalogShowTypeResultV3"]>({
84
+ path: `/v3/catalog_types/${catalogTypeId}`,
85
+ });
86
+
87
+ const { schema } = typeResponse.catalog_type;
88
+ const escalationAttr = schema.attributes.find(a => a.type === 'EscalationPath');
89
+ const scheduleAttr = schema.attributes.find(a => a.type === 'Schedule');
90
+
91
+ const entriesResponse = await IncidentApi.request<components["schemas"]["CatalogListEntriesResultV3"]>({
92
+ path: `/v3/catalog_entries?catalog_type_id=${catalogTypeId}&identifier=${encodeURIComponent(entityExternalId)}&page_size=1`,
93
+ });
94
+
95
+ const entry = entriesResponse.catalog_entries[0];
96
+ if (!entry) throw new Error(`No incident.io catalog entry found for ${entityExternalId}`);
97
+
98
+ const escalationPath = escalationAttr ? entry.attribute_values[escalationAttr.id]?.value ?? null : null;
99
+ const schedule = scheduleAttr ? entry.attribute_values[scheduleAttr.id]?.value ?? null : null;
100
+
101
+ let escalationPathStatus: 'ok' | 'no_field' | 'empty';
102
+ if (!escalationAttr) escalationPathStatus = 'no_field';
103
+ else if (!escalationPath) escalationPathStatus = 'empty';
104
+ else escalationPathStatus = 'ok';
105
+
106
+ let scheduleStatus: 'ok' | 'no_field' | 'empty';
107
+ if (!scheduleAttr) scheduleStatus = 'no_field';
108
+ else if (!schedule) scheduleStatus = 'empty';
109
+ else scheduleStatus = 'ok';
110
+
111
+ let currentlyOnCall: NonNullable<components["schemas"]["CatalogEntryEngineParamBindingV3"]["array_value"]> = [];
112
+ if (schedule) {
113
+ const scheduleEntry = await IncidentApi.request<components["schemas"]["CatalogShowEntryResultV3"]>({
114
+ path: `/v3/catalog_entries/${schedule.literal}`,
115
+ });
116
+ currentlyOnCall = scheduleEntry.catalog_entry.attribute_values.currently_on_call?.array_value ?? [];
117
+ }
118
+
119
+ return { escalationPath, schedule, currentlyOnCall, escalationPathStatus, scheduleStatus };
120
+ }, deps);
121
+ };
122
+
123
+ export const useAllEscalationPaths = (deps?: DependencyList) => {
124
+ const IncidentApi = useApi(IncidentApiRef);
125
+ return useAsync(async () => {
126
+ const response = await IncidentApi.request<components["schemas"]["EscalationsListPathsResultV2"]>({
127
+ path: `/v2/escalation_paths`,
128
+ });
129
+ return response.escalation_paths;
130
+ }, deps);
131
+ };
132
+
133
+ export const useAllSchedules = (deps?: DependencyList) => {
134
+ const IncidentApi = useApi(IncidentApiRef);
135
+ return useAsync(async () => {
136
+ const response = await IncidentApi.request<components["schemas"]["SchedulesListResultV2"]>({
137
+ path: `/v2/schedules`,
138
+ });
139
+ return response.schedules;
140
+ }, deps);
141
+ };
package/src/index.ts CHANGED
@@ -16,5 +16,9 @@
16
16
  export {
17
17
  incidentPlugin,
18
18
  EntityIncidentCard,
19
+ EntityAlertCard,
20
+ EntityOnCallCard,
19
21
  HomePageIncidentCard,
22
+ HomePageAlertCard,
23
+ HomePageOnCallCard,
20
24
  } from "./plugin";
package/src/plugin.ts CHANGED
@@ -58,9 +58,53 @@ export const EntityIncidentCard = incidentPlugin.provide(
58
58
  }),
59
59
  );
60
60
 
61
+ export const EntityAlertCard = incidentPlugin.provide(
62
+ createComponentExtension({
63
+ name: "EntityAlertCard",
64
+ component: {
65
+ lazy: () =>
66
+ import("./components/EntityAlertCard").then(
67
+ (m) => m.EntityAlertCard,
68
+ ),
69
+ },
70
+ }),
71
+ );
72
+
73
+ export const EntityOnCallCard = incidentPlugin.provide(
74
+ createComponentExtension({
75
+ name: "EntityOnCallCard",
76
+ component: {
77
+ lazy: () =>
78
+ import("./components/EntityOnCallCard").then(
79
+ (m) => m.EntityOnCallCard,
80
+ ),
81
+ },
82
+ }),
83
+ );
84
+
85
+ export const HomePageOnCallCard: (
86
+ props: CardExtensionProps<unknown>,
87
+ ) => JSX.Element = incidentPlugin.provide(
88
+ createCardExtension({
89
+ name: "HomePageOnCallCard",
90
+ title: "On-call",
91
+ components: () => import("./components/HomePageOnCallCard"),
92
+ }),
93
+ );
94
+
95
+ export const HomePageAlertCard: (
96
+ props: CardExtensionProps<unknown>,
97
+ ) => JSX.Element = incidentPlugin.provide(
98
+ createCardExtension({
99
+ name: "HomePageAlertCard",
100
+ title: "Firing Alerts",
101
+ components: () => import("./components/HomePageAlertCard"),
102
+ }),
103
+ );
104
+
61
105
  export const HomePageIncidentCard: (
62
106
  props: CardExtensionProps<unknown>,
63
- ) => React.JSX.Element = incidentPlugin.provide(
107
+ ) => JSX.Element = incidentPlugin.provide(
64
108
  createCardExtension({
65
109
  name: "HomePageIncidentCard",
66
110
  title: "Ongoing Incidents",
package/src/setupTests.ts CHANGED
@@ -13,5 +13,5 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import "@testing-library/jest-dom";
17
- import "cross-fetch/polyfill";
16
+ import "@testing-library/jest-dom/vitest";
17
+ import "cross-fetch/polyfill";
@@ -1,96 +0,0 @@
1
- import { useApi } from '@backstage/core-plugin-api';
2
- import { useAsync } from 'react-use';
3
- import { I as IncidentApiRef } from './index-8f5c4c5f.esm.js';
4
- import { DateTime, Duration } from 'luxon';
5
- import { makeStyles, ListItem, ListItemText, Chip, Typography, ListItemSecondaryAction, Tooltip, IconButton } from '@material-ui/core';
6
- import OpenInBrowserIcon from '@material-ui/icons/OpenInBrowser';
7
- import React from 'react';
8
-
9
- const useIncidentList = (query, deps) => {
10
- const IncidentApi = useApi(IncidentApiRef);
11
- const { value, loading, error } = useAsync(async () => {
12
- return await IncidentApi.request({
13
- path: `/v2/incidents?${query.toString()}`
14
- });
15
- }, deps);
16
- return { loading, error, value };
17
- };
18
- const useIdentity = () => {
19
- const IncidentApi = useApi(IncidentApiRef);
20
- const { value, loading, error } = useAsync(async () => {
21
- return await IncidentApi.request({
22
- path: `/v1/identity`
23
- });
24
- });
25
- return { value, loading, error };
26
- };
27
-
28
- const useStyles = makeStyles((theme) => ({
29
- listItemPrimary: {
30
- display: "flex",
31
- // vertically align with chip
32
- fontWeight: "bold"
33
- },
34
- warning: {
35
- borderColor: theme.palette.status.warning,
36
- color: theme.palette.status.warning,
37
- "& *": {
38
- color: theme.palette.status.warning
39
- }
40
- },
41
- error: {
42
- borderColor: theme.palette.status.error,
43
- color: theme.palette.status.error,
44
- "& *": {
45
- color: theme.palette.status.error
46
- }
47
- }
48
- }));
49
- const IncidentListItem = ({
50
- baseUrl,
51
- incident
52
- }) => {
53
- var _a, _b;
54
- const classes = useStyles();
55
- const reportedAt = (_a = incident.incident_timestamp_values) == null ? void 0 : _a.find(
56
- (ts) => ts.incident_timestamp.name.match(/reported/i)
57
- );
58
- const reportedAtDate = ((_b = reportedAt == null ? void 0 : reportedAt.value) == null ? void 0 : _b.value) || incident.created_at;
59
- const sinceReported = (/* @__PURE__ */ new Date()).getTime() - new Date(reportedAtDate).getTime();
60
- const sinceReportedLabel = DateTime.local().minus(Duration.fromMillis(sinceReported)).toRelative({ locale: "en" });
61
- const lead = incident.incident_role_assignments.find((roleAssignment) => {
62
- return roleAssignment.role.role_type === "lead";
63
- });
64
- return /* @__PURE__ */ React.createElement(ListItem, { dense: true, key: incident.id }, /* @__PURE__ */ React.createElement(
65
- ListItemText,
66
- {
67
- primary: /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
68
- Chip,
69
- {
70
- "data-testid": `chip-${incident.incident_status.id}`,
71
- label: incident.incident_status.name,
72
- size: "small",
73
- variant: "outlined",
74
- className: ["active"].includes(incident.incident_status.category) ? classes.error : classes.warning
75
- }
76
- ), incident.reference, " ", incident.name),
77
- primaryTypographyProps: {
78
- variant: "body1",
79
- className: classes.listItemPrimary
80
- },
81
- secondary: /* @__PURE__ */ React.createElement(Typography, { noWrap: true, variant: "body2", color: "textSecondary" }, "Reported ", sinceReportedLabel, " and", " ", (lead == null ? void 0 : lead.assignee) ? `${lead.assignee.name} is lead` : "the lead is unassigned", ".")
82
- }
83
- ), /* @__PURE__ */ React.createElement(ListItemSecondaryAction, null, /* @__PURE__ */ React.createElement(Tooltip, { title: "View in incident.io", placement: "top" }, /* @__PURE__ */ React.createElement(
84
- IconButton,
85
- {
86
- href: `${baseUrl}/incidents/${incident.id}`,
87
- target: "_blank",
88
- rel: "noopener noreferrer",
89
- color: "primary"
90
- },
91
- /* @__PURE__ */ React.createElement(OpenInBrowserIcon, null)
92
- ))));
93
- };
94
-
95
- export { IncidentListItem as I, useIncidentList as a, useIdentity as u };
96
- //# sourceMappingURL=index-3220e1e0.esm.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index-3220e1e0.esm.js","sources":["../../src/hooks/useIncidentRequest.ts","../../src/components/IncidentListItem/index.tsx"],"sourcesContent":["import { useApi } from \"@backstage/core-plugin-api\";\nimport { useAsync } from \"react-use\";\nimport { IncidentApiRef } from \"../api/client\";\nimport { definitions } from \"../api/types\";\nimport { DependencyList } from \"react\";\n\nexport const useIncidentList = (\n query: URLSearchParams,\n deps?: DependencyList,\n) => {\n const IncidentApi = useApi(IncidentApiRef);\n\n const { value, loading, error } = useAsync(async () => {\n return await IncidentApi.request<\n definitions[\"IncidentsV2ListResponseBody\"]\n >({\n path: `/v2/incidents?${query.toString()}`,\n });\n }, deps);\n\n return { loading, error, value };\n};\n\nexport const useIdentity = () => {\n const IncidentApi = useApi(IncidentApiRef);\n\n const { value, loading, error } = useAsync(async () => {\n return await IncidentApi.request<\n definitions[\"UtilitiesV1IdentityResponseBody\"]\n >({\n path: `/v1/identity`,\n });\n });\n\n return { value, loading, error };\n};\n","/*\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 { DateTime, Duration } from \"luxon\";\nimport { BackstageTheme } from \"@backstage/theme\";\nimport {\n Chip,\n IconButton,\n ListItem,\n ListItemSecondaryAction,\n ListItemText,\n Tooltip,\n Typography,\n makeStyles,\n} from \"@material-ui/core\";\nimport OpenInBrowserIcon from \"@material-ui/icons/OpenInBrowser\";\nimport React from \"react\";\nimport { definitions } from \"../../api/types\";\n\nconst useStyles = makeStyles<BackstageTheme>((theme) => ({\n listItemPrimary: {\n display: \"flex\", // vertically align with chip\n fontWeight: \"bold\",\n },\n warning: {\n borderColor: theme.palette.status.warning,\n color: theme.palette.status.warning,\n \"& *\": {\n color: theme.palette.status.warning,\n },\n },\n error: {\n borderColor: theme.palette.status.error,\n color: theme.palette.status.error,\n \"& *\": {\n color: theme.palette.status.error,\n },\n },\n}));\n\n// Single item in the list of on-going incidents.\nexport const IncidentListItem = ({\n baseUrl,\n incident,\n}: {\n baseUrl: string;\n incident: definitions[\"IncidentV2ResponseBody\"];\n}) => {\n const classes = useStyles();\n const reportedAt = incident.incident_timestamp_values?.find((ts) =>\n ts.incident_timestamp.name.match(/reported/i),\n );\n\n // If reported isn't here for some reason, use created at.\n const reportedAtDate = reportedAt?.value?.value || incident.created_at;\n\n const sinceReported =\n new Date().getTime() - new Date(reportedAtDate).getTime();\n const sinceReportedLabel = DateTime.local()\n .minus(Duration.fromMillis(sinceReported))\n .toRelative({ locale: \"en\" });\n const lead = incident.incident_role_assignments.find((roleAssignment) => {\n return roleAssignment.role.role_type === \"lead\";\n });\n\n return (\n <ListItem dense key={incident.id}>\n <ListItemText\n primary={\n <>\n <Chip\n data-testid={`chip-${incident.incident_status.id}`}\n label={incident.incident_status.name}\n size=\"small\"\n variant=\"outlined\"\n className={\n [\"active\"].includes(incident.incident_status.category)\n ? classes.error\n : classes.warning\n }\n />\n {incident.reference} {incident.name}\n </>\n }\n primaryTypographyProps={{\n variant: \"body1\",\n className: classes.listItemPrimary,\n }}\n secondary={\n <Typography noWrap variant=\"body2\" color=\"textSecondary\">\n Reported {sinceReportedLabel} and{\" \"}\n {lead?.assignee\n ? `${lead.assignee.name} is lead`\n : \"the lead is unassigned\"}\n .\n </Typography>\n }\n />\n <ListItemSecondaryAction>\n <Tooltip title=\"View in incident.io\" placement=\"top\">\n <IconButton\n href={`${baseUrl}/incidents/${incident.id}`}\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":"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"}
@@ -1,73 +0,0 @@
1
- import { Progress } from '@backstage/core-components';
2
- import Link from '@material-ui/core/Link';
3
- import { Alert } from '@material-ui/lab';
4
- import React, { createContext, useMemo, useContext } from 'react';
5
- import { a as useIncidentList, I as IncidentListItem } from './index-3220e1e0.esm.js';
6
- import { Typography, List } from '@material-ui/core';
7
- import { useApi, configApiRef } from '@backstage/core-plugin-api';
8
- import 'react-use';
9
- import './index-8f5c4c5f.esm.js';
10
- import '@backstage/plugin-home-react';
11
- import 'luxon';
12
- import '@material-ui/icons/OpenInBrowser';
13
-
14
- const Context = createContext(
15
- void 0
16
- );
17
- const ContextProvider = (props) => {
18
- const {
19
- children,
20
- filterType: defaultFilterType,
21
- filter: defaultFilter
22
- } = props;
23
- const value = useMemo(
24
- () => ({
25
- filterType: defaultFilterType || "status_category",
26
- filter: defaultFilter || "active"
27
- }),
28
- [defaultFilter, defaultFilterType]
29
- );
30
- return /* @__PURE__ */ React.createElement(Context.Provider, { value }, children);
31
- };
32
- const useHomePageIncidentCard = () => {
33
- const value = useContext(Context);
34
- if (value === void 0) {
35
- throw new Error(
36
- "useHomePageIncidentCard must be used within a HomePageIncidentCardContextProvider"
37
- );
38
- }
39
- return value;
40
- };
41
-
42
- const HomePageIncidentCardContent = () => {
43
- const { filterType, filter } = useHomePageIncidentCard();
44
- const config = useApi(configApiRef);
45
- const baseUrl = config.getOptionalString("incident.baseUrl") || "https://app.incident.io";
46
- const query = React.useMemo(() => {
47
- const params = new URLSearchParams();
48
- params.set(`${filterType}[one_of]`, filter);
49
- return params;
50
- }, [filterType, filter]);
51
- const { loading, error, value } = useIncidentList(query, [query]);
52
- const incidents = value == null ? void 0 : value.incidents;
53
- if (loading)
54
- return /* @__PURE__ */ React.createElement(Progress, null);
55
- if (error)
56
- return /* @__PURE__ */ React.createElement(Alert, { severity: "error" }, error.message);
57
- return /* @__PURE__ */ React.createElement(React.Fragment, null, incidents && incidents.length > 0 && /* @__PURE__ */ React.createElement(Typography, { variant: "subtitle1" }, "There are ", /* @__PURE__ */ React.createElement("strong", null, incidents.length), " ongoing incidents."), incidents && incidents.length === 0 && /* @__PURE__ */ React.createElement(Typography, { variant: "subtitle1" }, "No ongoing incidents."), /* @__PURE__ */ React.createElement(List, { dense: true }, incidents == null ? void 0 : incidents.map((incident) => {
58
- return /* @__PURE__ */ React.createElement(
59
- IncidentListItem,
60
- {
61
- key: incident.id,
62
- incident,
63
- baseUrl
64
- }
65
- );
66
- })), /* @__PURE__ */ React.createElement(Typography, { variant: "subtitle1" }, "Click to", " ", /* @__PURE__ */ React.createElement(Link, { target: "_blank", href: `${baseUrl}/incidents?${query.toString()}` }, "see more.")));
67
- };
68
- const Content = () => {
69
- return /* @__PURE__ */ React.createElement(HomePageIncidentCardContent, null);
70
- };
71
-
72
- export { Content, ContextProvider };
73
- //# sourceMappingURL=index-5cb52808.esm.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index-5cb52808.esm.js","sources":["../../src/components/HomePageIncidentCard/Context.tsx","../../src/components/HomePageIncidentCard/Content.tsx"],"sourcesContent":["import React, { createContext, useContext, useMemo } from \"react\";\n\ntype HomePageIncidentCardContextValue = {\n filterType: \"status_category\" | \"status\";\n filter: string;\n};\n\nconst Context = createContext<HomePageIncidentCardContextValue | undefined>(\n undefined,\n);\n\nexport const ContextProvider = (props: {\n children: React.JSX.Element;\n filterType?: \"status_category\" | \"status\";\n filter?: string;\n}) => {\n const {\n children,\n filterType: defaultFilterType,\n filter: defaultFilter,\n } = props;\n\n const value = useMemo(\n () => ({\n filterType: defaultFilterType || \"status_category\",\n filter: defaultFilter || \"active\",\n }),\n [defaultFilter, defaultFilterType],\n );\n\n return <Context.Provider value={value}>{children}</Context.Provider>;\n};\n\nexport const useHomePageIncidentCard = () => {\n const value = useContext(Context);\n\n if (value === undefined) {\n throw new Error(\n \"useHomePageIncidentCard must be used within a HomePageIncidentCardContextProvider\",\n );\n }\n\n return value;\n};\n\nexport default Context;\n","import { Progress } from \"@backstage/core-components\";\nimport Link from \"@material-ui/core/Link\";\nimport { Alert } from \"@material-ui/lab\";\nimport React from \"react\";\nimport { useIncidentList } from \"../../hooks/useIncidentRequest\";\nimport { Typography, List } from \"@material-ui/core\";\nimport { IncidentListItem } from \"../IncidentListItem\";\nimport { configApiRef, useApi } from \"@backstage/core-plugin-api\";\nimport { useHomePageIncidentCard } from \"./Context\";\n\nexport const HomePageIncidentCardContent = () => {\n const { filterType, filter } = useHomePageIncidentCard();\n const config = useApi(configApiRef);\n const baseUrl =\n config.getOptionalString(\"incident.baseUrl\") || \"https://app.incident.io\";\n\n const query = React.useMemo(() => {\n const params = new URLSearchParams();\n params.set(`${filterType}[one_of]`, filter);\n return params;\n }, [filterType, filter]);\n\n const { loading, error, value } = useIncidentList(query, [query]);\n const incidents = value?.incidents;\n\n if (loading) return <Progress />;\n if (error) return <Alert severity=\"error\">{error.message}</Alert>;\n\n return (\n <>\n {incidents && incidents.length > 0 && (\n <Typography variant=\"subtitle1\">\n There are <strong>{incidents.length}</strong> ongoing incidents.\n </Typography>\n )}\n {incidents && incidents.length === 0 && (\n <Typography variant=\"subtitle1\">No ongoing incidents.</Typography>\n )}\n <List dense>\n {incidents?.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 target=\"_blank\" href={`${baseUrl}/incidents?${query.toString()}`}>\n see more.\n </Link>\n </Typography>\n </>\n );\n};\n\nexport const Content = () => {\n return <HomePageIncidentCardContent />;\n};\n"],"names":[],"mappings":"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"}
@@ -1,117 +0,0 @@
1
- import { Progress, HeaderIconLinkRow } from '@backstage/core-components';
2
- import { useApi, configApiRef } from '@backstage/core-plugin-api';
3
- import { useEntity } from '@backstage/plugin-catalog-react';
4
- import { Card, CardHeader, IconButton, Divider, CardContent, Typography, List } from '@material-ui/core';
5
- import Link from '@material-ui/core/Link';
6
- import CachedIcon from '@material-ui/icons/Cached';
7
- import HistoryIcon from '@material-ui/icons/History';
8
- import WhatshotIcon from '@material-ui/icons/Whatshot';
9
- import { Alert } from '@material-ui/lab';
10
- import React, { useState } from 'react';
11
- import { u as useIdentity, a as useIncidentList, I as IncidentListItem } from './index-3220e1e0.esm.js';
12
- import 'react-use';
13
- import './index-8f5c4c5f.esm.js';
14
- import '@backstage/plugin-home-react';
15
- import 'luxon';
16
- import '@material-ui/icons/OpenInBrowser';
17
-
18
- const IncorrectConfigCard = () => {
19
- return /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardHeader, { title: "incident.io" }), /* @__PURE__ */ React.createElement(Divider, null), /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(Typography, { variant: "subtitle1" }, "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.")));
20
- };
21
- const EntityIncidentCard = ({
22
- maxIncidents = 2
23
- }) => {
24
- var _a;
25
- const config = useApi(configApiRef);
26
- const { entity } = useEntity();
27
- const {
28
- value: identityResponse,
29
- loading: identityResponseLoading,
30
- error: identityResponseError
31
- } = useIdentity();
32
- const [reload, setReload] = useState(false);
33
- const entityFieldID = getEntityFieldID(config, entity);
34
- const entityID = `${entity.metadata.namespace}/${entity.metadata.name}`;
35
- const query = new URLSearchParams();
36
- query.set(`custom_field[${entityFieldID}][one_of]`, entityID);
37
- const queryLive = new URLSearchParams(query);
38
- queryLive.set(`status_category[one_of]`, "active");
39
- const {
40
- value: incidentsResponse,
41
- loading: incidentsLoading,
42
- error: incidentsError
43
- } = useIncidentList(queryLive, [reload]);
44
- const incidents = incidentsResponse == null ? void 0 : incidentsResponse.incidents;
45
- if (!entityFieldID) {
46
- return /* @__PURE__ */ React.createElement(IncorrectConfigCard, null);
47
- }
48
- if (incidentsLoading || identityResponseLoading || !identityResponse) {
49
- return /* @__PURE__ */ React.createElement(Progress, null);
50
- }
51
- const baseUrl = identityResponse.identity.dashboard_url;
52
- const createIncidentLink = {
53
- label: "Declare incident",
54
- disabled: false,
55
- icon: /* @__PURE__ */ React.createElement(WhatshotIcon, null),
56
- href: `${baseUrl}/incidents/create`
57
- };
58
- const viewIncidentsLink = {
59
- label: "View past incidents",
60
- disabled: false,
61
- icon: /* @__PURE__ */ React.createElement(HistoryIcon, null),
62
- href: `${baseUrl}/incidents?${query.toString()}`
63
- };
64
- return /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(
65
- CardHeader,
66
- {
67
- title: "incident.io",
68
- action: /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
69
- IconButton,
70
- {
71
- component: Link,
72
- "aria-label": "Refresh",
73
- disabled: false,
74
- title: "Refresh",
75
- onClick: () => setReload(!reload)
76
- },
77
- /* @__PURE__ */ React.createElement(CachedIcon, null)
78
- )),
79
- subheader: /* @__PURE__ */ React.createElement(HeaderIconLinkRow, { links: [createIncidentLink, viewIncidentsLink] })
80
- }
81
- ), /* @__PURE__ */ React.createElement(Divider, null), /* @__PURE__ */ React.createElement(CardContent, null, incidentsError && /* @__PURE__ */ React.createElement(Alert, { severity: "error" }, incidentsError.message), identityResponseError && /* @__PURE__ */ React.createElement(Alert, { severity: "error" }, identityResponseError.message), !incidentsLoading && !incidentsError && incidents && /* @__PURE__ */ React.createElement(React.Fragment, null, incidents && incidents.length > 0 && /* @__PURE__ */ React.createElement(Typography, { variant: "subtitle1" }, "There are ", /* @__PURE__ */ React.createElement("strong", null, incidents.length), " ongoing incidents involving ", /* @__PURE__ */ React.createElement("strong", null, entity.metadata.name), "."), incidents && incidents.length === 0 && /* @__PURE__ */ React.createElement(Typography, { variant: "subtitle1" }, "No ongoing incidents."), /* @__PURE__ */ React.createElement(List, { dense: true }, (_a = incidents == null ? void 0 : incidents.slice(0, maxIncidents)) == null ? void 0 : _a.map((incident) => {
82
- return /* @__PURE__ */ React.createElement(
83
- IncidentListItem,
84
- {
85
- key: incident.id,
86
- incident,
87
- baseUrl
88
- }
89
- );
90
- })), /* @__PURE__ */ React.createElement(Typography, { variant: "subtitle1" }, "Click to", " ", /* @__PURE__ */ React.createElement(
91
- Link,
92
- {
93
- target: "_blank",
94
- href: `${baseUrl}/incidents?${queryLive.toString()}`
95
- },
96
- "see more."
97
- )))));
98
- };
99
- function getEntityFieldID(config, entity) {
100
- switch (entity.kind) {
101
- case "API":
102
- return config.getOptional("incident.fields.api");
103
- case "Component":
104
- return config.getOptional("incident.fields.component");
105
- case "Domain":
106
- return config.getOptional("incident.fields.domain");
107
- case "System":
108
- return config.getOptional("incident.fields.system");
109
- case "Group":
110
- return config.getOptional("incident.fields.group");
111
- default:
112
- throw new Error(`unrecognised entity kind: ${entity.kind}`);
113
- }
114
- }
115
-
116
- export { EntityIncidentCard };
117
- //# sourceMappingURL=index-632bbd58.esm.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index-632bbd58.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 { Entity } from \"@backstage/catalog-model\";\nimport {\n HeaderIconLinkRow,\n IconLinkVerticalProps,\n Progress,\n} from \"@backstage/core-components\";\nimport { ConfigApi, 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 React, { useState } from \"react\";\nimport { useIncidentList, useIdentity } from \"../../hooks/useIncidentRequest\";\nimport { IncidentListItem } from \"../IncidentListItem\";\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// Find the ID of the custom field in incident that represents the association\n// to this type of entity.\n//\n// In practice, this will be kind=Component => ID of Affected components field.\nfunction getEntityFieldID(config: ConfigApi, entity: Entity) {\n switch (entity.kind) {\n case \"API\":\n return config.getOptional(\"incident.fields.api\");\n case \"Component\":\n return config.getOptional(\"incident.fields.component\");\n case \"Domain\":\n return config.getOptional(\"incident.fields.domain\");\n case \"System\":\n return config.getOptional(\"incident.fields.system\");\n case \"Group\":\n return config.getOptional(\"incident.fields.group\");\n default:\n throw new Error(`unrecognised entity kind: ${entity.kind}`);\n }\n}\n"],"names":[],"mappings":"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"}