@incident-io/backstage 0.0.10 → 0.0.12

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.
@@ -13,14 +13,14 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import { Entity } from '@backstage/catalog-model';
16
+ import { Entity } from "@backstage/catalog-model";
17
17
  import {
18
18
  HeaderIconLinkRow,
19
19
  IconLinkVerticalProps,
20
20
  Progress,
21
- } from '@backstage/core-components';
22
- import { ConfigApi, configApiRef, useApi } from '@backstage/core-plugin-api';
23
- import { useEntity } from '@backstage/plugin-catalog-react';
21
+ } from "@backstage/core-components";
22
+ import { ConfigApi, configApiRef, useApi } from "@backstage/core-plugin-api";
23
+ import { useEntity } from "@backstage/plugin-catalog-react";
24
24
  import {
25
25
  Card,
26
26
  CardContent,
@@ -29,16 +29,15 @@ import {
29
29
  IconButton,
30
30
  List,
31
31
  Typography,
32
- } from '@material-ui/core';
33
- import Link from '@material-ui/core/Link';
34
- import CachedIcon from '@material-ui/icons/Cached';
35
- import HistoryIcon from '@material-ui/icons/History';
36
- import WhatshotIcon from '@material-ui/icons/Whatshot';
37
- import { Alert } from '@material-ui/lab';
38
- import React, { useState } from 'react';
39
- import { getBaseUrl } from '../../config';
40
- import { useIncidentList } from '../../hooks/useIncidentRequest';
41
- import { IncidentListItem } from '../IncidentListItem';
32
+ } from "@material-ui/core";
33
+ import Link from "@material-ui/core/Link";
34
+ import CachedIcon from "@material-ui/icons/Cached";
35
+ import HistoryIcon from "@material-ui/icons/History";
36
+ import WhatshotIcon from "@material-ui/icons/Whatshot";
37
+ import { Alert } from "@material-ui/lab";
38
+ import React, { useState } from "react";
39
+ import { useIncidentList, useIdentity } from "../../hooks/useIncidentRequest";
40
+ import { IncidentListItem } from "../IncidentListItem";
42
41
 
43
42
  const IncorrectConfigCard = () => {
44
43
  return (
@@ -64,8 +63,12 @@ export const EntityIncidentCard = ({
64
63
  maxIncidents?: number;
65
64
  }) => {
66
65
  const config = useApi(configApiRef);
67
- const baseUrl = getBaseUrl(config);
68
66
  const { entity } = useEntity();
67
+ const {
68
+ value: identityResponse,
69
+ loading: identityResponseLoading,
70
+ error: identityResponseError,
71
+ } = useIdentity();
69
72
 
70
73
  const [reload, setReload] = useState(false);
71
74
 
@@ -79,21 +82,7 @@ export const EntityIncidentCard = ({
79
82
 
80
83
  // This restricts the previous filter to focus only on live incidents.
81
84
  const queryLive = new URLSearchParams(query);
82
- queryLive.set(`status_category[one_of]`, 'live');
83
-
84
- const createIncidentLink: IconLinkVerticalProps = {
85
- label: 'Create incident',
86
- disabled: false,
87
- icon: <WhatshotIcon />,
88
- href: `${baseUrl}/incidents/create`,
89
- };
90
-
91
- const viewIncidentsLink: IconLinkVerticalProps = {
92
- label: 'View past incidents',
93
- disabled: false,
94
- icon: <HistoryIcon />,
95
- href: `${baseUrl}/incidents?${query.toString()}`,
96
- };
85
+ queryLive.set(`status_category[one_of]`, "active");
97
86
 
98
87
  const {
99
88
  value: incidentsResponse,
@@ -107,6 +96,26 @@ export const EntityIncidentCard = ({
107
96
  return <IncorrectConfigCard />;
108
97
  }
109
98
 
99
+ if (incidentsLoading || identityResponseLoading || !identityResponse) {
100
+ return <Progress />;
101
+ }
102
+
103
+ const baseUrl = identityResponse.identity.dashboard_url;
104
+
105
+ const createIncidentLink: IconLinkVerticalProps = {
106
+ label: "Declare incident",
107
+ disabled: false,
108
+ icon: <WhatshotIcon />,
109
+ href: `${baseUrl}/incidents/create`,
110
+ };
111
+
112
+ const viewIncidentsLink: IconLinkVerticalProps = {
113
+ label: "View past incidents",
114
+ disabled: false,
115
+ icon: <HistoryIcon />,
116
+ href: `${baseUrl}/incidents?${query.toString()}`,
117
+ };
118
+
110
119
  return (
111
120
  <Card>
112
121
  <CardHeader
@@ -130,10 +139,12 @@ export const EntityIncidentCard = ({
130
139
  />
131
140
  <Divider />
132
141
  <CardContent>
133
- {incidentsLoading && <Progress />}
134
142
  {incidentsError && (
135
143
  <Alert severity="error">{incidentsError.message}</Alert>
136
144
  )}
145
+ {identityResponseError && (
146
+ <Alert severity="error">{identityResponseError.message}</Alert>
147
+ )}
137
148
  {!incidentsLoading && !incidentsError && incidents && (
138
149
  <>
139
150
  {incidents && incidents.length > 0 && (
@@ -157,7 +168,7 @@ export const EntityIncidentCard = ({
157
168
  })}
158
169
  </List>
159
170
  <Typography variant="subtitle1">
160
- Click to{' '}
171
+ Click to{" "}
161
172
  <Link
162
173
  target="_blank"
163
174
  href={`${baseUrl}/incidents?${queryLive.toString()}`}
@@ -178,14 +189,14 @@ export const EntityIncidentCard = ({
178
189
  // In practice, this will be kind=Component => ID of Affected components field.
179
190
  function getEntityFieldID(config: ConfigApi, entity: Entity) {
180
191
  switch (entity.kind) {
181
- case 'API':
182
- return config.getOptional('incident.fields.api');
183
- case 'Component':
184
- return config.getOptional('incident.fields.component');
185
- case 'Domain':
186
- return config.getOptional('incident.fields.domain');
187
- case 'System':
188
- return config.getOptional('incident.fields.system');
192
+ case "API":
193
+ return config.getOptional("incident.fields.api");
194
+ case "Component":
195
+ return config.getOptional("incident.fields.component");
196
+ case "Domain":
197
+ return config.getOptional("incident.fields.domain");
198
+ case "System":
199
+ return config.getOptional("incident.fields.system");
189
200
  default:
190
201
  throw new Error(`unrecognised entity kind: ${entity.kind}`);
191
202
  }
@@ -1,39 +1,39 @@
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';
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
5
 
6
6
  const mockIncidentApi: jest.Mocked<Partial<IncidentApi>> = {
7
7
  request: jest.fn().mockResolvedValue({
8
8
  incidents: [
9
9
  {
10
- id: 'incident-id',
11
- name: 'Incident',
12
- reference: 'INC-1',
10
+ id: "incident-id",
11
+ name: "Incident",
12
+ reference: "INC-1",
13
13
  incident_status: {
14
- id: 'status-id',
15
- category: 'live',
16
- name: 'triage',
14
+ id: "status-id",
15
+ category: "active",
16
+ name: "triage",
17
17
  },
18
18
  incident_role_assignments: [
19
19
  {
20
20
  assignee: {
21
- name: 'John Smith',
21
+ name: "John Smith",
22
22
  },
23
23
  role: {
24
- role_type: 'lead',
24
+ role_type: "lead",
25
25
  },
26
26
  },
27
27
  ],
28
28
  incident_timestamp_values: [
29
29
  {
30
30
  incident_timestamp: {
31
- id: '01FCNDV6P870EA6S7TK1DSYD5H',
32
- name: 'reported',
31
+ id: "01FCNDV6P870EA6S7TK1DSYD5H",
32
+ name: "reported",
33
33
  rank: 1,
34
34
  },
35
35
  value: {
36
- value: '2021-08-17T13:28:57.801578Z',
36
+ value: "2021-08-17T13:28:57.801578Z",
37
37
  },
38
38
  },
39
39
  ],
@@ -42,14 +42,14 @@ const mockIncidentApi: jest.Mocked<Partial<IncidentApi>> = {
42
42
  }),
43
43
  };
44
44
 
45
- describe('HomePageIncidentCardContent', () => {
46
- it('should render a list of live incidents', async () => {
45
+ describe("HomePageIncidentCardContent", () => {
46
+ it("should render a list of live incidents", async () => {
47
47
  const { getByTestId } = await renderInTestApp(
48
48
  <TestApiProvider apis={[[IncidentApiRef, mockIncidentApi]]}>
49
49
  <HomePageIncidentCardContent />
50
- </TestApiProvider>
50
+ </TestApiProvider>,
51
51
  );
52
52
 
53
- expect(getByTestId('chip-status-id')).toBeInTheDocument();
53
+ expect(getByTestId("chip-status-id")).toBeInTheDocument();
54
54
  });
55
55
  });
@@ -1,19 +1,18 @@
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
- import { getBaseUrl } from '../../config';
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";
10
9
 
11
10
  export const HomePageIncidentCardContent = () => {
12
11
  const config = useApi(configApiRef);
13
- const baseUrl = getBaseUrl(config);
12
+ const baseUrl = config.getOptionalString('incident.baseUrl') || "https://app.incident.io";
14
13
 
15
14
  const query = new URLSearchParams();
16
- query.set(`status_category[one_of]`, 'live');
15
+ query.set(`status_category[one_of]`, "active");
17
16
  const { loading, error, value } = useIncidentList(query);
18
17
  const incidents = value?.incidents;
19
18
 
@@ -42,7 +41,7 @@ export const HomePageIncidentCardContent = () => {
42
41
  })}
43
42
  </List>
44
43
  <Typography variant="subtitle1">
45
- Click to{' '}
44
+ Click to{" "}
46
45
  <Link target="_blank" href={`${baseUrl}/incidents?${query.toString()}`}>
47
46
  see more.
48
47
  </Link>
@@ -1 +1 @@
1
- export { Content } from './Content';
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
  }
@@ -1,18 +1,18 @@
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';
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
6
 
7
7
  export const useIncidentList = (
8
8
  query: URLSearchParams,
9
- deps?: DependencyList
9
+ deps?: DependencyList,
10
10
  ) => {
11
11
  const IncidentApi = useApi(IncidentApiRef);
12
12
 
13
13
  const { value, loading, error } = useAsync(async () => {
14
14
  return await IncidentApi.request<
15
- definitions['IncidentsV2ListResponseBody']
15
+ definitions["IncidentsV2ListResponseBody"]
16
16
  >({
17
17
  path: `/v2/incidents?${query.toString()}`,
18
18
  });
@@ -20,3 +20,17 @@ export const useIncidentList = (
20
20
 
21
21
  return { loading, error, value };
22
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
@@ -17,4 +17,4 @@ export {
17
17
  incidentPlugin,
18
18
  EntityIncidentCard,
19
19
  HomePageIncidentCard,
20
- } from './plugin';
20
+ } from "./plugin";
package/src/plugin.ts CHANGED
@@ -19,13 +19,13 @@ import {
19
19
  createPlugin,
20
20
  discoveryApiRef,
21
21
  identityApiRef,
22
- } from '@backstage/core-plugin-api';
23
- import { createCardExtension } from '@backstage/plugin-home-react';
22
+ } from "@backstage/core-plugin-api";
23
+ import {CardExtensionProps, createCardExtension} from "@backstage/plugin-home-react";
24
24
 
25
- import { IncidentApi, IncidentApiRef } from './api/client';
25
+ import { IncidentApi, IncidentApiRef } from "./api/client";
26
26
 
27
27
  export const incidentPlugin = createPlugin({
28
- id: 'incident',
28
+ id: "incident",
29
29
  apis: [
30
30
  createApiFactory({
31
31
  api: IncidentApiRef,
@@ -42,20 +42,20 @@ export const incidentPlugin = createPlugin({
42
42
 
43
43
  export const EntityIncidentCard = incidentPlugin.provide(
44
44
  createComponentExtension({
45
- name: 'EntityIncidentCard',
45
+ name: "EntityIncidentCard",
46
46
  component: {
47
47
  lazy: () =>
48
- import('./components/EntityIncidentCard').then(
49
- (m) => m.EntityIncidentCard
48
+ import("./components/EntityIncidentCard").then(
49
+ (m) => m.EntityIncidentCard,
50
50
  ),
51
51
  },
52
- })
52
+ }),
53
53
  );
54
54
 
55
- export const HomePageIncidentCard = incidentPlugin.provide(
55
+ export const HomePageIncidentCard: (props: CardExtensionProps<unknown>) => React.JSX.Element = incidentPlugin.provide(
56
56
  createCardExtension({
57
- name: 'HomePageIncidentCard',
58
- title: 'Ongoing Incidents',
59
- components: () => import('./components/HomePageIncidentCard'),
60
- })
57
+ name: "HomePageIncidentCard",
58
+ title: "Ongoing Incidents",
59
+ components: () => import("./components/HomePageIncidentCard"),
60
+ }),
61
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 +0,0 @@
1
- {"version":3,"file":"index-40a020a8.esm.js","sources":["../../src/config.ts","../../src/hooks/useIncidentRequest.ts","../../src/components/IncidentListItem/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 { ConfigApi } from '@backstage/core-plugin-api';\n\n// Find the baseUrl of the incident dashboard.\nexport function getBaseUrl(config: ConfigApi) {\n try {\n const baseUrl = config.getString('incident.baseUrl');\n if (baseUrl !== '') {\n return baseUrl;\n }\n } catch (e) {\n // no action\n }\n\n return 'https://app.incident.io';\n}\n","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","/*\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 ['live'].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 +0,0 @@
1
- {"version":3,"file":"index-6eb32695.esm.js","sources":["../../src/api/client.ts","../../src/plugin.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 IdentityApi,\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 identityApi: IdentityApi;\n proxyPath?: string;\n};\n\nexport class IncidentApi implements Incident {\n private readonly discoveryApi: DiscoveryApi;\n private readonly identityApi: IdentityApi;\n private readonly proxyPath: string;\n\n constructor(opts: Options) {\n this.discoveryApi = opts.discoveryApi;\n this.identityApi = opts.identityApi;\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 const { token } = await this.identityApi.getCredentials();\n\n const resp = await fetch(`${apiUrl}${path}`, {\n method: method,\n body: body,\n headers: {\n Authorization: `Bearer ${token}`,\n },\n });\n if (!resp.ok) {\n throw new Error(`${resp.status} ${resp.statusText}`);\n }\n\n return await resp.json();\n }\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 {\n createApiFactory,\n createComponentExtension,\n createPlugin,\n discoveryApiRef,\n identityApiRef,\n} from '@backstage/core-plugin-api';\nimport { createCardExtension } from '@backstage/plugin-home-react';\n\nimport { IncidentApi, IncidentApiRef } from './api/client';\n\nexport const incidentPlugin = createPlugin({\n id: 'incident',\n apis: [\n createApiFactory({\n api: IncidentApiRef,\n deps: { discoveryApi: discoveryApiRef, identityApi: identityApiRef },\n factory: ({ discoveryApi, identityApi }) => {\n return new IncidentApi({\n discoveryApi: discoveryApi,\n identityApi: identityApi,\n });\n },\n }),\n ],\n});\n\nexport const EntityIncidentCard = incidentPlugin.provide(\n createComponentExtension({\n name: 'EntityIncidentCard',\n component: {\n lazy: () =>\n import('./components/EntityIncidentCard').then(\n (m) => m.EntityIncidentCard\n ),\n },\n })\n);\n\nexport const HomePageIncidentCard = incidentPlugin.provide(\n createCardExtension({\n name: 'HomePageIncidentCard',\n title: 'Ongoing Incidents',\n components: () => import('./components/HomePageIncidentCard'),\n })\n);\n"],"names":[],"mappings":";;;;;;;;;AAqBO,MAAM,iBAAiB,YAAuB,CAAA;AAAA,EACnD,EAAI,EAAA,yBAAA;AACN,CAAC,EAAA;AAgBD,MAAM,kBAAqB,GAAA,eAAA,CAAA;AAQpB,MAAM,WAAgC,CAAA;AAAA,EAK3C,YAAY,IAAe,EAAA;AAJ3B,IAAiB,aAAA,CAAA,IAAA,EAAA,cAAA,CAAA,CAAA;AACjB,IAAiB,aAAA,CAAA,IAAA,EAAA,aAAA,CAAA,CAAA;AACjB,IAAiB,aAAA,CAAA,IAAA,EAAA,WAAA,CAAA,CAAA;AAlDnB,IAAA,IAAA,EAAA,CAAA;AAqDI,IAAA,IAAA,CAAK,eAAe,IAAK,CAAA,YAAA,CAAA;AACzB,IAAA,IAAA,CAAK,cAAc,IAAK,CAAA,WAAA,CAAA;AACxB,IAAK,IAAA,CAAA,SAAA,GAAA,CAAY,EAAK,GAAA,IAAA,CAAA,SAAA,KAAL,IAAkB,GAAA,EAAA,GAAA,kBAAA,CAAA;AAAA,GACrC;AAAA,EAEA,MAAM,OAAiB,CAAA;AAAA,IACrB,IAAA;AAAA,IACA,MAAS,GAAA,KAAA;AAAA,IACT,IAAA;AAAA,GAKa,EAAA;AACb,IAAA,MAAM,SACH,MAAM,IAAA,CAAK,aAAa,UAAW,CAAA,OAAO,IAAK,IAAK,CAAA,SAAA,CAAA;AACvD,IAAA,MAAM,EAAE,KAAM,EAAA,GAAI,MAAM,IAAA,CAAK,YAAY,cAAe,EAAA,CAAA;AAExD,IAAA,MAAM,OAAO,MAAM,KAAA,CAAM,GAAG,MAAM,CAAA,EAAG,IAAI,CAAI,CAAA,EAAA;AAAA,MAC3C,MAAA;AAAA,MACA,IAAA;AAAA,MACA,OAAS,EAAA;AAAA,QACP,aAAA,EAAe,UAAU,KAAK,CAAA,CAAA;AAAA,OAChC;AAAA,KACD,CAAA,CAAA;AACD,IAAI,IAAA,CAAC,KAAK,EAAI,EAAA;AACZ,MAAM,MAAA,IAAI,MAAM,CAAG,EAAA,IAAA,CAAK,MAAM,CAAI,CAAA,EAAA,IAAA,CAAK,UAAU,CAAE,CAAA,CAAA,CAAA;AAAA,KACrD;AAEA,IAAO,OAAA,MAAM,KAAK,IAAK,EAAA,CAAA;AAAA,GACzB;AACF;;AC1DO,MAAM,iBAAiB,YAAa,CAAA;AAAA,EACzC,EAAI,EAAA,UAAA;AAAA,EACJ,IAAM,EAAA;AAAA,IACJ,gBAAiB,CAAA;AAAA,MACf,GAAK,EAAA,cAAA;AAAA,MACL,IAAM,EAAA,EAAE,YAAc,EAAA,eAAA,EAAiB,aAAa,cAAe,EAAA;AAAA,MACnE,OAAS,EAAA,CAAC,EAAE,YAAA,EAAc,aAAkB,KAAA;AAC1C,QAAA,OAAO,IAAI,WAAY,CAAA;AAAA,UACrB,YAAA;AAAA,UACA,WAAA;AAAA,SACD,CAAA,CAAA;AAAA,OACH;AAAA,KACD,CAAA;AAAA,GACH;AACF,CAAC,EAAA;AAEM,MAAM,qBAAqB,cAAe,CAAA,OAAA;AAAA,EAC/C,wBAAyB,CAAA;AAAA,IACvB,IAAM,EAAA,oBAAA;AAAA,IACN,SAAW,EAAA;AAAA,MACT,IAAM,EAAA,MACJ,OAAO,yBAAiC,CAAE,CAAA,IAAA;AAAA,QACxC,CAAC,MAAM,CAAE,CAAA,kBAAA;AAAA,OACX;AAAA,KACJ;AAAA,GACD,CAAA;AACH,EAAA;AAEO,MAAM,uBAAuB,cAAe,CAAA,OAAA;AAAA,EACjD,mBAAoB,CAAA;AAAA,IAClB,IAAM,EAAA,sBAAA;AAAA,IACN,KAAO,EAAA,mBAAA;AAAA,IACP,UAAA,EAAY,MAAM,OAAO,yBAAmC,CAAA;AAAA,GAC7D,CAAA;AACH;;;;"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index-a76fa310.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 { getBaseUrl } from '../../config';\nimport { useIncidentList } 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 baseUrl = getBaseUrl(config);\n const { entity } = useEntity();\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]`, 'live');\n\n const createIncidentLink: IconLinkVerticalProps = {\n label: 'Create 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 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 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 {incidentsLoading && <Progress />}\n {incidentsError && (\n <Alert severity=\"error\">{incidentsError.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 default:\n throw new Error(`unrecognised entity kind: ${entity.kind}`);\n }\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AA0CA,MAAM,sBAAsB,MAAM;AAChC,EAAA,2CACG,IACC,EAAA,IAAA,kBAAA,KAAA,CAAA,aAAA,CAAC,cAAW,KAAM,EAAA,aAAA,EAAc,mBAC/B,KAAA,CAAA,aAAA,CAAA,OAAA,EAAA,IAAQ,CACT,kBAAA,KAAA,CAAA,aAAA,CAAC,mCACE,KAAA,CAAA,aAAA,CAAA,UAAA,EAAA,EAAW,SAAQ,WAAY,EAAA,EAAA,sKAIhC,CACF,CACF,CAAA,CAAA;AAEJ,CAAA,CAAA;AAIO,MAAM,qBAAqB,CAAC;AAAA,EACjC,YAAe,GAAA,CAAA;AACjB,CAEM,KAAA;AAhEN,EAAA,IAAA,EAAA,CAAA;AAiEE,EAAM,MAAA,MAAA,GAAS,OAAO,YAAY,CAAA,CAAA;AAClC,EAAM,MAAA,OAAA,GAAU,WAAW,MAAM,CAAA,CAAA;AACjC,EAAM,MAAA,EAAE,MAAO,EAAA,GAAI,SAAU,EAAA,CAAA;AAE7B,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,SAAS,KAAK,CAAA,CAAA;AAE1C,EAAM,MAAA,aAAA,GAAgB,gBAAiB,CAAA,MAAA,EAAQ,MAAM,CAAA,CAAA;AACrD,EAAM,MAAA,QAAA,GAAW,GAAG,MAAO,CAAA,QAAA,CAAS,SAAS,CAAI,CAAA,EAAA,MAAA,CAAO,SAAS,IAAI,CAAA,CAAA,CAAA;AAIrE,EAAM,MAAA,KAAA,GAAQ,IAAI,eAAgB,EAAA,CAAA;AAClC,EAAA,KAAA,CAAM,GAAI,CAAA,CAAA,aAAA,EAAgB,aAAa,CAAA,SAAA,CAAA,EAAa,QAAQ,CAAA,CAAA;AAG5D,EAAM,MAAA,SAAA,GAAY,IAAI,eAAA,CAAgB,KAAK,CAAA,CAAA;AAC3C,EAAU,SAAA,CAAA,GAAA,CAAI,2BAA2B,MAAM,CAAA,CAAA;AAE/C,EAAA,MAAM,kBAA4C,GAAA;AAAA,IAChD,KAAO,EAAA,iBAAA;AAAA,IACP,QAAU,EAAA,KAAA;AAAA,IACV,IAAA,sCAAO,YAAa,EAAA,IAAA,CAAA;AAAA,IACpB,IAAA,EAAM,GAAG,OAAO,CAAA,iBAAA,CAAA;AAAA,GAClB,CAAA;AAEA,EAAA,MAAM,iBAA2C,GAAA;AAAA,IAC/C,KAAO,EAAA,qBAAA;AAAA,IACP,QAAU,EAAA,KAAA;AAAA,IACV,IAAA,sCAAO,WAAY,EAAA,IAAA,CAAA;AAAA,IACnB,MAAM,CAAG,EAAA,OAAO,CAAc,WAAA,EAAA,KAAA,CAAM,UAAU,CAAA,CAAA;AAAA,GAChD,CAAA;AAEA,EAAM,MAAA;AAAA,IACJ,KAAO,EAAA,iBAAA;AAAA,IACP,OAAS,EAAA,gBAAA;AAAA,IACT,KAAO,EAAA,cAAA;AAAA,GACL,GAAA,eAAA,CAAgB,SAAW,EAAA,CAAC,MAAM,CAAC,CAAA,CAAA;AAEvC,EAAA,MAAM,YAAY,iBAAmB,IAAA,IAAA,GAAA,KAAA,CAAA,GAAA,iBAAA,CAAA,SAAA,CAAA;AAErC,EAAA,IAAI,CAAC,aAAe,EAAA;AAClB,IAAA,2CAAQ,mBAAoB,EAAA,IAAA,CAAA,CAAA;AAAA,GAC9B;AAEA,EAAA,2CACG,IACC,EAAA,IAAA,kBAAA,KAAA,CAAA,aAAA;AAAA,IAAC,UAAA;AAAA,IAAA;AAAA,MACC,KAAM,EAAA,aAAA;AAAA,MACN,wBAEI,KAAA,CAAA,aAAA,CAAA,KAAA,CAAA,QAAA,EAAA,IAAA,kBAAA,KAAA,CAAA,aAAA;AAAA,QAAC,UAAA;AAAA,QAAA;AAAA,UACC,SAAW,EAAA,IAAA;AAAA,UACX,YAAW,EAAA,SAAA;AAAA,UACX,QAAU,EAAA,KAAA;AAAA,UACV,KAAM,EAAA,SAAA;AAAA,UACN,OAAS,EAAA,MAAM,SAAU,CAAA,CAAC,MAAM,CAAA;AAAA,SAAA;AAAA,4CAE/B,UAAW,EAAA,IAAA,CAAA;AAAA,OAEhB,CAAA;AAAA,MAEF,2BACG,KAAA,CAAA,aAAA,CAAA,iBAAA,EAAA,EAAkB,OAAO,CAAC,kBAAA,EAAoB,iBAAiB,CAAG,EAAA,CAAA;AAAA,KAAA;AAAA,GAGvE,kBAAA,KAAA,CAAA,aAAA,CAAC,OAAQ,EAAA,IAAA,CAAA,sCACR,WACE,EAAA,IAAA,EAAA,gBAAA,oBAAqB,KAAA,CAAA,aAAA,CAAA,QAAA,EAAA,IAAS,CAC9B,EAAA,cAAA,oBACE,KAAA,CAAA,aAAA,CAAA,KAAA,EAAA,EAAM,UAAS,OAAS,EAAA,EAAA,cAAA,CAAe,OAAQ,CAAA,EAEjD,CAAC,gBAAA,IAAoB,CAAC,cAAA,IAAkB,6BAEpC,KAAA,CAAA,aAAA,CAAA,KAAA,CAAA,QAAA,EAAA,IAAA,EAAA,SAAA,IAAa,SAAU,CAAA,MAAA,GAAS,CAC/B,oBAAA,KAAA,CAAA,aAAA,CAAC,UAAW,EAAA,EAAA,OAAA,EAAQ,eAAY,YACpB,kBAAA,KAAA,CAAA,aAAA,CAAC,QAAQ,EAAA,IAAA,EAAA,SAAA,CAAU,MAAO,CAAA,EAAS,+BACnC,kBAAA,KAAA,CAAA,aAAA,CAAC,gBAAQ,MAAO,CAAA,QAAA,CAAS,IAAK,CAAA,EAAS,GACnD,CAAA,EAED,SAAa,IAAA,SAAA,CAAU,WAAW,CACjC,oBAAA,KAAA,CAAA,aAAA,CAAC,UAAW,EAAA,EAAA,OAAA,EAAQ,WAAY,EAAA,EAAA,uBAAqB,CAEvD,kBAAA,KAAA,CAAA,aAAA,CAAC,QAAK,KAAK,EAAA,IAAA,EAAA,EAAA,CACR,EAAW,GAAA,SAAA,IAAA,IAAA,GAAA,KAAA,CAAA,GAAA,SAAA,CAAA,KAAA,CAAM,CAAG,EAAA,YAAA,CAAA,KAApB,IAAmC,GAAA,KAAA,CAAA,GAAA,EAAA,CAAA,GAAA,CAAI,CAAC,QAAa,KAAA;AACpD,IACE,uBAAA,KAAA,CAAA,aAAA;AAAA,MAAC,gBAAA;AAAA,MAAA;AAAA,QACC,KAAK,QAAS,CAAA,EAAA;AAAA,QACd,QAAA;AAAA,QACA,OAAA;AAAA,OAAA;AAAA,KACF,CAAA;AAAA,IAGN,CACA,kBAAA,KAAA,CAAA,aAAA,CAAC,cAAW,OAAQ,EAAA,WAAA,EAAA,EAAY,YACrB,GACT,kBAAA,KAAA,CAAA,aAAA;AAAA,IAAC,IAAA;AAAA,IAAA;AAAA,MACC,MAAO,EAAA,QAAA;AAAA,MACP,MAAM,CAAG,EAAA,OAAO,CAAc,WAAA,EAAA,SAAA,CAAU,UAAU,CAAA,CAAA;AAAA,KAAA;AAAA,IACnD,WAAA;AAAA,GAGH,CACF,CAEJ,CACF,CAAA,CAAA;AAEJ,EAAA;AAMA,SAAS,gBAAA,CAAiB,QAAmB,MAAgB,EAAA;AAC3D,EAAA,QAAQ,OAAO,IAAM;AAAA,IACnB,KAAK,KAAA;AACH,MAAO,OAAA,MAAA,CAAO,YAAY,qBAAqB,CAAA,CAAA;AAAA,IACjD,KAAK,WAAA;AACH,MAAO,OAAA,MAAA,CAAO,YAAY,2BAA2B,CAAA,CAAA;AAAA,IACvD,KAAK,QAAA;AACH,MAAO,OAAA,MAAA,CAAO,YAAY,wBAAwB,CAAA,CAAA;AAAA,IACpD,KAAK,QAAA;AACH,MAAO,OAAA,MAAA,CAAO,YAAY,wBAAwB,CAAA,CAAA;AAAA,IACpD;AACE,MAAA,MAAM,IAAI,KAAA,CAAM,CAA6B,0BAAA,EAAA,MAAA,CAAO,IAAI,CAAE,CAAA,CAAA,CAAA;AAAA,GAC9D;AACF;;;;"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index-ba2cdf39.esm.js","sources":["../../src/components/HomePageIncidentCard/Content.tsx"],"sourcesContent":["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 { getBaseUrl } from '../../config';\n\nexport const HomePageIncidentCardContent = () => {\n const config = useApi(configApiRef);\n const baseUrl = getBaseUrl(config);\n\n const query = new URLSearchParams();\n query.set(`status_category[one_of]`, 'live');\n const { loading, error, value } = useIncidentList(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":";;;;;;;;;;;;;AAUO,MAAM,8BAA8B,MAAM;AAC/C,EAAM,MAAA,MAAA,GAAS,OAAO,YAAY,CAAA,CAAA;AAClC,EAAM,MAAA,OAAA,GAAU,WAAW,MAAM,CAAA,CAAA;AAEjC,EAAM,MAAA,KAAA,GAAQ,IAAI,eAAgB,EAAA,CAAA;AAClC,EAAM,KAAA,CAAA,GAAA,CAAI,2BAA2B,MAAM,CAAA,CAAA;AAC3C,EAAA,MAAM,EAAE,OAAS,EAAA,KAAA,EAAO,KAAM,EAAA,GAAI,gBAAgB,KAAK,CAAA,CAAA;AACvD,EAAA,MAAM,YAAY,KAAO,IAAA,IAAA,GAAA,KAAA,CAAA,GAAA,KAAA,CAAA,SAAA,CAAA;AAEzB,EAAI,IAAA,OAAA;AAAS,IAAA,2CAAQ,QAAS,EAAA,IAAA,CAAA,CAAA;AAC9B,EAAI,IAAA,KAAA;AAAO,IAAA,uBAAQ,KAAA,CAAA,aAAA,CAAA,KAAA,EAAA,EAAM,QAAS,EAAA,OAAA,EAAA,EAAS,MAAM,OAAQ,CAAA,CAAA;AAEzD,EAAA,uBAEK,KAAA,CAAA,aAAA,CAAA,KAAA,CAAA,QAAA,EAAA,IAAA,EAAA,SAAA,IAAa,SAAU,CAAA,MAAA,GAAS,qBAC9B,KAAA,CAAA,aAAA,CAAA,UAAA,EAAA,EAAW,OAAQ,EAAA,WAAA,EAAA,EAAY,8BACnB,KAAA,CAAA,aAAA,CAAA,QAAA,EAAA,IAAA,EAAQ,SAAU,CAAA,MAAO,GAAS,qBAC/C,CAAA,EAED,SAAa,IAAA,SAAA,CAAU,MAAW,KAAA,CAAA,oBAChC,KAAA,CAAA,aAAA,CAAA,UAAA,EAAA,EAAW,SAAQ,WAAY,EAAA,EAAA,uBAAqB,CAEvD,kBAAA,KAAA,CAAA,aAAA,CAAC,QAAK,KAAK,EAAA,IAAA,EAAA,EACR,SAAW,IAAA,IAAA,GAAA,KAAA,CAAA,GAAA,SAAA,CAAA,GAAA,CAAI,CAAC,QAAa,KAAA;AAC5B,IACE,uBAAA,KAAA,CAAA,aAAA;AAAA,MAAC,gBAAA;AAAA,MAAA;AAAA,QACC,KAAK,QAAS,CAAA,EAAA;AAAA,QACd,QAAA;AAAA,QACA,OAAA;AAAA,OAAA;AAAA,KACF,CAAA;AAAA,GAEJ,CACF,mBACC,KAAA,CAAA,aAAA,CAAA,UAAA,EAAA,EAAW,SAAQ,WAAY,EAAA,EAAA,UAAA,EACrB,GACT,kBAAA,KAAA,CAAA,aAAA,CAAC,IAAK,EAAA,EAAA,MAAA,EAAO,UAAS,IAAM,EAAA,CAAA,EAAG,OAAO,CAAc,WAAA,EAAA,KAAA,CAAM,UAAU,CAAA,CAAA,EAAA,EAAI,WAExE,CACF,CACF,CAAA,CAAA;AAEJ,CAAA,CAAA;AAEO,MAAM,UAAU,MAAM;AAC3B,EAAA,2CAAQ,2BAA4B,EAAA,IAAA,CAAA,CAAA;AACtC;;;;"}
package/src/config.ts DELETED
@@ -1,30 +0,0 @@
1
- /*
2
- * Copyright 2023 The Backstage Authors
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- */
16
- import { ConfigApi } from '@backstage/core-plugin-api';
17
-
18
- // Find the baseUrl of the incident dashboard.
19
- export function getBaseUrl(config: ConfigApi) {
20
- try {
21
- const baseUrl = config.getString('incident.baseUrl');
22
- if (baseUrl !== '') {
23
- return baseUrl;
24
- }
25
- } catch (e) {
26
- // no action
27
- }
28
-
29
- return 'https://app.incident.io';
30
- }