@pagerduty/backstage-plugin 0.9.4-next.3 → 0.9.4-next.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.
@@ -3,9 +3,7 @@ import { NotFoundError } from '@backstage/errors';
3
3
  import { createCardExtension } from '@backstage/plugin-home-react';
4
4
  import React, { useEffect, useState, useCallback } from 'react';
5
5
  import { useEntity } from '@backstage/plugin-catalog-react';
6
- import { makeStyles, ListItem, ListItemText, Chip, Typography, ListItemSecondaryAction, Tooltip, IconButton, Grid, List, ListItemIcon, ListSubheader, Button, Dialog, DialogTitle, DialogContent, TextField, DialogActions, CircularProgress, Card, CardHeader, Divider, CardContent } from '@material-ui/core';
7
- import Done from '@material-ui/icons/Done';
8
- import Warning from '@material-ui/icons/Warning';
6
+ import { makeStyles, ListItem, ListItemText, Chip, Typography, ListItemSecondaryAction, Tooltip, IconButton, Grid, List, ListItemIcon, ListSubheader, Button, Dialog, DialogTitle, DialogContent, TextField, DialogActions, CircularProgress, Card, withStyles, LinearProgress, CardHeader, Divider, CardContent } from '@material-ui/core';
9
7
  import { DateTime, Duration } from 'luxon';
10
8
  import OpenInBrowserIcon from '@material-ui/icons/OpenInBrowser';
11
9
  import { Link, Progress, StatusWarning, StatusError, EmptyState, TabbedCard, CardTab, InfoCard } from '@backstage/core-components';
@@ -17,12 +15,13 @@ import Avatar from '@material-ui/core/Avatar';
17
15
  import NotificationsIcon from '@material-ui/icons/Notifications';
18
16
  import MailOutline from '@material-ui/icons/MailOutline';
19
17
  import useAsync from 'react-use/lib/useAsync';
20
- import { parseEntityRef, DEFAULT_NAMESPACE } from '@backstage/catalog-model';
18
+ import LinkIcon from '@material-ui/icons/Link';
21
19
  import PDGreenImage from '../assets/PD-Green.svg';
22
20
  import PDWhiteImage from '../assets/PD-White.svg';
21
+ import { parseEntityRef, DEFAULT_NAMESPACE } from '@backstage/catalog-model';
23
22
  import AddAlert from '@material-ui/icons/AddAlert';
24
- import { useTheme } from '@material-ui/core/styles';
25
- import LinearProgress from '@material-ui/core/LinearProgress';
23
+ import { makeStyles as makeStyles$1, createStyles, useTheme } from '@material-ui/core/styles';
24
+ import validateColor from 'validate-color';
26
25
 
27
26
  const PAGERDUTY_INTEGRATION_KEY = "pagerduty.com/integration-key";
28
27
  const PAGERDUTY_SERVICE_ID = "pagerduty.com/service-id";
@@ -94,6 +93,12 @@ class PagerDutyClient {
94
93
  )}/services/${serviceId}/change-events`;
95
94
  return await this.findByUrl(url);
96
95
  }
96
+ async getServiceStandardsByServiceId(serviceId) {
97
+ const url = `${await this.config.discoveryApi.getBaseUrl(
98
+ "pagerduty"
99
+ )}/services/${serviceId}/standards`;
100
+ return await this.findByUrl(url);
101
+ }
97
102
  async getOnCallByPolicyId(policyId) {
98
103
  const params = `escalation_policy_ids[]=${policyId}`;
99
104
  const url = `${await this.config.discoveryApi.getBaseUrl(
@@ -194,7 +199,7 @@ const HomePagePagerDutyCard = pagerDutyPlugin.provide(
194
199
  createCardExtension({
195
200
  name: "HomePagePagerDutyCard",
196
201
  title: "PagerDuty Homepage Card",
197
- components: () => import('./index-9209930a.esm.js'),
202
+ components: () => import('./index-02ffa0de.esm.js'),
198
203
  settings: {
199
204
  schema: {
200
205
  title: "PagerDuty",
@@ -218,7 +223,7 @@ const HomePagePagerDutyCard = pagerDutyPlugin.provide(
218
223
  })
219
224
  );
220
225
 
221
- const useStyles$7 = makeStyles((theme) => ({
226
+ const useStyles$8 = makeStyles((theme) => ({
222
227
  denseListIcon: {
223
228
  marginRight: 0,
224
229
  display: "flex",
@@ -230,37 +235,51 @@ const useStyles$7 = makeStyles((theme) => ({
230
235
  fontWeight: "bold"
231
236
  },
232
237
  warning: {
233
- borderColor: theme.palette.status.warning,
234
- color: theme.palette.status.warning,
238
+ borderColor: theme.palette.warning.main,
239
+ color: "#fff",
240
+ backgroundColor: theme.palette.warning.main,
241
+ boxShadow: "0 8px 16px 0 rgba(0,0,0,0.2)",
242
+ textTransform: "uppercase",
235
243
  "& *": {
236
- color: theme.palette.status.warning
244
+ color: "#fff"
237
245
  }
238
246
  },
239
247
  error: {
240
- borderColor: theme.palette.status.error,
241
- color: theme.palette.status.error,
248
+ marginTop: "2px",
249
+ borderColor: theme.palette.error.main,
250
+ color: "#fff",
251
+ backgroundColor: theme.palette.error.main,
252
+ boxShadow: "0 8px 16px 0 rgba(0,0,0,0.2)",
253
+ textTransform: "uppercase",
242
254
  "& *": {
243
- color: theme.palette.status.error
255
+ color: "#fff"
244
256
  }
257
+ },
258
+ textContainer: {
259
+ display: "flex",
260
+ alignItems: "baseline"
261
+ },
262
+ smallIconStyle: {
263
+ color: theme.palette.text.primary,
264
+ marginRight: "-20px"
245
265
  }
246
266
  }));
247
267
  const IncidentListItem = ({ incident }) => {
248
268
  var _a, _b, _c;
249
- const classes = useStyles$7();
269
+ const classes = useStyles$8();
250
270
  const duration = (/* @__PURE__ */ new Date()).getTime() - new Date(incident.created_at).getTime();
251
271
  const createdAt = DateTime.local().minus(Duration.fromMillis(duration)).toRelative({ locale: "en" });
252
272
  const user = (_a = incident.assignments[0]) == null ? void 0 : _a.assignee;
253
273
  return /* @__PURE__ */ React.createElement(ListItem, { dense: true, key: incident.id }, /* @__PURE__ */ React.createElement(
254
274
  ListItemText,
255
275
  {
256
- primary: /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
276
+ primary: /* @__PURE__ */ React.createElement("div", { className: classes.textContainer }, /* @__PURE__ */ React.createElement(
257
277
  Chip,
258
278
  {
259
279
  "data-testid": `chip-${incident.status}`,
260
280
  label: incident.status,
261
281
  size: "small",
262
282
  variant: "outlined",
263
- icon: incident.status === "acknowledged" ? /* @__PURE__ */ React.createElement(Done, null) : /* @__PURE__ */ React.createElement(Warning, null),
264
283
  className: incident.status === "triggered" ? classes.error : classes.warning
265
284
  }
266
285
  ), incident.title),
@@ -276,7 +295,7 @@ const IncidentListItem = ({ incident }) => {
276
295
  href: incident.html_url,
277
296
  target: "_blank",
278
297
  rel: "noopener noreferrer",
279
- color: "primary"
298
+ className: classes.smallIconStyle
280
299
  },
281
300
  /* @__PURE__ */ React.createElement(OpenInBrowserIcon, null)
282
301
  ))));
@@ -342,7 +361,7 @@ const Incidents = ({ serviceId, refreshIncidents }) => {
342
361
  return /* @__PURE__ */ React.createElement(List, { dense: true }, incidents.map((incident, index) => /* @__PURE__ */ React.createElement(IncidentListItem, { key: incident.id + index, incident })));
343
362
  };
344
363
 
345
- const useStyles$6 = makeStyles({
364
+ const useStyles$7 = makeStyles({
346
365
  denseListIcon: {
347
366
  marginRight: 0,
348
367
  display: "flex",
@@ -352,11 +371,11 @@ const useStyles$6 = makeStyles({
352
371
  }
353
372
  });
354
373
  const EscalationUsersEmptyState = () => {
355
- const classes = useStyles$6();
374
+ const classes = useStyles$7();
356
375
  return /* @__PURE__ */ React.createElement(ListItem, null, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement("div", { className: classes.denseListIcon }, /* @__PURE__ */ React.createElement(StatusWarning, null))), /* @__PURE__ */ React.createElement(ListItemText, { primary: "No one is on-call. Update the escalation policy." }));
357
376
  };
358
377
 
359
- const useStyles$5 = makeStyles({
378
+ const useStyles$6 = makeStyles({
360
379
  denseListIcon: {
361
380
  marginRight: 0,
362
381
  display: "flex",
@@ -366,11 +385,11 @@ const useStyles$5 = makeStyles({
366
385
  }
367
386
  });
368
387
  const EscalationUsersForbiddenState = () => {
369
- const classes = useStyles$5();
388
+ const classes = useStyles$6();
370
389
  return /* @__PURE__ */ React.createElement(ListItem, null, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement("div", { className: classes.denseListIcon }, /* @__PURE__ */ React.createElement(StatusError, null))), /* @__PURE__ */ React.createElement(ListItemText, { primary: "You don't permissions to list on-calls. Check your OAuth token permissions." }));
371
390
  };
372
391
 
373
- const useStyles$4 = makeStyles((theme) => ({
392
+ const useStyles$5 = makeStyles((theme) => ({
374
393
  listItemPrimary: {
375
394
  fontWeight: "bold"
376
395
  },
@@ -401,11 +420,17 @@ const useStyles$4 = makeStyles((theme) => ({
401
420
  },
402
421
  smallIconStyle: {
403
422
  color: theme.palette.text.primary
423
+ },
424
+ avatarStyle: {
425
+ marginTop: "-20px"
404
426
  }
405
427
  }));
406
- const EscalationUser = ({ user }) => {
407
- const classes = useStyles$4();
408
- return /* @__PURE__ */ React.createElement(ListItem, null, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(Avatar, { alt: user.name, src: user.avatar_url })), /* @__PURE__ */ React.createElement(
428
+ function navigateToEscalationPolicy(url) {
429
+ window.open(url, "_blank");
430
+ }
431
+ const EscalationUser = ({ user, policyUrl, policyName }) => {
432
+ const classes = useStyles$5();
433
+ return /* @__PURE__ */ React.createElement(ListItem, null, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(Avatar, { alt: user.name, src: user.avatar_url, className: classes.avatarStyle })), /* @__PURE__ */ React.createElement(
409
434
  ListItemText,
410
435
  {
411
436
  primary: /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Typography, { className: classes.listItemPrimary }, user.name), /* @__PURE__ */ React.createElement(
@@ -420,9 +445,10 @@ const EscalationUser = ({ user }) => {
420
445
  IconButton,
421
446
  {
422
447
  "aria-label": "open-service-in-browser",
448
+ onClick: () => navigateToEscalationPolicy(policyUrl),
423
449
  className: classes.buttonStyle
424
450
  },
425
- /* @__PURE__ */ React.createElement("span", { className: classes.containerStyle }, /* @__PURE__ */ React.createElement(NotificationsIcon, { className: classes.iconStyle }), "Web Team")
451
+ /* @__PURE__ */ React.createElement("span", { className: classes.containerStyle }, /* @__PURE__ */ React.createElement(NotificationsIcon, { className: classes.iconStyle }), policyName)
426
452
  )
427
453
  }
428
454
  ), /* @__PURE__ */ React.createElement(ListItemSecondaryAction, null, /* @__PURE__ */ React.createElement(Tooltip, { title: "Send e-mail to user", placement: "top" }, /* @__PURE__ */ React.createElement(IconButton, { href: `mailto:${user.email}` }, /* @__PURE__ */ React.createElement(MailOutline, { className: classes.smallIconStyle }))), /* @__PURE__ */ React.createElement(Tooltip, { title: "View in PagerDuty", placement: "top" }, /* @__PURE__ */ React.createElement(
@@ -437,7 +463,7 @@ const EscalationUser = ({ user }) => {
437
463
  ))));
438
464
  };
439
465
 
440
- const EscalationPolicy = ({ policyId }) => {
466
+ const EscalationPolicy = ({ policyId, policyUrl, policyName }) => {
441
467
  const api = useApi(pagerDutyApiRef);
442
468
  const {
443
469
  value: users,
@@ -458,7 +484,7 @@ const EscalationPolicy = ({ policyId }) => {
458
484
  if (!(users == null ? void 0 : users.length)) {
459
485
  return /* @__PURE__ */ React.createElement(EscalationUsersEmptyState, null);
460
486
  }
461
- return /* @__PURE__ */ React.createElement(List, { dense: true, subheader: /* @__PURE__ */ React.createElement(ListSubheader, null, "ON CALL"), style: { marginLeft: "-15px" } }, users.map((user, index) => /* @__PURE__ */ React.createElement(EscalationUser, { key: index, user })));
487
+ return /* @__PURE__ */ React.createElement(List, { dense: true, subheader: /* @__PURE__ */ React.createElement(ListSubheader, null, "ON CALL"), style: { marginLeft: "-15px" } }, users.map((user, index) => /* @__PURE__ */ React.createElement(EscalationUser, { key: index, user, policyUrl, policyName })));
462
488
  };
463
489
 
464
490
  const MissingTokenError = () => /* @__PURE__ */ React.createElement(
@@ -497,91 +523,7 @@ const ServiceNotFoundError = () => /* @__PURE__ */ React.createElement(
497
523
  }
498
524
  );
499
525
 
500
- const TriggerDialog = ({
501
- showDialog,
502
- handleDialog,
503
- onIncidentCreated,
504
- name,
505
- integrationKey
506
- }) => {
507
- const alertApi = useApi(alertApiRef);
508
- const identityApi = useApi(identityApiRef);
509
- const api = useApi(pagerDutyApiRef);
510
- const [description, setDescription] = useState("");
511
- const [{ value, loading, error }, handleTriggerAlarm] = useAsyncFn(
512
- async (descriptions) => {
513
- const { userEntityRef } = await identityApi.getBackstageIdentity();
514
- const { name: userName } = parseEntityRef(userEntityRef, {
515
- defaultKind: "User",
516
- defaultNamespace: DEFAULT_NAMESPACE
517
- });
518
- return await api.triggerAlarm({
519
- integrationKey,
520
- source: window.location.toString(),
521
- description: descriptions,
522
- userName
523
- });
524
- }
525
- );
526
- const descriptionChanged = (event) => {
527
- setDescription(event.target.value);
528
- };
529
- useEffect(() => {
530
- if (value) {
531
- (async () => {
532
- alertApi.post({
533
- message: `Alarm successfully triggered`
534
- });
535
- handleDialog();
536
- await new Promise((resolve) => setTimeout(resolve, 1e3));
537
- onIncidentCreated == null ? void 0 : onIncidentCreated();
538
- })();
539
- }
540
- }, [value, alertApi, handleDialog, onIncidentCreated]);
541
- if (error) {
542
- alertApi.post({
543
- message: `Failed to trigger alarm. ${error.message}`,
544
- severity: "error"
545
- });
546
- }
547
- return /* @__PURE__ */ React.createElement(Dialog, { maxWidth: "md", open: showDialog, onClose: handleDialog, fullWidth: true }, /* @__PURE__ */ React.createElement(DialogTitle, null, "This action will trigger an incident for ", /* @__PURE__ */ React.createElement("strong", null, '"', name, '"'), "."), /* @__PURE__ */ React.createElement(DialogContent, null, /* @__PURE__ */ React.createElement(Alert, { severity: "info" }, /* @__PURE__ */ React.createElement(Typography, { variant: "body1", align: "justify" }, `If the issue you are seeing does not need urgent attention, please get in touch with the responsible team using their preferred communications channel. You can find information about the owner of this entity in the "About" card. If the issue is urgent, please don't hesitate to trigger the alert.`)), /* @__PURE__ */ React.createElement(
548
- Typography,
549
- {
550
- variant: "body1",
551
- style: { marginTop: "1em" },
552
- gutterBottom: true,
553
- align: "justify"
554
- },
555
- "Please describe the problem you want to report. Be as descriptive as possible. Your signed in user and a reference to the current page will automatically be amended to the alarm so that the receiver can reach out to you if necessary."
556
- ), /* @__PURE__ */ React.createElement(
557
- TextField,
558
- {
559
- inputProps: { "data-testid": "trigger-input" },
560
- id: "description",
561
- multiline: true,
562
- fullWidth: true,
563
- rows: "4",
564
- margin: "normal",
565
- label: "Problem description",
566
- variant: "outlined",
567
- onChange: descriptionChanged
568
- }
569
- )), /* @__PURE__ */ React.createElement(DialogActions, null, /* @__PURE__ */ React.createElement(
570
- Button,
571
- {
572
- "data-testid": "trigger-button",
573
- id: "trigger",
574
- color: "secondary",
575
- disabled: !description || loading,
576
- variant: "contained",
577
- onClick: () => handleTriggerAlarm(description),
578
- endIcon: loading && /* @__PURE__ */ React.createElement(CircularProgress, { size: 16 })
579
- },
580
- "Trigger Incident"
581
- ), /* @__PURE__ */ React.createElement(Button, { id: "close", color: "primary", onClick: handleDialog }, "Close")));
582
- };
583
-
584
- const useStyles$3 = makeStyles({
526
+ const useStyles$4 = makeStyles((theme) => ({
585
527
  denseListIcon: {
586
528
  marginRight: 0,
587
529
  display: "flex",
@@ -591,10 +533,17 @@ const useStyles$3 = makeStyles({
591
533
  },
592
534
  listItemPrimary: {
593
535
  fontWeight: "bold"
536
+ },
537
+ smallExternalLinkIconStyle: {
538
+ color: theme.palette.text.primary
539
+ },
540
+ smallIconStyle: {
541
+ color: theme.palette.text.primary,
542
+ marginRight: "-20px"
594
543
  }
595
- });
544
+ }));
596
545
  const ChangeEventListItem = ({ changeEvent }) => {
597
- const classes = useStyles$3();
546
+ const classes = useStyles$4();
598
547
  const duration = (/* @__PURE__ */ new Date()).getTime() - new Date(changeEvent.timestamp).getTime();
599
548
  const changedAt = DateTime.local().minus(Duration.fromMillis(duration)).toRelative({ locale: "en" });
600
549
  let externalLinkElem;
@@ -605,9 +554,9 @@ const ChangeEventListItem = ({ changeEvent }) => {
605
554
  {
606
555
  component: Link,
607
556
  to: changeEvent.links[0].href,
608
- color: "primary"
557
+ className: classes.smallExternalLinkIconStyle
609
558
  },
610
- /* @__PURE__ */ React.createElement(OpenInBrowserIcon, null)
559
+ /* @__PURE__ */ React.createElement(LinkIcon, null)
611
560
  ));
612
561
  }
613
562
  return /* @__PURE__ */ React.createElement(ListItem, { dense: true, key: changeEvent.id }, /* @__PURE__ */ React.createElement(
@@ -625,7 +574,7 @@ const ChangeEventListItem = ({ changeEvent }) => {
625
574
  {
626
575
  component: Link,
627
576
  to: changeEvent.html_url,
628
- color: "primary"
577
+ className: classes.smallIconStyle
629
578
  },
630
579
  /* @__PURE__ */ React.createElement(OpenInBrowserIcon, null)
631
580
  ))));
@@ -708,7 +657,91 @@ function usePagerdutyEntity() {
708
657
  return getPagerDutyEntity(entity);
709
658
  }
710
659
 
711
- const useStyles$2 = makeStyles((theme) => ({
660
+ const TriggerDialog = ({
661
+ showDialog,
662
+ handleDialog,
663
+ onIncidentCreated,
664
+ name,
665
+ integrationKey
666
+ }) => {
667
+ const alertApi = useApi(alertApiRef);
668
+ const identityApi = useApi(identityApiRef);
669
+ const api = useApi(pagerDutyApiRef);
670
+ const [description, setDescription] = useState("");
671
+ const [{ value, loading, error }, handleTriggerAlarm] = useAsyncFn(
672
+ async (descriptions) => {
673
+ const { userEntityRef } = await identityApi.getBackstageIdentity();
674
+ const { name: userName } = parseEntityRef(userEntityRef, {
675
+ defaultKind: "User",
676
+ defaultNamespace: DEFAULT_NAMESPACE
677
+ });
678
+ return await api.triggerAlarm({
679
+ integrationKey,
680
+ source: window.location.toString(),
681
+ description: descriptions,
682
+ userName
683
+ });
684
+ }
685
+ );
686
+ const descriptionChanged = (event) => {
687
+ setDescription(event.target.value);
688
+ };
689
+ useEffect(() => {
690
+ if (value) {
691
+ (async () => {
692
+ alertApi.post({
693
+ message: `Alarm successfully triggered`
694
+ });
695
+ handleDialog();
696
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
697
+ onIncidentCreated == null ? void 0 : onIncidentCreated();
698
+ })();
699
+ }
700
+ }, [value, alertApi, handleDialog, onIncidentCreated]);
701
+ if (error) {
702
+ alertApi.post({
703
+ message: `Failed to trigger alarm. ${error.message}`,
704
+ severity: "error"
705
+ });
706
+ }
707
+ return /* @__PURE__ */ React.createElement(Dialog, { maxWidth: "md", open: showDialog, onClose: handleDialog, fullWidth: true }, /* @__PURE__ */ React.createElement(DialogTitle, null, "This action will trigger an incident for ", /* @__PURE__ */ React.createElement("strong", null, '"', name, '"'), "."), /* @__PURE__ */ React.createElement(DialogContent, null, /* @__PURE__ */ React.createElement(Alert, { severity: "info" }, /* @__PURE__ */ React.createElement(Typography, { variant: "body1", align: "justify" }, `If the issue you are seeing does not need urgent attention, please get in touch with the responsible team using their preferred communications channel. You can find information about the owner of this entity in the "About" card. If the issue is urgent, please don't hesitate to trigger the alert.`)), /* @__PURE__ */ React.createElement(
708
+ Typography,
709
+ {
710
+ variant: "body1",
711
+ style: { marginTop: "1em" },
712
+ gutterBottom: true,
713
+ align: "justify"
714
+ },
715
+ "Please describe the problem you want to report. Be as descriptive as possible. Your signed in user and a reference to the current page will automatically be amended to the alarm so that the receiver can reach out to you if necessary."
716
+ ), /* @__PURE__ */ React.createElement(
717
+ TextField,
718
+ {
719
+ inputProps: { "data-testid": "trigger-input" },
720
+ id: "description",
721
+ multiline: true,
722
+ fullWidth: true,
723
+ rows: "4",
724
+ margin: "normal",
725
+ label: "Problem description",
726
+ variant: "outlined",
727
+ onChange: descriptionChanged
728
+ }
729
+ )), /* @__PURE__ */ React.createElement(DialogActions, null, /* @__PURE__ */ React.createElement(
730
+ Button,
731
+ {
732
+ "data-testid": "trigger-button",
733
+ id: "trigger",
734
+ color: "secondary",
735
+ disabled: !description || loading,
736
+ variant: "contained",
737
+ onClick: () => handleTriggerAlarm(description),
738
+ endIcon: loading && /* @__PURE__ */ React.createElement(CircularProgress, { size: 16 })
739
+ },
740
+ "Trigger Incident"
741
+ ), /* @__PURE__ */ React.createElement(Button, { id: "close", color: "primary", onClick: handleDialog }, "Close")));
742
+ };
743
+
744
+ const useStyles$3 = makeStyles((theme) => ({
712
745
  buttonStyle: {
713
746
  color: theme.palette.text.primary,
714
747
  "&:hover": {
@@ -728,7 +761,7 @@ const useStyles$2 = makeStyles((theme) => ({
728
761
  }
729
762
  }));
730
763
  function TriggerIncidentButton() {
731
- const { buttonStyle, containerStyle, iconStyle } = useStyles$2();
764
+ const { buttonStyle, containerStyle, iconStyle } = useStyles$3();
732
765
  const { integrationKey, name } = usePagerdutyEntity();
733
766
  const [dialogShown, setDialogShown] = useState(false);
734
767
  const showDialog = useCallback(() => {
@@ -746,7 +779,7 @@ function TriggerIncidentButton() {
746
779
  className: disabled ? "" : buttonStyle,
747
780
  disabled
748
781
  },
749
- /* @__PURE__ */ React.createElement("div", { className: containerStyle }, /* @__PURE__ */ React.createElement(AddAlert, { className: iconStyle }), /* @__PURE__ */ React.createElement("p", null, "Create New Incident"))
782
+ /* @__PURE__ */ React.createElement("div", { className: containerStyle }, /* @__PURE__ */ React.createElement(AddAlert, { className: iconStyle }), /* @__PURE__ */ React.createElement("p", null, "Create new incident"))
750
783
  ), integrationKey && /* @__PURE__ */ React.createElement(
751
784
  TriggerDialog,
752
785
  {
@@ -758,7 +791,7 @@ function TriggerIncidentButton() {
758
791
  ));
759
792
  }
760
793
 
761
- const useStyles$1 = makeStyles((theme) => ({
794
+ const useStyles$2 = makeStyles((theme) => ({
762
795
  buttonStyle: {
763
796
  color: theme.palette.text.primary,
764
797
  "&:hover": {
@@ -778,7 +811,7 @@ const useStyles$1 = makeStyles((theme) => ({
778
811
  }
779
812
  }));
780
813
  function OpenServiceButton(props) {
781
- const { buttonStyle, containerStyle, iconStyle } = useStyles$1();
814
+ const { buttonStyle, containerStyle, iconStyle } = useStyles$2();
782
815
  function navigateToService() {
783
816
  window.open(props.serviceUrl, "_blank");
784
817
  }
@@ -793,8 +826,196 @@ function OpenServiceButton(props) {
793
826
  ));
794
827
  }
795
828
 
829
+ function labelFromStatus(status) {
830
+ let label;
831
+ switch (status) {
832
+ case "active":
833
+ label = "OK";
834
+ break;
835
+ case "warning":
836
+ label = "ACTIVE";
837
+ break;
838
+ case "critical":
839
+ label = "ALARM";
840
+ break;
841
+ case "maintenance":
842
+ label = "MAINTENANCE";
843
+ break;
844
+ case "disabled":
845
+ label = "DISABLED";
846
+ break;
847
+ default:
848
+ label = "OK";
849
+ break;
850
+ }
851
+ return label;
852
+ }
853
+ function colorFromStatus(theme, status) {
854
+ let color;
855
+ switch (status) {
856
+ case "active":
857
+ color = theme.palette.success.main;
858
+ break;
859
+ case "warning":
860
+ color = theme.palette.warningBackground;
861
+ break;
862
+ case "critical":
863
+ color = theme.palette.error.main;
864
+ break;
865
+ case "maintenance":
866
+ color = "#ebdc00";
867
+ break;
868
+ case "disabled":
869
+ color = "#A9A9A9";
870
+ break;
871
+ default:
872
+ color = theme.palette.success.main;
873
+ break;
874
+ }
875
+ return color;
876
+ }
877
+ function StatusCard({ status }) {
878
+ const useStyles = makeStyles$1((theme) => ({
879
+ cardStyle: {
880
+ height: "120px",
881
+ display: "flex",
882
+ alignItems: "center",
883
+ justifyContent: "center",
884
+ backgroundColor: status !== void 0 ? colorFromStatus(theme, status) : colorFromStatus(theme, "active"),
885
+ marginRight: "10px"
886
+ },
887
+ largeTextStyle: {
888
+ color: "white",
889
+ fontWeight: "bold",
890
+ fontSize: "20px",
891
+ wordWrap: "break-word"
892
+ }
893
+ }));
894
+ const { cardStyle, largeTextStyle } = useStyles();
895
+ return /* @__PURE__ */ React.createElement(Card, { className: cardStyle }, status !== void 0 ? /* @__PURE__ */ React.createElement(Typography, { className: largeTextStyle }, labelFromStatus(status)) : /* @__PURE__ */ React.createElement(Typography, { className: largeTextStyle }, "Unable to get status"));
896
+ }
897
+
898
+ function colorFromPercentage(theme, percentage) {
899
+ if (percentage < 0.5) {
900
+ return theme.palette.error.main;
901
+ } else if (percentage < 0.8) {
902
+ return theme.palette.warning.main;
903
+ }
904
+ return theme.palette.success.main;
905
+ }
906
+ function ServiceStandardsCard({ total, completed }) {
907
+ const useStyles = makeStyles((theme) => ({
908
+ cardStyle: {
909
+ height: "120px",
910
+ display: "grid",
911
+ gridTemplateRows: "1fr auto auto",
912
+ backgroundColor: "rgba(0, 0, 0, 0.03)"
913
+ },
914
+ containerStyle: {
915
+ display: "flex",
916
+ justifyContent: "center"
917
+ },
918
+ largeTextStyle: {
919
+ fontSize: "50px",
920
+ color: completed !== void 0 && total !== void 0 ? colorFromPercentage(theme, completed / total) : colorFromPercentage(theme, 0),
921
+ alignSelf: "center",
922
+ justifyContent: "center"
923
+ },
924
+ smallTextStyle: {
925
+ color: theme.palette.textSubtle,
926
+ fontSize: "14px",
927
+ fontWeight: "bold",
928
+ alignSelf: "center",
929
+ justifyContent: "center",
930
+ marginLeft: "-2px",
931
+ marginTop: "25px"
932
+ }
933
+ }));
934
+ const BorderLinearProgress = withStyles((theme) => ({
935
+ root: {
936
+ height: 10,
937
+ borderRadius: 5,
938
+ margin: 5
939
+ },
940
+ colorPrimary: {
941
+ backgroundColor: theme.palette.grey[theme.palette.type === "light" ? 200 : 700]
942
+ },
943
+ bar: {
944
+ borderRadius: 5,
945
+ backgroundColor: completed !== void 0 && total !== void 0 ? colorFromPercentage(theme, completed / total) : colorFromPercentage(theme, 0)
946
+ }
947
+ }))(LinearProgress);
948
+ const { cardStyle, containerStyle, largeTextStyle, smallTextStyle } = useStyles();
949
+ return /* @__PURE__ */ React.createElement(Card, { className: cardStyle }, completed !== void 0 && total !== void 0 ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: containerStyle }, /* @__PURE__ */ React.createElement(Typography, { className: largeTextStyle }, completed), /* @__PURE__ */ React.createElement(Typography, { className: smallTextStyle }, "/", total)), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(
950
+ BorderLinearProgress,
951
+ {
952
+ variant: "determinate",
953
+ value: completed / total * 100
954
+ }
955
+ ))) : /* @__PURE__ */ React.createElement("div", { className: containerStyle }, /* @__PURE__ */ React.createElement(Typography, { className: largeTextStyle }, "Unable to retrieve Scores")));
956
+ }
957
+
958
+ function IncidentCounterCard({ count, label, color }) {
959
+ const textColor = color && validateColor(color) ? color : "black";
960
+ const useStyles = makeStyles(() => ({
961
+ cardStyle: {
962
+ marginRight: "10px",
963
+ height: "120px",
964
+ display: "flex",
965
+ alignItems: "center",
966
+ justifyContent: "center",
967
+ backgroundColor: "rgba(0, 0, 0, 0.03)"
968
+ },
969
+ largeTextStyle: {
970
+ color: textColor,
971
+ fontSize: "40px",
972
+ marginTop: "-20px"
973
+ },
974
+ smallTextStyle: {
975
+ color: textColor,
976
+ fontWeight: "bold",
977
+ fontSize: "10px",
978
+ marginTop: "-10px",
979
+ textTransform: "uppercase",
980
+ flexWrap: "wrap"
981
+ }
982
+ }));
983
+ const { cardStyle, largeTextStyle, smallTextStyle } = useStyles();
984
+ return /* @__PURE__ */ React.createElement(Card, { className: cardStyle }, /* @__PURE__ */ React.createElement(Typography, { className: largeTextStyle }, count), /* @__PURE__ */ React.createElement(Typography, { className: smallTextStyle }, label));
985
+ }
986
+
987
+ const useStyles$1 = makeStyles$1(
988
+ (theme) => createStyles({
989
+ overviewHeaderTextStyle: {
990
+ fontSize: "14px",
991
+ fontWeight: 500,
992
+ color: theme.palette.type === "light" ? "rgba(0, 0, 0, 0.54)" : "rgba(255, 255, 255, 0.7)"
993
+ },
994
+ headerStyle: {
995
+ marginRight: "1em"
996
+ },
997
+ overviewHeaderContainerStyle: {
998
+ display: "flex",
999
+ margin: "15px",
1000
+ marginBottom: "20px"
1001
+ },
1002
+ overviewCardsContainerStyle: {
1003
+ display: "flex",
1004
+ margin: "15px",
1005
+ marginTop: "-15px"
1006
+ },
1007
+ incidentMetricsContainerStyle: {
1008
+ display: "flex",
1009
+ height: "100%",
1010
+ justifyContent: "center",
1011
+ columnSpan: "all"
1012
+ }
1013
+ })
1014
+ );
796
1015
  const BasicCard = ({ children }) => /* @__PURE__ */ React.createElement(InfoCard, { title: "PagerDuty" }, children);
797
1016
  const PagerDutyCard$1 = (props) => {
1017
+ var _a, _b;
1018
+ const classes = useStyles$1();
798
1019
  const theme = useTheme();
799
1020
  const { readOnly, disableChangeEvents, integrationKey, name } = props;
800
1021
  const api = useApi(pagerDutyApiRef);
@@ -816,13 +1037,18 @@ const PagerDutyCard$1 = (props) => {
816
1037
  const { service: foundService } = await api.getServiceByPagerDutyEntity(
817
1038
  props
818
1039
  );
819
- return {
1040
+ const { standards: serviceStandards } = await api.getServiceStandardsByServiceId(foundService.id);
1041
+ const result = {
820
1042
  id: foundService.id,
821
1043
  name: foundService.name,
822
1044
  url: foundService.html_url,
823
1045
  policyId: foundService.escalation_policy.id,
824
- policyLink: foundService.escalation_policy.html_url
1046
+ policyLink: foundService.escalation_policy.html_url,
1047
+ policyName: foundService.escalation_policy.name,
1048
+ status: foundService.status,
1049
+ standardsScore: serviceStandards.score
825
1050
  };
1051
+ return result;
826
1052
  }, [props]);
827
1053
  if (error) {
828
1054
  let errorNode;
@@ -845,249 +1071,38 @@ const PagerDutyCard$1 = (props) => {
845
1071
  return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Card, { "data-testid": "pagerduty-card" }, /* @__PURE__ */ React.createElement(
846
1072
  CardHeader,
847
1073
  {
848
- style: { marginRight: "1em" },
1074
+ className: classes.headerStyle,
849
1075
  title: theme.palette.type === "dark" ? /* @__PURE__ */ React.createElement("img", { src: PDWhiteImage, alt: "PagerDuty", height: "35" }) : /* @__PURE__ */ React.createElement("img", { src: PDGreenImage, alt: "PagerDuty", height: "35" }),
850
1076
  action: !readOnly ? /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(TriggerIncidentButton, null), /* @__PURE__ */ React.createElement(OpenServiceButton, { serviceUrl: service.url })) : /* @__PURE__ */ React.createElement(OpenServiceButton, { serviceUrl: service.url })
851
1077
  }
852
- ), /* @__PURE__ */ React.createElement(Grid, { item: true, md: 12, style: { display: "flex", margin: "15px", marginBottom: "20px" } }, /* @__PURE__ */ React.createElement(Grid, { item: true, md: 3 }, /* @__PURE__ */ React.createElement(
853
- Typography,
1078
+ ), /* @__PURE__ */ React.createElement(Grid, { item: true, md: 12, className: classes.overviewHeaderContainerStyle }, /* @__PURE__ */ React.createElement(Grid, { item: true, md: 3 }, /* @__PURE__ */ React.createElement(Typography, { className: classes.overviewHeaderTextStyle }, "STATUS")), /* @__PURE__ */ React.createElement(Grid, { item: true, md: 6 }, /* @__PURE__ */ React.createElement(Typography, { className: classes.overviewHeaderTextStyle }, "INSIGHTS")), /* @__PURE__ */ React.createElement(Grid, { item: true, md: 3 }, /* @__PURE__ */ React.createElement(Typography, { className: classes.overviewHeaderTextStyle }, "STANDARDS"))), /* @__PURE__ */ React.createElement(Grid, { item: true, md: 12, className: classes.overviewCardsContainerStyle }, /* @__PURE__ */ React.createElement(Grid, { item: true, md: 3 }, /* @__PURE__ */ React.createElement(StatusCard, { status: service == null ? void 0 : service.status })), /* @__PURE__ */ React.createElement(Grid, { item: true, md: 6, className: classes.incidentMetricsContainerStyle }, /* @__PURE__ */ React.createElement(Grid, { md: 4 }, /* @__PURE__ */ React.createElement(
1079
+ IncidentCounterCard,
854
1080
  {
855
- style: {
856
- fontSize: "14px",
857
- fontWeight: "500",
858
- color: "rgba(0, 0, 0, 0.54)"
859
- // rgba(255, 255, 255, 0.7)
860
- }
861
- },
862
- "STATUS"
863
- )), /* @__PURE__ */ React.createElement(Grid, { item: true, md: 6 }, /* @__PURE__ */ React.createElement(
864
- Typography,
1081
+ count: 29,
1082
+ label: "interruptions",
1083
+ color: theme.palette.textSubtle
1084
+ }
1085
+ )), /* @__PURE__ */ React.createElement(Grid, { md: 4 }, /* @__PURE__ */ React.createElement(
1086
+ IncidentCounterCard,
865
1087
  {
866
- style: {
867
- fontSize: "14px",
868
- fontWeight: "500",
869
- color: "rgba(0, 0, 0, 0.54)"
870
- // rgba(255, 255, 255, 0.7)
871
- }
872
- },
873
- "INCIDENT HISTORY"
874
- )), /* @__PURE__ */ React.createElement(Grid, { item: true, md: 3 }, /* @__PURE__ */ React.createElement(
875
- Typography,
1088
+ count: 29,
1089
+ label: "high urgency",
1090
+ color: theme.palette.warning.main
1091
+ }
1092
+ )), /* @__PURE__ */ React.createElement(Grid, { md: 4 }, /* @__PURE__ */ React.createElement(
1093
+ IncidentCounterCard,
876
1094
  {
877
- style: {
878
- fontSize: "14px",
879
- fontWeight: "500",
880
- color: "rgba(0, 0, 0, 0.54)"
881
- // rgba(255, 255, 255, 0.7)
882
- }
883
- },
884
- "SERVICE STANDARDS"
885
- ))), /* @__PURE__ */ React.createElement(
886
- Grid,
1095
+ count: 32,
1096
+ label: "incidents",
1097
+ color: theme.palette.error.main
1098
+ }
1099
+ ))), /* @__PURE__ */ React.createElement(Grid, { item: true, md: 3 }, /* @__PURE__ */ React.createElement(
1100
+ ServiceStandardsCard,
887
1101
  {
888
- item: true,
889
- md: 12,
890
- style: { display: "flex", margin: "15px", marginTop: "-15px" }
891
- },
892
- /* @__PURE__ */ React.createElement(Grid, { item: true, md: 3 }, /* @__PURE__ */ React.createElement(
893
- Card,
894
- {
895
- style: {
896
- background: theme.palette.error.main,
897
- marginRight: "10px",
898
- height: "120px",
899
- display: "flex",
900
- alignItems: "center",
901
- justifyContent: "center"
902
- }
903
- },
904
- /* @__PURE__ */ React.createElement(
905
- Typography,
906
- {
907
- variant: "h3",
908
- style: { color: "white", fontWeight: "bold" }
909
- },
910
- "ALARM"
911
- )
912
- )),
913
- /* @__PURE__ */ React.createElement(
914
- Grid,
915
- {
916
- item: true,
917
- md: 6,
918
- style: {
919
- display: "flex",
920
- height: "100%",
921
- justifyContent: "center",
922
- columnSpan: "all"
923
- }
924
- },
925
- /* @__PURE__ */ React.createElement(Grid, { md: 4, style: { height: "100%" } }, /* @__PURE__ */ React.createElement(
926
- Card,
927
- {
928
- style: {
929
- marginRight: "10px",
930
- height: "120px",
931
- display: "flex",
932
- alignItems: "center",
933
- justifyContent: "center",
934
- backgroundColor: "rgba(0, 0, 0, 0.03)"
935
- }
936
- },
937
- /* @__PURE__ */ React.createElement(
938
- Typography,
939
- {
940
- style: {
941
- color: theme.palette.error.main,
942
- fontSize: "40px"
943
- }
944
- },
945
- "1"
946
- ),
947
- /* @__PURE__ */ React.createElement(
948
- Typography,
949
- {
950
- style: {
951
- color: theme.palette.error.main,
952
- fontWeight: "bold",
953
- fontSize: "10px"
954
- }
955
- },
956
- "TRIGGERED"
957
- )
958
- )),
959
- /* @__PURE__ */ React.createElement(Grid, { md: 4 }, /* @__PURE__ */ React.createElement(
960
- Card,
961
- {
962
- style: {
963
- marginRight: "10px",
964
- height: "120px",
965
- display: "flex",
966
- alignItems: "center",
967
- justifyContent: "center",
968
- backgroundColor: "rgba(0, 0, 0, 0.03)"
969
- }
970
- },
971
- /* @__PURE__ */ React.createElement(
972
- Typography,
973
- {
974
- style: {
975
- color: theme.palette.warning.main,
976
- fontSize: "40px"
977
- }
978
- },
979
- "1"
980
- ),
981
- /* @__PURE__ */ React.createElement(
982
- Typography,
983
- {
984
- style: {
985
- color: theme.palette.warning.main,
986
- fontWeight: "bold",
987
- fontSize: "10px"
988
- }
989
- },
990
- "ACKNOWLEDGED"
991
- )
992
- )),
993
- /* @__PURE__ */ React.createElement(Grid, { md: 4 }, /* @__PURE__ */ React.createElement(
994
- Card,
995
- {
996
- style: {
997
- marginRight: "10px",
998
- height: "120px",
999
- display: "flex",
1000
- alignItems: "center",
1001
- justifyContent: "center",
1002
- backgroundColor: "rgba(0, 0, 0, 0.03)"
1003
- }
1004
- },
1005
- /* @__PURE__ */ React.createElement(
1006
- Typography,
1007
- {
1008
- style: {
1009
- color: theme.palette.success.main,
1010
- fontSize: "40px"
1011
- }
1012
- },
1013
- "43"
1014
- ),
1015
- /* @__PURE__ */ React.createElement(
1016
- Typography,
1017
- {
1018
- style: {
1019
- color: theme.palette.success.main,
1020
- fontWeight: "bold",
1021
- fontSize: "10px"
1022
- }
1023
- },
1024
- "RESOLVED"
1025
- )
1026
- ))
1027
- ),
1028
- /* @__PURE__ */ React.createElement(Grid, { item: true, md: 3 }, /* @__PURE__ */ React.createElement(
1029
- Card,
1030
- {
1031
- style: {
1032
- height: "120px",
1033
- display: "grid",
1034
- gridTemplateRows: "1fr auto auto",
1035
- backgroundColor: "rgba(0, 0, 0, 0.03)"
1036
- }
1037
- },
1038
- /* @__PURE__ */ React.createElement(
1039
- "div",
1040
- {
1041
- style: {
1042
- display: "flex",
1043
- justifyContent: "center"
1044
- }
1045
- },
1046
- /* @__PURE__ */ React.createElement(
1047
- Typography,
1048
- {
1049
- style: {
1050
- fontSize: "50px",
1051
- color: "rgba(0, 0, 0, 0.7)",
1052
- // rgba(255, 255, 255, 0.7)
1053
- fontWeight: "bold",
1054
- alignSelf: "center",
1055
- justifyContent: "center"
1056
- }
1057
- },
1058
- "4"
1059
- ),
1060
- /* @__PURE__ */ React.createElement(
1061
- Typography,
1062
- {
1063
- variant: "body1",
1064
- style: {
1065
- fontWeight: "500",
1066
- color: "rgba(0, 0, 0, 0.7)",
1067
- // rgba(255, 255, 255, 0.7)
1068
- alignSelf: "center",
1069
- justifyContent: "center",
1070
- marginLeft: "1px",
1071
- marginTop: "20px"
1072
- }
1073
- },
1074
- "/5"
1075
- )
1076
- ),
1077
- /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(
1078
- LinearProgress,
1079
- {
1080
- variant: "determinate",
1081
- value: 80,
1082
- style: {
1083
- margin: "5px",
1084
- borderRadius: "10px",
1085
- height: "10px"
1086
- }
1087
- }
1088
- ))
1089
- ))
1090
- ), /* @__PURE__ */ React.createElement(Divider, null), /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(TabbedCard, null, /* @__PURE__ */ React.createElement(CardTab, { label: "Incidents" }, /* @__PURE__ */ React.createElement(
1102
+ total: (_a = service == null ? void 0 : service.standardsScore) == null ? void 0 : _a.total,
1103
+ completed: (_b = service == null ? void 0 : service.standardsScore) == null ? void 0 : _b.passing
1104
+ }
1105
+ ))), /* @__PURE__ */ React.createElement(Divider, null), /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(TabbedCard, null, /* @__PURE__ */ React.createElement(CardTab, { label: "Incidents" }, /* @__PURE__ */ React.createElement(
1091
1106
  Incidents,
1092
1107
  {
1093
1108
  serviceId: service.id,
@@ -1099,7 +1114,14 @@ const PagerDutyCard$1 = (props) => {
1099
1114
  serviceId: service.id,
1100
1115
  refreshEvents: refreshChangeEvents
1101
1116
  }
1102
- )) : /* @__PURE__ */ React.createElement(React.Fragment, null)), /* @__PURE__ */ React.createElement(EscalationPolicy, { policyId: service.policyId }))), !createIncidentDisabled && /* @__PURE__ */ React.createElement(
1117
+ )) : /* @__PURE__ */ React.createElement(React.Fragment, null)), /* @__PURE__ */ React.createElement(
1118
+ EscalationPolicy,
1119
+ {
1120
+ policyId: service.policyId,
1121
+ policyUrl: service.policyLink,
1122
+ policyName: service.policyName
1123
+ }
1124
+ ))), !createIncidentDisabled && /* @__PURE__ */ React.createElement(
1103
1125
  TriggerDialog,
1104
1126
  {
1105
1127
  "data-testid": "trigger-dialog",
@@ -1167,7 +1189,7 @@ function TriggerButton(props) {
1167
1189
  className: disabled ? "" : buttonStyle,
1168
1190
  disabled
1169
1191
  },
1170
- integrationKey ? (_a = props.children) != null ? _a : "Create Incident" : "Missing integration key"
1192
+ integrationKey ? (_a = props.children) != null ? _a : "Create new incident" : "Missing integration key"
1171
1193
  ), integrationKey && /* @__PURE__ */ React.createElement(
1172
1194
  TriggerDialog,
1173
1195
  {
@@ -1182,4 +1204,4 @@ function TriggerButton(props) {
1182
1204
  const PagerDutyCard = EntityPagerDutyCard;
1183
1205
 
1184
1206
  export { EntityPagerDutyCard$1 as E, HomePagePagerDutyCard as H, PagerDutyCard$1 as P, TriggerButton as T, UnauthorizedError as U, PagerDutyClient as a, pagerDutyApiRef as b, PagerDutyCard as c, isPluginApplicableToEntity as i, pagerDutyPlugin as p };
1185
- //# sourceMappingURL=index-b664ea6c.esm.js.map
1207
+ //# sourceMappingURL=index-76653f8d.esm.js.map