diff --git a/.changeset/sour-shoes-perform.md b/.changeset/sour-shoes-perform.md new file mode 100644 index 0000000000..e2361cb815 --- /dev/null +++ b/.changeset/sour-shoes-perform.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-pagerduty': minor +--- + +Improved the UI of the pagerduty plugin, and added a standalone TriggerButton diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index e849667c19..dcd0cb4c05 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -33,7 +33,7 @@ import { EntityLinksCard, EntityPageLayout, } from '@backstage/plugin-catalog'; -import { useEntity } from '@backstage/plugin-catalog-react'; +import { EntityProvider, useEntity } from '@backstage/plugin-catalog-react'; import { isPluginApplicableToEntity as isCircleCIAvailable, Router as CircleCIRouter, @@ -178,7 +178,9 @@ const ComponentOverviewContent = ({ entity }: { entity: Entity }) => ( {isPagerDutyAvailable(entity) && ( - + + + )} diff --git a/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx index bf0d5d2587..2d4afa53cd 100644 --- a/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx +++ b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx @@ -17,7 +17,6 @@ import React from 'react'; import { ListItem, - ListItemIcon, ListItemSecondaryAction, Tooltip, ListItemText, @@ -25,13 +24,16 @@ import { IconButton, Link, Typography, + Chip, } from '@material-ui/core'; -import { StatusError, StatusWarning } from '@backstage/core'; +import Done from '@material-ui/icons/Done'; +import Warning from '@material-ui/icons/Warning'; import { DateTime, Duration } from 'luxon'; import { Incident } from '../types'; import OpenInBrowserIcon from '@material-ui/icons/OpenInBrowser'; +import { BackstageTheme } from '@backstage/theme'; -const useStyles = makeStyles({ +const useStyles = makeStyles(theme => ({ denseListIcon: { marginRight: 0, display: 'flex', @@ -42,10 +44,21 @@ const useStyles = makeStyles({ listItemPrimary: { fontWeight: 'bold', }, - listItemIcon: { - minWidth: '1em', + warning: { + borderColor: theme.palette.status.warning, + color: theme.palette.status.warning, + '& *': { + color: theme.palette.status.warning, + }, }, -}); + error: { + borderColor: theme.palette.status.error, + color: theme.palette.status.error, + '& *': { + color: theme.palette.status.error, + }, + }, +})); type Props = { incident: Incident; @@ -62,19 +75,23 @@ export const IncidentListItem = ({ incident }: Props) => { return ( - - -
- {incident.status === 'triggered' ? ( - - ) : ( - - )} -
-
-
+ : } + className={ + incident.status === 'triggered' + ? classes.error + : classes.warning + } + /> + {incident.title} + + } primaryTypographyProps={{ variant: 'body1', className: classes.listItemPrimary, diff --git a/plugins/pagerduty/src/components/PagerDutyCard.tsx b/plugins/pagerduty/src/components/PagerDutyCard.tsx index e03fb0fdbe..5f2ae42aee 100644 --- a/plugins/pagerduty/src/components/PagerDutyCard.tsx +++ b/plugins/pagerduty/src/components/PagerDutyCard.tsx @@ -19,7 +19,6 @@ import { Entity } from '@backstage/catalog-model'; import { useEntity } from '@backstage/plugin-catalog-react'; import { Button, - makeStyles, Card, CardHeader, Divider, @@ -31,54 +30,31 @@ import { useAsync } from 'react-use'; import { Alert } from '@material-ui/lab'; import { pagerDutyApiRef, UnauthorizedError } from '../api'; import AlarmAddIcon from '@material-ui/icons/AlarmAdd'; -import { TriggerDialog } from './TriggerDialog'; import { MissingTokenError } from './Errors/MissingTokenError'; import WebIcon from '@material-ui/icons/Web'; - -const useStyles = makeStyles({ - triggerAlarm: { - paddingTop: 0, - paddingBottom: 0, - fontSize: '0.7rem', - textTransform: 'uppercase', - fontWeight: 600, - letterSpacing: 1.2, - lineHeight: 1.5, - '&:hover, &:focus, &.focus': { - backgroundColor: 'transparent', - textDecoration: 'none', - }, - }, -}); - -export const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key'; +import { PAGERDUTY_INTEGRATION_KEY } from './constants'; +import { TriggerButton, useShowDialog } from './TriggerButton'; export const isPluginApplicableToEntity = (entity: Entity) => Boolean(entity.metadata.annotations?.[PAGERDUTY_INTEGRATION_KEY]); -type Props = { - /** @deprecated The entity is now grabbed from context instead */ - entity?: Entity; -}; - -export const PagerDutyCard = (_props: Props) => { - const classes = useStyles(); +export const PagerDutyCard = () => { const { entity } = useEntity(); const api = useApi(pagerDutyApiRef); - const [showDialog, setShowDialog] = useState(false); const [refreshIncidents, setRefreshIncidents] = useState(false); const integrationKey = entity.metadata.annotations![ PAGERDUTY_INTEGRATION_KEY ]; + const setShowDialog = useShowDialog()[1]; + + const showDialog = useCallback(() => { + setShowDialog(true); + }, [setShowDialog]); const handleRefresh = useCallback(() => { setRefreshIncidents(x => !x); }, []); - const handleDialog = useCallback(() => { - setShowDialog(x => !x); - }, []); - const { value: service, loading, error } = useAsync(async () => { const services = await api.getServiceByIntegrationKey(integrationKey); @@ -114,17 +90,8 @@ export const PagerDutyCard = (_props: Props) => { const triggerLink = { label: 'Create Incident', - action: ( - - ), - icon: , + action: , + icon: , }; return ( @@ -140,13 +107,6 @@ export const PagerDutyCard = (_props: Props) => { refreshIncidents={refreshIncidents} /> - ); diff --git a/plugins/pagerduty/src/components/TriggerButton/index.tsx b/plugins/pagerduty/src/components/TriggerButton/index.tsx new file mode 100644 index 0000000000..833e01d8aa --- /dev/null +++ b/plugins/pagerduty/src/components/TriggerButton/index.tsx @@ -0,0 +1,94 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { useCallback, PropsWithChildren } from 'react'; +import { createGlobalState } from 'react-use'; +import { makeStyles, Button } from '@material-ui/core'; +import { useEntity } from '@backstage/plugin-catalog-react'; +import { BackstageTheme } from '@backstage/theme'; + +import { TriggerDialog } from '../TriggerDialog'; +import { PAGERDUTY_INTEGRATION_KEY } from '../constants'; + +export interface TriggerButtonProps { + design: 'link' | 'button'; + onIncidentCreated?: () => void; +} + +const useStyles = makeStyles(theme => ({ + buttonStyle: { + backgroundColor: theme.palette.error.main, + color: theme.palette.error.contrastText, + '&:hover': { + backgroundColor: theme.palette.error.dark, + }, + }, + triggerAlarm: { + paddingTop: 0, + paddingBottom: 0, + fontSize: '0.7rem', + textTransform: 'uppercase', + fontWeight: 600, + letterSpacing: 1.2, + lineHeight: 1.5, + '&:hover, &:focus, &.focus': { + backgroundColor: 'transparent', + textDecoration: 'none', + }, + }, +})); + +export const useShowDialog = createGlobalState(false); + +export function TriggerButton({ + design, + onIncidentCreated, + children, +}: PropsWithChildren) { + const { buttonStyle, triggerAlarm } = useStyles(); + const { entity } = useEntity(); + const [dialogShown = false, setDialogShown] = useShowDialog(); + + const showDialog = useCallback(() => { + setDialogShown(true); + }, [setDialogShown]); + const hideDialog = useCallback(() => { + setDialogShown(false); + }, [setDialogShown]); + + const integrationKey = entity.metadata.annotations![ + PAGERDUTY_INTEGRATION_KEY + ]; + + return ( + <> + + + + ); +} diff --git a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx index c390ed1f82..c2084a41ed 100644 --- a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx +++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx @@ -35,7 +35,7 @@ type Props = { integrationKey: string; showDialog: boolean; handleDialog: () => void; - onIncidentCreated: () => void; + onIncidentCreated?: () => void; }; export const TriggerDialog = ({ @@ -69,11 +69,17 @@ export const TriggerDialog = ({ useEffect(() => { if (value) { - alertApi.post({ - message: `Alarm successfully triggered by ${userName}`, - }); - onIncidentCreated(); - handleDialog(); + (async () => { + alertApi.post({ + message: `Alarm successfully triggered by ${userName}`, + }); + + handleDialog(); + + // The pager duty API isn't always returning the newly created alarm immediately + await new Promise(resolve => setTimeout(resolve, 1000)); + onIncidentCreated?.(); + })(); } }, [value, alertApi, handleDialog, userName, onIncidentCreated]); diff --git a/plugins/pagerduty/src/components/constants.ts b/plugins/pagerduty/src/components/constants.ts new file mode 100644 index 0000000000..a7c32a362e --- /dev/null +++ b/plugins/pagerduty/src/components/constants.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const PAGERDUTY_INTEGRATION_KEY = 'pagerduty.com/integration-key'; diff --git a/plugins/pagerduty/src/index.ts b/plugins/pagerduty/src/index.ts index 2dfedab164..92c3c8e73c 100644 --- a/plugins/pagerduty/src/index.ts +++ b/plugins/pagerduty/src/index.ts @@ -23,6 +23,7 @@ export { isPluginApplicableToEntity as isPagerDutyAvailable, PagerDutyCard, } from './components/PagerDutyCard'; +export { TriggerButton } from './components/TriggerButton'; export { PagerDutyClient, pagerDutyApiRef,