From 71c45827838fe75a0c49c1d8ab8b86ea015bcb79 Mon Sep 17 00:00:00 2001 From: Remi Date: Thu, 4 Feb 2021 23:42:27 +0100 Subject: [PATCH] feat(splunk-on-call-plugin): add Incident --- .../Incident/IncidentEmptyState.tsx | 36 +++ .../components/Incident/IncidentListItem.tsx | 242 ++++++++++++++++++ .../src/components/Incident/Incidents.tsx | 68 +++++ .../src/components/Incident/index.ts | 17 ++ 4 files changed, 363 insertions(+) create mode 100644 plugins/splunk-on-call/src/components/Incident/IncidentEmptyState.tsx create mode 100644 plugins/splunk-on-call/src/components/Incident/IncidentListItem.tsx create mode 100644 plugins/splunk-on-call/src/components/Incident/Incidents.tsx create mode 100644 plugins/splunk-on-call/src/components/Incident/index.ts diff --git a/plugins/splunk-on-call/src/components/Incident/IncidentEmptyState.tsx b/plugins/splunk-on-call/src/components/Incident/IncidentEmptyState.tsx new file mode 100644 index 0000000000..f7a0398c55 --- /dev/null +++ b/plugins/splunk-on-call/src/components/Incident/IncidentEmptyState.tsx @@ -0,0 +1,36 @@ +/* + * 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 from 'react'; +import { Grid, Typography } from '@material-ui/core'; +import EmptyStateImage from '../../assets/emptystate.svg'; + +export const IncidentsEmptyState = () => { + return ( + + + Nice! No incidents found! + + + EmptyState + + + ); +}; diff --git a/plugins/splunk-on-call/src/components/Incident/IncidentListItem.tsx b/plugins/splunk-on-call/src/components/Incident/IncidentListItem.tsx new file mode 100644 index 0000000000..7384498df5 --- /dev/null +++ b/plugins/splunk-on-call/src/components/Incident/IncidentListItem.tsx @@ -0,0 +1,242 @@ +/* + * 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, { useEffect } from 'react'; +import { + ListItem, + ListItemIcon, + ListItemSecondaryAction, + Tooltip, + ListItemText, + makeStyles, + IconButton, + Typography, + withStyles, +} from '@material-ui/core'; +import DoneIcon from '@material-ui/icons/Done'; +import DoneAllIcon from '@material-ui/icons/DoneAll'; +import { + StatusError, + StatusWarning, + StatusOK, + identityApiRef, + useApi, + alertApiRef, +} from '@backstage/core'; +import { formatDistanceToNowStrict } from 'date-fns'; +import { Incident } from '../types'; +import OpenInBrowserIcon from '@material-ui/icons/OpenInBrowser'; +import { splunkOnCallApiRef } from '../../api/client'; +import { useAsyncFn } from 'react-use'; +import { PatchIncidentRequest } from '../../api/types'; + +const useStyles = makeStyles({ + denseListIcon: { + marginRight: 0, + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + }, + listItemPrimary: { + fontWeight: 'bold', + }, + listItemIcon: { + minWidth: '1em', + }, + secondaryAction: { + paddingRight: 48, + }, +}); + +const ListItemWithWiderSecondaryAction = withStyles({ + secondaryAction: { + paddingRight: 96, + }, +})(ListItem); + +type Props = { + incident: Incident; + onIncidentAction: () => void; +}; + +const IncidentPhase = ({ currentPhase }: { currentPhase: string }) => { + switch (currentPhase) { + case 'UNACKED': + return ; + case 'ACKED': + return ; + default: + return ; + } +}; + +const IncidentAction = ({ + currentPhase, + userName, + incidentNames, + resolveAction, + aknowledgeAction, +}: { + currentPhase: string; + userName: string; + incidentNames: string[]; + resolveAction: (args: PatchIncidentRequest) => void; + aknowledgeAction: (args: PatchIncidentRequest) => void; +}) => { + switch (currentPhase) { + case 'UNACKED': + return ( + + aknowledgeAction({ userName, incidentNames })} + > + + + + ); + case 'ACKED': + return ( + + resolveAction({ userName, incidentNames })} + > + + + + ); + default: + return <>; + } +}; + +export const IncidentListItem = ({ incident, onIncidentAction }: Props) => { + const classes = useStyles(); + const createdAt = formatDistanceToNowStrict(new Date(incident.startTime!)); + const alertApi = useApi(alertApiRef); + const identityApi = useApi(identityApiRef); + const userName = identityApi.getUserId(); + const api = useApi(splunkOnCallApiRef); + + const hasBeenManuallyTriggered = incident.monitorName?.includes('vouser-'); + + const user = hasBeenManuallyTriggered + ? incident.monitorName?.replace('vouser-', '') + : incident.monitorName; + + const [ + { value: resolveValue, loading: _resolveLoading, error: resolveError }, + handleResolveIncident, + ] = useAsyncFn( + async ({ userName, incidentNames }: PatchIncidentRequest) => + await api.resolveIncident({ + userName, + incidentNames, + }), + ); + + const [ + { + value: acknowledgeValue, + loading: _acknowledgeLoading, + error: acknowledgeError, + }, + handleAcknowledgeIncident, + ] = useAsyncFn( + async ({ userName, incidentNames }: PatchIncidentRequest) => + await api.acknowledgeIncident({ + userName, + incidentNames, + }), + ); + + useEffect(() => { + if (acknowledgeValue) { + alertApi.post({ + message: `Incident successfully acknowledged`, + }); + } + + if (resolveValue) { + alertApi.post({ + message: `Incident successfully resolved`, + }); + } + if (resolveValue || acknowledgeValue) { + onIncidentAction(); + } + }, [acknowledgeValue, resolveValue, alertApi, onIncidentAction]); + + if (acknowledgeError) { + alertApi.post({ + message: `Failed to acknowledge incident. ${acknowledgeError.message}`, + severity: 'error', + }); + } + + if (resolveError) { + alertApi.post({ + message: `Failed to resolve incident. ${resolveError.message}`, + severity: 'error', + }); + } + + return ( + + + +
+ +
+
+
+ + Created {createdAt} ago {user && `by ${user}`} + + } + /> + + {incident.incidentLink && incident.incidentNumber && ( + + + + + + + + + )} +
+ ); +}; diff --git a/plugins/splunk-on-call/src/components/Incident/Incidents.tsx b/plugins/splunk-on-call/src/components/Incident/Incidents.tsx new file mode 100644 index 0000000000..84b0866757 --- /dev/null +++ b/plugins/splunk-on-call/src/components/Incident/Incidents.tsx @@ -0,0 +1,68 @@ +/* + * 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, { useEffect } from 'react'; +import { List, ListSubheader } from '@material-ui/core'; +import { IncidentListItem } from './IncidentListItem'; +import { IncidentsEmptyState } from './IncidentEmptyState'; +import { useAsyncFn } from 'react-use'; +import { splunkOnCallApiRef } from '../../api'; +import { useApi, Progress } from '@backstage/core'; +import { Alert } from '@material-ui/lab'; + +type Props = { + refreshIncidents: boolean; +}; + +export const Incidents = ({ refreshIncidents }: Props) => { + const api = useApi(splunkOnCallApiRef); + + const [{ value: incidents, loading, error }, getIncidents] = useAsyncFn( + async () => await api.getIncidents(), + ); + + useEffect(() => { + getIncidents(); + }, [refreshIncidents, getIncidents]); + + if (error) { + return ( + + Error encountered while fetching information. {error.message} + + ); + } + + if (loading) { + return ; + } + + if (!incidents?.length) { + return ; + } + + return ( + INCIDENTS}> + {incidents!.map((incident, index) => ( + getIncidents()} + key={index} + incident={incident} + /> + ))} + + ); +}; diff --git a/plugins/splunk-on-call/src/components/Incident/index.ts b/plugins/splunk-on-call/src/components/Incident/index.ts new file mode 100644 index 0000000000..fb2702602b --- /dev/null +++ b/plugins/splunk-on-call/src/components/Incident/index.ts @@ -0,0 +1,17 @@ +/* + * 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 { Incidents } from './Incidents';