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!
+
+
+
+
+
+ );
+};
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';