@incident-io/backstage 0.0.9 → 0.0.11

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.
@@ -36,12 +36,25 @@ import HistoryIcon from "@material-ui/icons/History";
36
36
  import WhatshotIcon from "@material-ui/icons/Whatshot";
37
37
  import { Alert } from "@material-ui/lab";
38
38
  import React, { useState } from "react";
39
- import { useAsync } from "react-use";
40
- import { IncidentApiRef } from "../../api/client";
41
- import { definitions } from "../../api/types";
42
- import { getBaseUrl } from "../../config";
39
+ import { useIncidentList, useIdentity } from "../../hooks/useIncidentRequest";
43
40
  import { IncidentListItem } from "../IncidentListItem";
44
41
 
42
+ const IncorrectConfigCard = () => {
43
+ return (
44
+ <Card>
45
+ <CardHeader title="incident.io" />
46
+ <Divider />
47
+ <CardContent>
48
+ <Typography variant="subtitle1">
49
+ No custom field configuration was found. In order to display
50
+ incidents, this entity must be mapped to an incident.io custom field
51
+ ID in Backstage's app-config.yaml.
52
+ </Typography>
53
+ </CardContent>
54
+ </Card>
55
+ );
56
+ };
57
+
45
58
  // The card displayed on the entity page showing a handful of the most recent
46
59
  // incidents that are on-going for that component.
47
60
  export const EntityIncidentCard = ({
@@ -50,19 +63,16 @@ export const EntityIncidentCard = ({
50
63
  maxIncidents?: number;
51
64
  }) => {
52
65
  const config = useApi(configApiRef);
53
- const baseUrl = getBaseUrl(config);
54
66
  const { entity } = useEntity();
55
-
56
- const IncidentApi = useApi(IncidentApiRef);
67
+ const {
68
+ value: identityResponse,
69
+ loading: identityResponseLoading,
70
+ error: identityResponseError,
71
+ } = useIdentity();
57
72
 
58
73
  const [reload, setReload] = useState(false);
59
74
 
60
75
  const entityFieldID = getEntityFieldID(config, entity);
61
-
62
- if (!entityFieldID) {
63
- return <IncorrectConfigCard />;
64
- }
65
-
66
76
  const entityID = `${entity.metadata.namespace}/${entity.metadata.name}`;
67
77
 
68
78
  // This query filters incidents for those that are associated with this
@@ -72,7 +82,25 @@ export const EntityIncidentCard = ({
72
82
 
73
83
  // This restricts the previous filter to focus only on live incidents.
74
84
  const queryLive = new URLSearchParams(query);
75
- queryLive.set(`status_category[one_of]`, "live");
85
+ queryLive.set(`status_category[one_of]`, "active");
86
+
87
+ const {
88
+ value: incidentsResponse,
89
+ loading: incidentsLoading,
90
+ error: incidentsError,
91
+ } = useIncidentList(queryLive, [reload]);
92
+
93
+ const incidents = incidentsResponse?.incidents;
94
+
95
+ if (!entityFieldID) {
96
+ return <IncorrectConfigCard />;
97
+ }
98
+
99
+ if (incidentsLoading || identityResponseLoading || !identityResponse) {
100
+ return <Progress />;
101
+ }
102
+
103
+ const baseUrl = identityResponse.identity.dashboard_url;
76
104
 
77
105
  const createIncidentLink: IconLinkVerticalProps = {
78
106
  label: "Create incident",
@@ -88,20 +116,6 @@ export const EntityIncidentCard = ({
88
116
  href: `${baseUrl}/incidents?${query.toString()}`,
89
117
  };
90
118
 
91
- const {
92
- value: incidentsResponse,
93
- loading: incidentsLoading,
94
- error: incidentsError,
95
- } = useAsync(async () => {
96
- return await IncidentApi.request<
97
- definitions["IncidentsV2ListResponseBody"]
98
- >({
99
- path: `/v2/incidents?${queryLive.toString()}`,
100
- });
101
- }, [reload]);
102
-
103
- const incidents = incidentsResponse?.incidents;
104
-
105
119
  return (
106
120
  <Card>
107
121
  <CardHeader
@@ -125,10 +139,12 @@ export const EntityIncidentCard = ({
125
139
  />
126
140
  <Divider />
127
141
  <CardContent>
128
- {incidentsLoading && <Progress />}
129
142
  {incidentsError && (
130
143
  <Alert severity="error">{incidentsError.message}</Alert>
131
144
  )}
145
+ {identityResponseError && (
146
+ <Alert severity="error">{identityResponseError.message}</Alert>
147
+ )}
132
148
  {!incidentsLoading && !incidentsError && incidents && (
133
149
  <>
134
150
  {incidents && incidents.length > 0 && (
@@ -167,22 +183,6 @@ export const EntityIncidentCard = ({
167
183
  );
168
184
  };
169
185
 
170
- const IncorrectConfigCard = () => {
171
- return (
172
- <Card>
173
- <CardHeader title="incident.io" />
174
- <Divider />
175
- <CardContent>
176
- <Typography variant="subtitle1">
177
- No custom field configuration was found. In order to display
178
- incidents, this entity must be mapped to an incident.io custom field
179
- ID in Backstage's app-config.yaml.
180
- </Typography>
181
- </CardContent>
182
- </Card>
183
- );
184
- };
185
-
186
186
  // Find the ID of the custom field in incident that represents the association
187
187
  // to this type of entity.
188
188
  //
@@ -0,0 +1,55 @@
1
+ import { TestApiProvider, renderInTestApp } from "@backstage/test-utils";
2
+ import React from "react";
3
+ import { IncidentApi, IncidentApiRef } from "../../api/client";
4
+ import { HomePageIncidentCardContent } from "./Content";
5
+
6
+ const mockIncidentApi: jest.Mocked<Partial<IncidentApi>> = {
7
+ request: jest.fn().mockResolvedValue({
8
+ incidents: [
9
+ {
10
+ id: "incident-id",
11
+ name: "Incident",
12
+ reference: "INC-1",
13
+ incident_status: {
14
+ id: "status-id",
15
+ category: "active",
16
+ name: "triage",
17
+ },
18
+ incident_role_assignments: [
19
+ {
20
+ assignee: {
21
+ name: "John Smith",
22
+ },
23
+ role: {
24
+ role_type: "lead",
25
+ },
26
+ },
27
+ ],
28
+ incident_timestamp_values: [
29
+ {
30
+ incident_timestamp: {
31
+ id: "01FCNDV6P870EA6S7TK1DSYD5H",
32
+ name: "reported",
33
+ rank: 1,
34
+ },
35
+ value: {
36
+ value: "2021-08-17T13:28:57.801578Z",
37
+ },
38
+ },
39
+ ],
40
+ },
41
+ ],
42
+ }),
43
+ };
44
+
45
+ describe("HomePageIncidentCardContent", () => {
46
+ it("should render a list of live incidents", async () => {
47
+ const { getByTestId } = await renderInTestApp(
48
+ <TestApiProvider apis={[[IncidentApiRef, mockIncidentApi]]}>
49
+ <HomePageIncidentCardContent />
50
+ </TestApiProvider>,
51
+ );
52
+
53
+ expect(getByTestId("chip-status-id")).toBeInTheDocument();
54
+ });
55
+ });
@@ -0,0 +1,55 @@
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 from "react";
5
+ import { useIncidentList } from "../../hooks/useIncidentRequest";
6
+ import { Typography, List } from "@material-ui/core";
7
+ import { IncidentListItem } from "../IncidentListItem";
8
+ import { configApiRef, useApi } from "@backstage/core-plugin-api";
9
+
10
+ export const HomePageIncidentCardContent = () => {
11
+ const config = useApi(configApiRef);
12
+ const baseUrl = config.getOptionalString('incident.baseUrl') || "https://app.incident.io";
13
+
14
+ const query = new URLSearchParams();
15
+ query.set(`status_category[one_of]`, "active");
16
+ const { loading, error, value } = useIncidentList(query);
17
+ const incidents = value?.incidents;
18
+
19
+ if (loading) return <Progress />;
20
+ if (error) return <Alert severity="error">{error.message}</Alert>;
21
+
22
+ return (
23
+ <>
24
+ {incidents && incidents.length > 0 && (
25
+ <Typography variant="subtitle1">
26
+ There are <strong>{incidents.length}</strong> ongoing incidents.
27
+ </Typography>
28
+ )}
29
+ {incidents && incidents.length === 0 && (
30
+ <Typography variant="subtitle1">No ongoing incidents.</Typography>
31
+ )}
32
+ <List dense>
33
+ {incidents?.map((incident) => {
34
+ return (
35
+ <IncidentListItem
36
+ key={incident.id}
37
+ incident={incident}
38
+ baseUrl={baseUrl}
39
+ />
40
+ );
41
+ })}
42
+ </List>
43
+ <Typography variant="subtitle1">
44
+ Click to{" "}
45
+ <Link target="_blank" href={`${baseUrl}/incidents?${query.toString()}`}>
46
+ see more.
47
+ </Link>
48
+ </Typography>
49
+ </>
50
+ );
51
+ };
52
+
53
+ export const Content = () => {
54
+ return <HomePageIncidentCardContent />;
55
+ };
@@ -0,0 +1 @@
1
+ export { Content } from "./Content";
@@ -13,8 +13,8 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import { DateTime, Duration } from 'luxon';
17
- import { BackstageTheme } from '@backstage/theme';
16
+ import { DateTime, Duration } from "luxon";
17
+ import { BackstageTheme } from "@backstage/theme";
18
18
  import {
19
19
  Chip,
20
20
  IconButton,
@@ -24,27 +24,27 @@ import {
24
24
  Tooltip,
25
25
  Typography,
26
26
  makeStyles,
27
- } from '@material-ui/core';
28
- import OpenInBrowserIcon from '@material-ui/icons/OpenInBrowser';
29
- import React from 'react';
30
- import { definitions } from '../../api/types';
27
+ } from "@material-ui/core";
28
+ import OpenInBrowserIcon from "@material-ui/icons/OpenInBrowser";
29
+ import React from "react";
30
+ import { definitions } from "../../api/types";
31
31
 
32
- const useStyles = makeStyles<BackstageTheme>(theme => ({
32
+ const useStyles = makeStyles<BackstageTheme>((theme) => ({
33
33
  listItemPrimary: {
34
- display: 'flex', // vertically align with chip
35
- fontWeight: 'bold',
34
+ display: "flex", // vertically align with chip
35
+ fontWeight: "bold",
36
36
  },
37
37
  warning: {
38
38
  borderColor: theme.palette.status.warning,
39
39
  color: theme.palette.status.warning,
40
- '& *': {
40
+ "& *": {
41
41
  color: theme.palette.status.warning,
42
42
  },
43
43
  },
44
44
  error: {
45
45
  borderColor: theme.palette.status.error,
46
46
  color: theme.palette.status.error,
47
- '& *': {
47
+ "& *": {
48
48
  color: theme.palette.status.error,
49
49
  },
50
50
  },
@@ -56,10 +56,10 @@ export const IncidentListItem = ({
56
56
  incident,
57
57
  }: {
58
58
  baseUrl: string;
59
- incident: definitions['IncidentV2ResponseBody'];
59
+ incident: definitions["IncidentV2ResponseBody"];
60
60
  }) => {
61
61
  const classes = useStyles();
62
- const reportedAt = incident.incident_timestamp_values?.find(ts =>
62
+ const reportedAt = incident.incident_timestamp_values?.find((ts) =>
63
63
  ts.incident_timestamp.name.match(/reported/i),
64
64
  );
65
65
 
@@ -70,9 +70,9 @@ export const IncidentListItem = ({
70
70
  new Date().getTime() - new Date(reportedAtDate).getTime();
71
71
  const sinceReportedLabel = DateTime.local()
72
72
  .minus(Duration.fromMillis(sinceReported))
73
- .toRelative({ locale: 'en' });
74
- const lead = incident.incident_role_assignments.find(roleAssignment => {
75
- return roleAssignment.role.role_type === 'lead';
73
+ .toRelative({ locale: "en" });
74
+ const lead = incident.incident_role_assignments.find((roleAssignment) => {
75
+ return roleAssignment.role.role_type === "lead";
76
76
  });
77
77
 
78
78
  return (
@@ -86,7 +86,7 @@ export const IncidentListItem = ({
86
86
  size="small"
87
87
  variant="outlined"
88
88
  className={
89
- ['live'].includes(incident.incident_status.category)
89
+ ["active"].includes(incident.incident_status.category)
90
90
  ? classes.error
91
91
  : classes.warning
92
92
  }
@@ -95,15 +95,15 @@ export const IncidentListItem = ({
95
95
  </>
96
96
  }
97
97
  primaryTypographyProps={{
98
- variant: 'body1',
98
+ variant: "body1",
99
99
  className: classes.listItemPrimary,
100
100
  }}
101
101
  secondary={
102
102
  <Typography noWrap variant="body2" color="textSecondary">
103
- Reported {sinceReportedLabel} and{' '}
103
+ Reported {sinceReportedLabel} and{" "}
104
104
  {lead?.assignee
105
105
  ? `${lead.assignee.name} is lead`
106
- : 'the lead is unassigned'}
106
+ : "the lead is unassigned"}
107
107
  .
108
108
  </Typography>
109
109
  }
@@ -0,0 +1,36 @@
1
+ import { useApi } from "@backstage/core-plugin-api";
2
+ import { useAsync } from "react-use";
3
+ import { IncidentApiRef } from "../api/client";
4
+ import { definitions } from "../api/types";
5
+ import { DependencyList } from "react";
6
+
7
+ export const useIncidentList = (
8
+ query: URLSearchParams,
9
+ deps?: DependencyList,
10
+ ) => {
11
+ const IncidentApi = useApi(IncidentApiRef);
12
+
13
+ const { value, loading, error } = useAsync(async () => {
14
+ return await IncidentApi.request<
15
+ definitions["IncidentsV2ListResponseBody"]
16
+ >({
17
+ path: `/v2/incidents?${query.toString()}`,
18
+ });
19
+ }, deps);
20
+
21
+ return { loading, error, value };
22
+ };
23
+
24
+ export const useIdentity = () => {
25
+ const IncidentApi = useApi(IncidentApiRef);
26
+
27
+ const { value, loading, error } = useAsync(async () => {
28
+ return await IncidentApi.request<
29
+ definitions["UtilitiesV1IdentityResponseBody"]
30
+ >({
31
+ path: `/v1/identity`,
32
+ });
33
+ });
34
+
35
+ return { value, loading, error };
36
+ };
package/src/index.ts CHANGED
@@ -13,4 +13,8 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- export { incidentPlugin, EntityIncidentCard } from './plugin';
16
+ export {
17
+ incidentPlugin,
18
+ EntityIncidentCard,
19
+ HomePageIncidentCard,
20
+ } from "./plugin";
package/src/plugin.ts CHANGED
@@ -19,12 +19,13 @@ import {
19
19
  createPlugin,
20
20
  discoveryApiRef,
21
21
  identityApiRef,
22
- } from '@backstage/core-plugin-api';
22
+ } from "@backstage/core-plugin-api";
23
+ import {CardExtensionProps, createCardExtension} from "@backstage/plugin-home-react";
23
24
 
24
- import { IncidentApi, IncidentApiRef } from './api/client';
25
+ import { IncidentApi, IncidentApiRef } from "./api/client";
25
26
 
26
27
  export const incidentPlugin = createPlugin({
27
- id: 'incident',
28
+ id: "incident",
28
29
  apis: [
29
30
  createApiFactory({
30
31
  api: IncidentApiRef,
@@ -41,12 +42,20 @@ export const incidentPlugin = createPlugin({
41
42
 
42
43
  export const EntityIncidentCard = incidentPlugin.provide(
43
44
  createComponentExtension({
44
- name: 'EntityIncidentCard',
45
+ name: "EntityIncidentCard",
45
46
  component: {
46
47
  lazy: () =>
47
- import('./components/EntityIncidentCard').then(
48
- m => m.EntityIncidentCard,
48
+ import("./components/EntityIncidentCard").then(
49
+ (m) => m.EntityIncidentCard,
49
50
  ),
50
51
  },
51
52
  }),
52
53
  );
54
+
55
+ export const HomePageIncidentCard: (props: CardExtensionProps<unknown>) => React.JSX.Element = incidentPlugin.provide(
56
+ createCardExtension({
57
+ name: "HomePageIncidentCard",
58
+ title: "Ongoing Incidents",
59
+ components: () => import("./components/HomePageIncidentCard"),
60
+ }),
61
+ );
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";
17
+ import "cross-fetch/polyfill";
@@ -1,188 +0,0 @@
1
- import { HeaderIconLinkRow, Progress } from '@backstage/core-components';
2
- import { useApi, configApiRef } from '@backstage/core-plugin-api';
3
- import { useEntity } from '@backstage/plugin-catalog-react';
4
- import { makeStyles, ListItem, ListItemText, Chip, Typography, ListItemSecondaryAction, Tooltip, IconButton, Card, CardHeader, Divider, CardContent, 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 { useAsync } from 'react-use';
12
- import { I as IncidentApiRef } from './index-14b311c0.esm.js';
13
- import { DateTime, Duration } from 'luxon';
14
- import OpenInBrowserIcon from '@material-ui/icons/OpenInBrowser';
15
-
16
- function getBaseUrl(config) {
17
- try {
18
- const baseUrl = config.getString("incident.baseUrl");
19
- if (baseUrl !== "") {
20
- return baseUrl;
21
- }
22
- } catch (e) {
23
- }
24
- return "https://app.incident.io";
25
- }
26
-
27
- const useStyles = makeStyles((theme) => ({
28
- listItemPrimary: {
29
- display: "flex",
30
- // vertically align with chip
31
- fontWeight: "bold"
32
- },
33
- warning: {
34
- borderColor: theme.palette.status.warning,
35
- color: theme.palette.status.warning,
36
- "& *": {
37
- color: theme.palette.status.warning
38
- }
39
- },
40
- error: {
41
- borderColor: theme.palette.status.error,
42
- color: theme.palette.status.error,
43
- "& *": {
44
- color: theme.palette.status.error
45
- }
46
- }
47
- }));
48
- const IncidentListItem = ({
49
- baseUrl,
50
- incident
51
- }) => {
52
- var _a, _b;
53
- const classes = useStyles();
54
- const reportedAt = (_a = incident.incident_timestamp_values) == null ? void 0 : _a.find(
55
- (ts) => ts.incident_timestamp.name.match(/reported/i)
56
- );
57
- const reportedAtDate = ((_b = reportedAt == null ? void 0 : reportedAt.value) == null ? void 0 : _b.value) || incident.created_at;
58
- const sinceReported = (/* @__PURE__ */ new Date()).getTime() - new Date(reportedAtDate).getTime();
59
- const sinceReportedLabel = DateTime.local().minus(Duration.fromMillis(sinceReported)).toRelative({ locale: "en" });
60
- const lead = incident.incident_role_assignments.find((roleAssignment) => {
61
- return roleAssignment.role.role_type === "lead";
62
- });
63
- return /* @__PURE__ */ React.createElement(ListItem, { dense: true, key: incident.id }, /* @__PURE__ */ React.createElement(
64
- ListItemText,
65
- {
66
- primary: /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
67
- Chip,
68
- {
69
- "data-testid": `chip-${incident.incident_status.id}`,
70
- label: incident.incident_status.name,
71
- size: "small",
72
- variant: "outlined",
73
- className: ["live"].includes(incident.incident_status.category) ? classes.error : classes.warning
74
- }
75
- ), incident.reference, " ", incident.name),
76
- primaryTypographyProps: {
77
- variant: "body1",
78
- className: classes.listItemPrimary
79
- },
80
- 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", ".")
81
- }
82
- ), /* @__PURE__ */ React.createElement(ListItemSecondaryAction, null, /* @__PURE__ */ React.createElement(Tooltip, { title: "View in incident.io", placement: "top" }, /* @__PURE__ */ React.createElement(
83
- IconButton,
84
- {
85
- href: `${baseUrl}/incidents/${incident.id}`,
86
- target: "_blank",
87
- rel: "noopener noreferrer",
88
- color: "primary"
89
- },
90
- /* @__PURE__ */ React.createElement(OpenInBrowserIcon, null)
91
- ))));
92
- };
93
-
94
- const EntityIncidentCard = ({
95
- maxIncidents = 2
96
- }) => {
97
- var _a;
98
- const config = useApi(configApiRef);
99
- const baseUrl = getBaseUrl(config);
100
- const { entity } = useEntity();
101
- const IncidentApi = useApi(IncidentApiRef);
102
- const [reload, setReload] = useState(false);
103
- const entityFieldID = getEntityFieldID(config, entity);
104
- if (!entityFieldID) {
105
- return /* @__PURE__ */ React.createElement(IncorrectConfigCard, null);
106
- }
107
- const entityID = `${entity.metadata.namespace}/${entity.metadata.name}`;
108
- const query = new URLSearchParams();
109
- query.set(`custom_field[${entityFieldID}][one_of]`, entityID);
110
- const queryLive = new URLSearchParams(query);
111
- queryLive.set(`status_category[one_of]`, "live");
112
- const createIncidentLink = {
113
- label: "Create incident",
114
- disabled: false,
115
- icon: /* @__PURE__ */ React.createElement(WhatshotIcon, null),
116
- href: `${baseUrl}/incidents/create`
117
- };
118
- const viewIncidentsLink = {
119
- label: "View past incidents",
120
- disabled: false,
121
- icon: /* @__PURE__ */ React.createElement(HistoryIcon, null),
122
- href: `${baseUrl}/incidents?${query.toString()}`
123
- };
124
- const {
125
- value: incidentsResponse,
126
- loading: incidentsLoading,
127
- error: incidentsError
128
- } = useAsync(async () => {
129
- return await IncidentApi.request({
130
- path: `/v2/incidents?${queryLive.toString()}`
131
- });
132
- }, [reload]);
133
- const incidents = incidentsResponse == null ? void 0 : incidentsResponse.incidents;
134
- return /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(
135
- CardHeader,
136
- {
137
- title: "incident.io",
138
- action: /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
139
- IconButton,
140
- {
141
- component: Link,
142
- "aria-label": "Refresh",
143
- disabled: false,
144
- title: "Refresh",
145
- onClick: () => setReload(!reload)
146
- },
147
- /* @__PURE__ */ React.createElement(CachedIcon, null)
148
- )),
149
- subheader: /* @__PURE__ */ React.createElement(HeaderIconLinkRow, { links: [createIncidentLink, viewIncidentsLink] })
150
- }
151
- ), /* @__PURE__ */ React.createElement(Divider, null), /* @__PURE__ */ React.createElement(CardContent, null, incidentsLoading && /* @__PURE__ */ React.createElement(Progress, null), incidentsError && /* @__PURE__ */ React.createElement(Alert, { severity: "error" }, incidentsError.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) => {
152
- return /* @__PURE__ */ React.createElement(
153
- IncidentListItem,
154
- {
155
- key: incident.id,
156
- incident,
157
- baseUrl
158
- }
159
- );
160
- })), /* @__PURE__ */ React.createElement(Typography, { variant: "subtitle1" }, "Click to", " ", /* @__PURE__ */ React.createElement(
161
- Link,
162
- {
163
- target: "_blank",
164
- href: `${baseUrl}/incidents?${queryLive.toString()}`
165
- },
166
- "see more."
167
- )))));
168
- };
169
- const IncorrectConfigCard = () => {
170
- 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.")));
171
- };
172
- function getEntityFieldID(config, entity) {
173
- switch (entity.kind) {
174
- case "API":
175
- return config.getOptional("incident.fields.api");
176
- case "Component":
177
- return config.getOptional("incident.fields.component");
178
- case "Domain":
179
- return config.getOptional("incident.fields.domain");
180
- case "System":
181
- return config.getOptional("incident.fields.system");
182
- default:
183
- throw new Error(`unrecognised entity kind: ${entity.kind}`);
184
- }
185
- }
186
-
187
- export { EntityIncidentCard };
188
- //# sourceMappingURL=index-08133e09.esm.js.map