Improve pagerduty plugin
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-pagerduty': minor
|
||||
---
|
||||
|
||||
Improved the UI of the pagerduty plugin, and added a standalone TriggerButton
|
||||
@@ -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 }) => (
|
||||
</Grid>
|
||||
{isPagerDutyAvailable(entity) && (
|
||||
<Grid item md={6}>
|
||||
<PagerDutyCard entity={entity} />
|
||||
<EntityProvider entity={entity}>
|
||||
<PagerDutyCard />
|
||||
</EntityProvider>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item md={4} sm={6}>
|
||||
|
||||
@@ -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<BackstageTheme>(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 (
|
||||
<ListItem dense key={incident.id}>
|
||||
<ListItemIcon className={classes.listItemIcon}>
|
||||
<Tooltip title={incident.status} placement="top">
|
||||
<div className={classes.denseListIcon}>
|
||||
{incident.status === 'triggered' ? (
|
||||
<StatusError />
|
||||
) : (
|
||||
<StatusWarning />
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={incident.title}
|
||||
primary={
|
||||
<>
|
||||
<Chip
|
||||
label={incident.status}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
icon={incident.status === 'acknowledged' ? <Done /> : <Warning />}
|
||||
className={
|
||||
incident.status === 'triggered'
|
||||
? classes.error
|
||||
: classes.warning
|
||||
}
|
||||
/>
|
||||
{incident.title}
|
||||
</>
|
||||
}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body1',
|
||||
className: classes.listItemPrimary,
|
||||
|
||||
@@ -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<boolean>(false);
|
||||
const [refreshIncidents, setRefreshIncidents] = useState<boolean>(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: (
|
||||
<Button
|
||||
data-testid="trigger-button"
|
||||
color="secondary"
|
||||
onClick={handleDialog}
|
||||
className={classes.triggerAlarm}
|
||||
>
|
||||
Create Incident
|
||||
</Button>
|
||||
),
|
||||
icon: <AlarmAddIcon onClick={handleDialog} />,
|
||||
action: <TriggerButton design="link" onIncidentCreated={handleRefresh} />,
|
||||
icon: <AlarmAddIcon onClick={showDialog} />,
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -140,13 +107,6 @@ export const PagerDutyCard = (_props: Props) => {
|
||||
refreshIncidents={refreshIncidents}
|
||||
/>
|
||||
<EscalationPolicy policyId={service!.policyId} />
|
||||
<TriggerDialog
|
||||
showDialog={showDialog}
|
||||
handleDialog={handleDialog}
|
||||
name={entity.metadata.name}
|
||||
integrationKey={integrationKey}
|
||||
onIncidentCreated={handleRefresh}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -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<BackstageTheme>(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<TriggerButtonProps>) {
|
||||
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 (
|
||||
<>
|
||||
<Button
|
||||
data-testid="trigger-button"
|
||||
{...(design === 'link' && { color: 'secondary' })}
|
||||
onClick={showDialog}
|
||||
className={design === 'link' ? triggerAlarm : buttonStyle}
|
||||
>
|
||||
{children ?? 'Create Incident'}
|
||||
</Button>
|
||||
<TriggerDialog
|
||||
showDialog={dialogShown}
|
||||
handleDialog={hideDialog}
|
||||
name={entity.metadata.name}
|
||||
integrationKey={integrationKey}
|
||||
onIncidentCreated={onIncidentCreated}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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]);
|
||||
|
||||
|
||||
@@ -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';
|
||||
@@ -23,6 +23,7 @@ export {
|
||||
isPluginApplicableToEntity as isPagerDutyAvailable,
|
||||
PagerDutyCard,
|
||||
} from './components/PagerDutyCard';
|
||||
export { TriggerButton } from './components/TriggerButton';
|
||||
export {
|
||||
PagerDutyClient,
|
||||
pagerDutyApiRef,
|
||||
|
||||
Reference in New Issue
Block a user