diff --git a/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx b/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx index cbce0ce4a3..ccf70abb0b 100644 --- a/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx +++ b/plugins/pagerduty/src/components/Escalation/EscalationPolicy.tsx @@ -15,21 +15,47 @@ */ import React from 'react'; -import { List, ListSubheader } from '@material-ui/core'; -import { User } from '../types'; +import { List, ListSubheader, LinearProgress } from '@material-ui/core'; import { EscalationUsersEmptyState } from './EscalationUsersEmptyState'; import { EscalationUser } from './EscalationUser'; +import { useAsync } from 'react-use'; +import { pagerDutyApiRef } from '../../api'; +import { useApi } from '@backstage/core'; +import { Alert } from '@material-ui/lab'; -type EscalationPolicyProps = { - users: User[]; +type Props = { + policyId: string; }; -export const EscalationPolicy = ({ users }: EscalationPolicyProps) => ( - ON CALL}> - {users.length ? ( - users.map((user, index) => ) - ) : ( - - )} - -); +export const EscalationPolicy = ({ policyId }: Props) => { + const api = useApi(pagerDutyApiRef); + + const { value: users, loading, error } = useAsync(async () => { + const oncalls = await api.getOnCallByPolicyId(policyId); + const users = oncalls.map(oncall => oncall.user); + + return users; + }); + + if (error) { + return ( + + Error encountered while fetching information. {error.message} + + ); + } + + if (loading) { + return ; + } + + return users!.length ? ( + ON CALL}> + {users!.map((user, index) => ( + + ))} + + ) : ( + + ); +}; diff --git a/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx b/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx index adc8d4e6d7..642c9ecd96 100644 --- a/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx +++ b/plugins/pagerduty/src/components/Escalation/EscalationUser.tsx @@ -36,8 +36,13 @@ const useStyles = makeStyles({ }, }); -export const EscalationUser = ({ user }: { user: User }) => { +type Props = { + user: User; +}; + +export const EscalationUser = ({ user }: Props) => { const classes = useStyles(); + return ( diff --git a/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx index 6e22ccbbac..7a97dad281 100644 --- a/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx +++ b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx @@ -31,10 +31,6 @@ import moment from 'moment'; import { Incident } from '../types'; import PagerdutyIcon from '../PagerDutyIcon'; -type IncidentListItemProps = { - incident: Incident; -}; - const useStyles = makeStyles({ denseListIcon: { marginRight: 0, @@ -51,9 +47,15 @@ const useStyles = makeStyles({ }, }); -export const IncidentListItem = ({ incident }: IncidentListItemProps) => { +type Props = { + incident: Incident; +}; + +export const IncidentListItem = ({ incident }: Props) => { const classes = useStyles(); - const user = incident.assignments[0].assignee; + const user = incident.assignments[0]?.assignee; + const createdAt = moment(incident.created_at).fromNow(); + return ( @@ -68,25 +70,28 @@ export const IncidentListItem = ({ incident }: IncidentListItemProps) => { - {incident.title} - - } + primary={incident.title} + primaryTypographyProps={{ + variant: 'body1', + className: classes.listItemPrimary, + }} secondary={ - - Created {moment(incident.created_at).fromNow()}, assigned to{' '} - {(incident?.assignments[0]?.assignee?.summary && ( - {user.summary} - )) || - 'nobody'} - + + Created {createdAt} and assigned to{' '} + + {user?.summary ?? 'nobody'} + + } /> void; }; -export const Incidents = ({ incidents }: IncidentsProps) => ( - {incidents.length && 'INCIDENTS'}} - > - {incidents.length ? ( - incidents.map((incident, index) => ( +export const Incidents = ({ + serviceId, + shouldRefreshIncidents, + setShouldRefreshIncidents, +}: Props) => { + const api = useApi(pagerDutyApiRef); + + const [{ value: incidents, loading, error }, getIncidents] = useAsyncFn( + async () => { + setShouldRefreshIncidents(false); + return await api.getIncidentsByServiceId(serviceId); + }, + ); + + useEffect(() => { + getIncidents(); + }, [shouldRefreshIncidents, getIncidents]); + + if (error) { + return ( + + Error encountered while fetching information. {error.message} + + ); + } + + if (loading) { + return ; + } + + return incidents?.length ? ( + INCIDENTS}> + {incidents!.map((incident, index) => ( - )) - ) : ( - - )} - -); + ))} + + ) : ( + + ); +}; diff --git a/plugins/pagerduty/src/components/PagerdutyCard.tsx b/plugins/pagerduty/src/components/PagerdutyCard.tsx index 6b6d466850..f24e91a9e0 100644 --- a/plugins/pagerduty/src/components/PagerdutyCard.tsx +++ b/plugins/pagerduty/src/components/PagerdutyCard.tsx @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import React, { useState } from 'react'; import { useApi, EmptyState } from '@backstage/core'; import { Entity } from '@backstage/catalog-model'; import { + Button, Card, CardContent, CardHeader, @@ -26,13 +27,13 @@ import { } from '@material-ui/core'; import { Incidents } from './Incident'; import { EscalationPolicy } from './Escalation'; -import { TriggerButton } from './TriggerButton'; import { useAsync } from 'react-use'; import { Alert } from '@material-ui/lab'; import { pagerDutyApiRef, UnauthorizedError } from '../api'; import { IconLinkVertical } from '@backstage/plugin-catalog'; import PagerDutyIcon from './PagerDutyIcon'; -import ReportProblemIcon from '@material-ui/icons/ReportProblem'; +import AlarmAddIcon from '@material-ui/icons/AlarmAdd'; +import { TriggerDialog } from './TriggerDialog'; const useStyles = makeStyles(theme => ({ links: { @@ -42,6 +43,19 @@ const useStyles = makeStyles(theme => ({ gridAutoColumns: 'min-content', gridGap: theme.spacing(3), }, + 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'; @@ -56,35 +70,45 @@ type Props = { export const PagerDutyCard = ({ entity }: Props) => { const classes = useStyles(); const api = useApi(pagerDutyApiRef); + const [showDialog, setShowDialog] = useState(false); + const [shouldRefreshIncidents, setShouldRefreshIncidents] = useState( + false, + ); + const integrationKey = entity.metadata.annotations![ + PAGERDUTY_INTEGRATION_KEY + ]; - const { value, loading, error } = useAsync(async () => { - const integrationKey = entity.metadata.annotations![ - PAGERDUTY_INTEGRATION_KEY - ]; - + const { value: service, loading, error } = useAsync(async () => { const services = await api.getServiceByIntegrationKey(integrationKey); - const incidents = await api.getIncidentsByServiceId(services[0].id); - const oncalls = await api.getOnCallByPolicyId( - services[0].escalation_policy.id, - ); - const users = oncalls.map(oncall => oncall.user); return { - incidents, - users, id: services[0].id, name: services[0].name, - homepageUrl: services[0].html_url, + url: services[0].html_url, + policyId: services[0].escalation_policy.id, }; }); + const handleDialog = () => { + setShowDialog(!showDialog); + }; + if (error) { if (error instanceof UnauthorizedError) { return ( + Read More + + } /> ); } @@ -102,12 +126,21 @@ export const PagerDutyCard = ({ entity }: Props) => { const pagerdutyLink = { title: 'View in PagerDuty', - href: value!.homepageUrl, + href: service!.url, }; const triggerAlarm = { title: 'Trigger Alarm', - action: , + action: ( + + ), }; return ( @@ -123,7 +156,7 @@ export const PagerDutyCard = ({ entity }: Props) => { /> } + icon={} action={triggerAlarm.action} /> @@ -131,8 +164,19 @@ export const PagerDutyCard = ({ entity }: Props) => { /> - - + + + ); diff --git a/plugins/pagerduty/src/components/TriggerButton/TriggerButton.tsx b/plugins/pagerduty/src/components/TriggerButton/TriggerButton.tsx deleted file mode 100644 index a4f012ac47..0000000000 --- a/plugins/pagerduty/src/components/TriggerButton/TriggerButton.tsx +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2020 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, { useState } from 'react'; -import { Button, makeStyles } from '@material-ui/core'; -import { TriggerDialog } from '../TriggerDialog'; -import { Entity } from '@backstage/catalog-model'; -import { PAGERDUTY_INTEGRATION_KEY } from '../PagerDutyCard'; - -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', - }, - }, -}); - -type Props = { - entity: Entity; -}; - -export const TriggerButton = ({ entity }: Props) => { - const [showDialog, setShowDialog] = useState(false); - const classes = useStyles(); - - const handleDialog = () => { - setShowDialog(!showDialog); - }; - - return ( - <> - - {showDialog && ( - - )} - - ); -}; diff --git a/plugins/pagerduty/src/components/TriggerButton/index.ts b/plugins/pagerduty/src/components/TriggerButton/index.ts deleted file mode 100644 index 6a58a4f43f..0000000000 --- a/plugins/pagerduty/src/components/TriggerButton/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 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 { TriggerButton } from './TriggerButton'; diff --git a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx index 4156074460..e6bb6d107d 100644 --- a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx +++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx @@ -19,67 +19,61 @@ import { Dialog, DialogTitle, TextField, - makeStyles, DialogActions, Button, DialogContent, Typography, CircularProgress, } from '@material-ui/core'; -import { - Progress, - useApi, - alertApiRef, - identityApiRef, - WarningPanel, -} from '@backstage/core'; +import { useApi, alertApiRef, identityApiRef } from '@backstage/core'; import { useAsyncFn } from 'react-use'; import { pagerDutyApiRef } from '../../api'; -import { Alert, AlertTitle } from '@material-ui/lab'; - -const useStyles = makeStyles({ - warningText: { - border: '1px solid rgba(245, 155, 35, 0.5)', - backgroundColor: 'rgba(245, 155, 35, 0.2)', - padding: '0.5em 1em', - }, -}); +import { Alert } from '@material-ui/lab'; type Props = { name: string; integrationKey: string; - onClose: () => void; + showDialog: boolean; + handleDialog: () => void; + setShouldRefreshIncidents: (shouldRefreshIncidents: boolean) => void; }; -export const TriggerDialog = ({ name, integrationKey, onClose }: Props) => { - const classes = useStyles(); - const [description, setDescription] = useState(''); +export const TriggerDialog = ({ + name, + integrationKey, + showDialog, + handleDialog, + setShouldRefreshIncidents, +}: Props) => { const alertApi = useApi(alertApiRef); const identityApi = useApi(identityApiRef); const userId = identityApi.getUserId(); const api = useApi(pagerDutyApiRef); - - const descriptionChanged = (event: any) => { - setDescription(event.target.value); - }; + const [description, setDescription] = useState(''); const [{ value, loading, error }, handleTriggerAlarm] = useAsyncFn( - async (desc: string) => { - return await api.triggerAlarm( + async (desc: string) => + await api.triggerAlarm( integrationKey, window.location.toString(), desc, userId, - ); - }, + ), ); + const descriptionChanged = ( + event: React.ChangeEvent, + ) => { + setDescription(event.target.value); + }; + useEffect(() => { if (value) { alertApi.post({ message: `Alarm successfully triggered by ${userId}`, }); - onClose(); + setShouldRefreshIncidents(true); + handleDialog(); } if (error) { @@ -88,16 +82,19 @@ export const TriggerDialog = ({ name, integrationKey, onClose }: Props) => { severity: 'error', }); } - }, [value, error, alertApi, onClose, userId]); + }, [value, error, alertApi, handleDialog, userId, setShouldRefreshIncidents]); + + if (!showDialog) { + return null; + } return ( - + This action will trigger an incident for "{name}". - {/* "Note" */} If the issue you are seeing does not need urgent attention, please get in touch with the responsible team using their preferred @@ -141,7 +138,7 @@ export const TriggerDialog = ({ name, integrationKey, onClose }: Props) => { > Trigger Incident - diff --git a/plugins/pagerduty/src/components/types.tsx b/plugins/pagerduty/src/components/types.tsx index 9c9e29cb01..e968656e65 100644 --- a/plugins/pagerduty/src/components/types.tsx +++ b/plugins/pagerduty/src/components/types.tsx @@ -18,10 +18,10 @@ export type Incident = { id: string; title: string; status: string; - homepageUrl: string; + html_url: string; assignments: [ { - assignee: User; + assignee: Assignee; }, ]; serviceId: string; @@ -43,6 +43,12 @@ export type OnCall = { user: User; }; +export type Assignee = { + id: string; + summary: string; + html_url: string; +}; + export type User = { id: string; summary: string; diff --git a/plugins/pagerduty/src/index.ts b/plugins/pagerduty/src/index.ts index 58dc39932d..1e8e549ac9 100644 --- a/plugins/pagerduty/src/index.ts +++ b/plugins/pagerduty/src/index.ts @@ -17,4 +17,9 @@ export { plugin } from './plugin'; export { isPluginApplicableToEntity, PagerDutyCard, -} from './components/PagerDutyCard'; +} from './components/PagerdutyCard'; +export { + PagerDutyClientApi, + pagerDutyApiRef, + UnauthorizedError, +} from './api/client';