@incident-io/backstage 0.0.4 → 0.0.5

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.
@@ -51,7 +51,7 @@ const EntityIncidentCard = incidentPlugin.provide(
51
51
  createComponentExtension({
52
52
  name: "EntityIncidentCard",
53
53
  component: {
54
- lazy: () => import('./index-0b104295.esm.js').then(
54
+ lazy: () => import('./index-d6d4efc6.esm.js').then(
55
55
  (m) => m.EntityIncidentCard
56
56
  )
57
57
  }
@@ -59,4 +59,4 @@ const EntityIncidentCard = incidentPlugin.provide(
59
59
  );
60
60
 
61
61
  export { EntityIncidentCard as E, IncidentApiRef as I, incidentPlugin as i };
62
- //# sourceMappingURL=index-44e2d69d.esm.js.map
62
+ //# sourceMappingURL=index-9302f6bc.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index-44e2d69d.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';\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"],"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;AApD7B,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,IAAO,GAAA,MAAM,KAAM,CAAA,CAAA,EAAG,SAAS,IAAQ,CAAA,CAAA,EAAA;AAAA,MAC3C,MAAA;AAAA,MACA,IAAA;AAAA,MACA,OAAS,EAAA;AAAA,QACP,eAAe,CAAU,OAAA,EAAA,KAAA,CAAA,CAAA;AAAA,OAC3B;AAAA,KACD,CAAA,CAAA;AACD,IAAI,IAAA,CAAC,KAAK,EAAI,EAAA;AACZ,MAAA,MAAM,IAAI,KAAM,CAAA,CAAA,EAAG,IAAK,CAAA,MAAA,CAAA,CAAA,EAAU,KAAK,UAAY,CAAA,CAAA,CAAA,CAAA;AAAA,KACrD;AAEA,IAAO,OAAA,MAAM,KAAK,IAAK,EAAA,CAAA;AAAA,GACzB;AACF;;AC3DO,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,OAAK,CAAE,CAAA,kBAAA;AAAA,OACT;AAAA,KACJ;AAAA,GACD,CAAA;AACH;;;;"}
1
+ {"version":3,"file":"index-9302f6bc.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';\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"],"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;AApD7B,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,IAAO,GAAA,MAAM,KAAM,CAAA,CAAA,EAAG,SAAS,IAAQ,CAAA,CAAA,EAAA;AAAA,MAC3C,MAAA;AAAA,MACA,IAAA;AAAA,MACA,OAAS,EAAA;AAAA,QACP,eAAe,CAAU,OAAA,EAAA,KAAA,CAAA,CAAA;AAAA,OAC3B;AAAA,KACD,CAAA,CAAA;AACD,IAAI,IAAA,CAAC,KAAK,EAAI,EAAA;AACZ,MAAA,MAAM,IAAI,KAAM,CAAA,CAAA,EAAG,IAAK,CAAA,MAAA,CAAA,CAAA,EAAU,KAAK,UAAY,CAAA,CAAA,CAAA,CAAA;AAAA,KACrD;AAEA,IAAO,OAAA,MAAM,KAAK,IAAK,EAAA,CAAA;AAAA,GACzB;AACF;;AC3DO,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,OAAK,CAAE,CAAA,kBAAA;AAAA,OACT;AAAA,KACJ;AAAA,GACD,CAAA;AACH;;;;"}
@@ -9,7 +9,7 @@ import WhatshotIcon from '@material-ui/icons/Whatshot';
9
9
  import { Alert } from '@material-ui/lab';
10
10
  import React, { useState } from 'react';
11
11
  import { useAsync } from 'react-use';
12
- import { I as IncidentApiRef } from './index-44e2d69d.esm.js';
12
+ import { I as IncidentApiRef } from './index-9302f6bc.esm.js';
13
13
  import { DateTime, Duration } from 'luxon';
14
14
  import OpenInBrowserIcon from '@material-ui/icons/OpenInBrowser';
15
15
 
@@ -101,6 +101,9 @@ const EntityIncidentCard = ({
101
101
  const IncidentApi = useApi(IncidentApiRef);
102
102
  const [reload, setReload] = useState(false);
103
103
  const entityFieldID = getEntityFieldID(config, entity);
104
+ if (!entityFieldID) {
105
+ return /* @__PURE__ */ React.createElement(IncorrectConfigCard, null);
106
+ }
104
107
  const entityID = `${entity.metadata.namespace}/${entity.metadata.name}`;
105
108
  const query = new URLSearchParams();
106
109
  query.set(`custom_field[${entityFieldID}][one_of]`, entityID);
@@ -163,20 +166,23 @@ const EntityIncidentCard = ({
163
166
  "see more."
164
167
  )))));
165
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
+ };
166
172
  function getEntityFieldID(config, entity) {
167
173
  switch (entity.kind) {
168
174
  case "API":
169
- return config.get("incident.fields.api");
175
+ return config.getOptional("incident.fields.api");
170
176
  case "Component":
171
- return config.get("incident.fields.component");
177
+ return config.getOptional("incident.fields.component");
172
178
  case "Domain":
173
- return config.get("incident.fields.domain");
179
+ return config.getOptional("incident.fields.domain");
174
180
  case "System":
175
- return config.get("incident.fields.system");
181
+ return config.getOptional("incident.fields.system");
176
182
  default:
177
183
  throw new Error(`unrecognised entity kind: ${entity.kind}`);
178
184
  }
179
185
  }
180
186
 
181
187
  export { EntityIncidentCard };
182
- //# sourceMappingURL=index-0b104295.esm.js.map
188
+ //# sourceMappingURL=index-d6d4efc6.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-d6d4efc6.esm.js","sources":["../../src/config.ts","../../src/components/IncidentListItem/index.tsx","../../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 { 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","/*\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","/*\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 { useAsync } from \"react-use\";\nimport { IncidentApiRef } from \"../../api/client\";\nimport { definitions } from \"../../api/types\";\nimport { getBaseUrl } from \"../../config\";\nimport { IncidentListItem } from \"../IncidentListItem\";\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 IncidentApi = useApi(IncidentApiRef);\n\n const [reload, setReload] = useState(false);\n\n const entityFieldID = getEntityFieldID(config, entity);\n\n if (!entityFieldID) {\n return <IncorrectConfigCard />;\n }\n\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 } = useAsync(async () => {\n return await IncidentApi.request<\n definitions[\"IncidentsV2ListResponseBody\"]\n >({\n path: `/v2/incidents?${queryLive.toString()}`,\n });\n }, [reload]);\n\n const incidents = incidentsResponse?.incidents;\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\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// 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":"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"}
package/dist/index.esm.js CHANGED
@@ -1,3 +1,3 @@
1
- export { E as EntityIncidentCard, i as incidentPlugin } from './esm/index-44e2d69d.esm.js';
1
+ export { E as EntityIncidentCard, i as incidentPlugin } from './esm/index-9302f6bc.esm.js';
2
2
  import '@backstage/core-plugin-api';
3
3
  //# sourceMappingURL=index.esm.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@incident-io/backstage",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "main": "dist/index.esm.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "license": "Apache-2.0",
@@ -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,18 +29,18 @@ 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 { useAsync } from 'react-use';
40
- import { IncidentApiRef } from '../../api/client';
41
- import { definitions } from '../../api/types';
42
- import { getBaseUrl } from '../../config';
43
- 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 { useAsync } from "react-use";
40
+ import { IncidentApiRef } from "../../api/client";
41
+ import { definitions } from "../../api/types";
42
+ import { getBaseUrl } from "../../config";
43
+ import { IncidentListItem } from "../IncidentListItem";
44
44
 
45
45
  // The card displayed on the entity page showing a handful of the most recent
46
46
  // incidents that are on-going for that component.
@@ -58,6 +58,11 @@ export const EntityIncidentCard = ({
58
58
  const [reload, setReload] = useState(false);
59
59
 
60
60
  const entityFieldID = getEntityFieldID(config, entity);
61
+
62
+ if (!entityFieldID) {
63
+ return <IncorrectConfigCard />;
64
+ }
65
+
61
66
  const entityID = `${entity.metadata.namespace}/${entity.metadata.name}`;
62
67
 
63
68
  // This query filters incidents for those that are associated with this
@@ -67,17 +72,17 @@ export const EntityIncidentCard = ({
67
72
 
68
73
  // This restricts the previous filter to focus only on live incidents.
69
74
  const queryLive = new URLSearchParams(query);
70
- queryLive.set(`status_category[one_of]`, 'live');
75
+ queryLive.set(`status_category[one_of]`, "live");
71
76
 
72
77
  const createIncidentLink: IconLinkVerticalProps = {
73
- label: 'Create incident',
78
+ label: "Create incident",
74
79
  disabled: false,
75
80
  icon: <WhatshotIcon />,
76
81
  href: `${baseUrl}/incidents/create`,
77
82
  };
78
83
 
79
84
  const viewIncidentsLink: IconLinkVerticalProps = {
80
- label: 'View past incidents',
85
+ label: "View past incidents",
81
86
  disabled: false,
82
87
  icon: <HistoryIcon />,
83
88
  href: `${baseUrl}/incidents?${query.toString()}`,
@@ -89,7 +94,7 @@ export const EntityIncidentCard = ({
89
94
  error: incidentsError,
90
95
  } = useAsync(async () => {
91
96
  return await IncidentApi.request<
92
- definitions['IncidentsV2ListResponseBody']
97
+ definitions["IncidentsV2ListResponseBody"]
93
98
  >({
94
99
  path: `/v2/incidents?${queryLive.toString()}`,
95
100
  });
@@ -136,7 +141,7 @@ export const EntityIncidentCard = ({
136
141
  <Typography variant="subtitle1">No ongoing incidents.</Typography>
137
142
  )}
138
143
  <List dense>
139
- {incidents?.slice(0, maxIncidents)?.map(incident => {
144
+ {incidents?.slice(0, maxIncidents)?.map((incident) => {
140
145
  return (
141
146
  <IncidentListItem
142
147
  key={incident.id}
@@ -147,7 +152,7 @@ export const EntityIncidentCard = ({
147
152
  })}
148
153
  </List>
149
154
  <Typography variant="subtitle1">
150
- Click to{' '}
155
+ Click to{" "}
151
156
  <Link
152
157
  target="_blank"
153
158
  href={`${baseUrl}/incidents?${queryLive.toString()}`}
@@ -162,20 +167,36 @@ export const EntityIncidentCard = ({
162
167
  );
163
168
  };
164
169
 
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
+
165
186
  // Find the ID of the custom field in incident that represents the association
166
187
  // to this type of entity.
167
188
  //
168
189
  // In practice, this will be kind=Component => ID of Affected components field.
169
190
  function getEntityFieldID(config: ConfigApi, entity: Entity) {
170
191
  switch (entity.kind) {
171
- case 'API':
172
- return config.get('incident.fields.api');
173
- case 'Component':
174
- return config.get('incident.fields.component');
175
- case 'Domain':
176
- return config.get('incident.fields.domain');
177
- case 'System':
178
- return config.get('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");
179
200
  default:
180
201
  throw new Error(`unrecognised entity kind: ${entity.kind}`);
181
202
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"index-0b104295.esm.js","sources":["../../src/config.ts","../../src/components/IncidentListItem/index.tsx","../../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 { 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","/*\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","/*\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 { useAsync } from 'react-use';\nimport { IncidentApiRef } from '../../api/client';\nimport { definitions } from '../../api/types';\nimport { getBaseUrl } from '../../config';\nimport { IncidentListItem } from '../IncidentListItem';\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 IncidentApi = useApi(IncidentApiRef);\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 } = useAsync(async () => {\n return await IncidentApi.request<\n definitions['IncidentsV2ListResponseBody']\n >({\n path: `/v2/incidents?${queryLive.toString()}`,\n });\n }, [reload]);\n\n const incidents = incidentsResponse?.incidents;\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.get('incident.fields.api');\n case 'Component':\n return config.get('incident.fields.component');\n case 'Domain':\n return config.get('incident.fields.domain');\n case 'System':\n return config.get('incident.fields.system');\n default:\n throw new Error(`unrecognised entity kind: ${entity.kind}`);\n }\n}\n"],"names":[],"mappings":"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"}