feat(splunk-on-call-plugin): edge cases
This commit is contained in:
+1
-1
@@ -394,5 +394,5 @@ homepage:
|
||||
timezone: 'Asia/Tokyo'
|
||||
pagerduty:
|
||||
eventsBaseUrl: 'https://events.pagerduty.com/v2'
|
||||
splunkoncall:
|
||||
splunkOnCall:
|
||||
username: 'guest'
|
||||
|
||||
@@ -54,7 +54,7 @@ The user supplied must be a valid Splunk On-Call user and a member of your organ
|
||||
In `app-config.yaml`:
|
||||
|
||||
```yaml
|
||||
splunkoncall:
|
||||
splunkOnCall:
|
||||
username: <SPLUNK_ON_CALL_USERNAME>
|
||||
```
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ export const splunkOnCallApiRef = createApiRef<SplunkOnCallApi>({
|
||||
export class SplunkOnCallClient implements SplunkOnCallApi {
|
||||
static fromConfig(configApi: ConfigApi, discoveryApi: DiscoveryApi) {
|
||||
const usernameFromConfig: string | null =
|
||||
configApi.getOptionalString('splunkoncall.username') || 'ayshiff';
|
||||
configApi.getOptionalString('splunkoncall.username') || null;
|
||||
return new SplunkOnCallClient({
|
||||
username: usernameFromConfig,
|
||||
discoveryApi,
|
||||
@@ -52,10 +52,6 @@ export class SplunkOnCallClient implements SplunkOnCallApi {
|
||||
}
|
||||
constructor(private readonly config: ClientApiConfig) {}
|
||||
|
||||
getUsername(): string | undefined {
|
||||
return this.config.username as string;
|
||||
}
|
||||
|
||||
async getIncidents(): Promise<Incident[]> {
|
||||
const url = `${await this.config.discoveryApi.getBaseUrl(
|
||||
'proxy',
|
||||
@@ -85,7 +81,6 @@ export class SplunkOnCallClient implements SplunkOnCallApi {
|
||||
}
|
||||
|
||||
async acknowledgeIncident({
|
||||
userName,
|
||||
incidentNames,
|
||||
}: PatchIncidentRequest): Promise<Response> {
|
||||
const options = {
|
||||
@@ -95,7 +90,7 @@ export class SplunkOnCallClient implements SplunkOnCallApi {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
userName: this.config.username || userName,
|
||||
userName: this.config.username,
|
||||
incidentNames,
|
||||
}),
|
||||
};
|
||||
@@ -108,7 +103,6 @@ export class SplunkOnCallClient implements SplunkOnCallApi {
|
||||
}
|
||||
|
||||
async resolveIncident({
|
||||
userName,
|
||||
incidentNames,
|
||||
}: PatchIncidentRequest): Promise<Response> {
|
||||
const options = {
|
||||
@@ -118,7 +112,7 @@ export class SplunkOnCallClient implements SplunkOnCallApi {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
userName: this.config.username || userName,
|
||||
userName: this.config.username,
|
||||
incidentNames,
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -83,15 +83,9 @@ export interface SplunkOnCallApi {
|
||||
* Get a list of escalation policies for your organization.
|
||||
*/
|
||||
getEscalationPolicies(): Promise<EscalationPolicyInfo[]>;
|
||||
|
||||
/**
|
||||
* Get the current user username.
|
||||
*/
|
||||
getUsername(): string | undefined;
|
||||
}
|
||||
|
||||
export type PatchIncidentRequest = {
|
||||
userName: string;
|
||||
incidentNames: string[];
|
||||
message?: string;
|
||||
};
|
||||
|
||||
@@ -94,13 +94,11 @@ const incidentPhaseTooltip = (currentPhase: IncidentPhase) => {
|
||||
|
||||
const IncidentAction = ({
|
||||
currentPhase,
|
||||
userName,
|
||||
incidentNames,
|
||||
resolveAction,
|
||||
aknowledgeAction,
|
||||
}: {
|
||||
currentPhase: string;
|
||||
userName: string;
|
||||
incidentNames: string[];
|
||||
resolveAction: (args: PatchIncidentRequest) => void;
|
||||
aknowledgeAction: (args: PatchIncidentRequest) => void;
|
||||
@@ -109,9 +107,7 @@ const IncidentAction = ({
|
||||
case 'UNACKED':
|
||||
return (
|
||||
<Tooltip title="Aknowledge" placement="top">
|
||||
<IconButton
|
||||
onClick={() => aknowledgeAction({ userName, incidentNames })}
|
||||
>
|
||||
<IconButton onClick={() => aknowledgeAction({ incidentNames })}>
|
||||
<DoneIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
@@ -119,9 +115,7 @@ const IncidentAction = ({
|
||||
case 'ACKED':
|
||||
return (
|
||||
<Tooltip title="Resolve" placement="top">
|
||||
<IconButton
|
||||
onClick={() => resolveAction({ userName, incidentNames })}
|
||||
>
|
||||
<IconButton onClick={() => resolveAction({ incidentNames })}>
|
||||
<DoneAllIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
@@ -135,8 +129,6 @@ 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-');
|
||||
@@ -149,9 +141,8 @@ export const IncidentListItem = ({ incident, onIncidentAction }: Props) => {
|
||||
{ value: resolveValue, loading: _resolveLoading, error: resolveError },
|
||||
handleResolveIncident,
|
||||
] = useAsyncFn(
|
||||
async ({ userName, incidentNames }: PatchIncidentRequest) =>
|
||||
async ({ incidentNames }: PatchIncidentRequest) =>
|
||||
await api.resolveIncident({
|
||||
userName,
|
||||
incidentNames,
|
||||
}),
|
||||
);
|
||||
@@ -164,9 +155,8 @@ export const IncidentListItem = ({ incident, onIncidentAction }: Props) => {
|
||||
},
|
||||
handleAcknowledgeIncident,
|
||||
] = useAsyncFn(
|
||||
async ({ userName, incidentNames }: PatchIncidentRequest) =>
|
||||
async ({ incidentNames }: PatchIncidentRequest) =>
|
||||
await api.acknowledgeIncident({
|
||||
userName,
|
||||
incidentNames,
|
||||
}),
|
||||
);
|
||||
@@ -230,7 +220,6 @@ export const IncidentListItem = ({ incident, onIncidentAction }: Props) => {
|
||||
{incident.incidentLink && incident.incidentNumber && (
|
||||
<ListItemSecondaryAction>
|
||||
<IncidentAction
|
||||
userName={userName}
|
||||
currentPhase={incident.currentPhase || ''}
|
||||
incidentNames={[incident.incidentNumber]}
|
||||
resolveAction={handleResolveIncident}
|
||||
|
||||
@@ -59,7 +59,7 @@ const entity: Entity = {
|
||||
metadata: {
|
||||
name: 'splunkoncall-test',
|
||||
annotations: {
|
||||
'splunkoncall.com/integration-key': 'abc123',
|
||||
'splunk-on-call.com/team': 'Example',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -14,7 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { useApi, Progress, HeaderIconLinkRow } from '@backstage/core';
|
||||
import {
|
||||
useApi,
|
||||
Progress,
|
||||
HeaderIconLinkRow,
|
||||
MissingAnnotationEmptyState,
|
||||
configApiRef,
|
||||
EmptyState,
|
||||
} from '@backstage/core';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import {
|
||||
Button,
|
||||
@@ -53,6 +60,20 @@ const useStyles = makeStyles({
|
||||
|
||||
export const SPLUNK_ON_CALL_TEAM = 'splunk-on-call.com/team';
|
||||
|
||||
export const MissingTeamAnnotation = () => (
|
||||
<MissingAnnotationEmptyState annotation={SPLUNK_ON_CALL_TEAM} />
|
||||
);
|
||||
|
||||
export const MissingUsername = () => (
|
||||
<CardContent>
|
||||
<EmptyState
|
||||
title="No Splunk On-Call user available."
|
||||
missing="info"
|
||||
description="You need to add a username to your 'app.config.yml' if you want to enable Splunk On-Call."
|
||||
/>
|
||||
</CardContent>
|
||||
);
|
||||
|
||||
export const isPluginApplicableToEntity = (entity: Entity) =>
|
||||
Boolean(entity.metadata.annotations?.[SPLUNK_ON_CALL_TEAM]);
|
||||
|
||||
@@ -62,11 +83,14 @@ type Props = {
|
||||
|
||||
export const SplunkOnCallCard = ({ entity }: Props) => {
|
||||
const classes = useStyles();
|
||||
const config = useApi(configApiRef);
|
||||
const api = useApi(splunkOnCallApiRef);
|
||||
const [showDialog, setShowDialog] = useState<boolean>(false);
|
||||
const [refreshIncidents, setRefreshIncidents] = useState<boolean>(false);
|
||||
const team = entity.metadata.annotations![SPLUNK_ON_CALL_TEAM];
|
||||
|
||||
const username = config.getOptionalString('splunkOnCall.username');
|
||||
|
||||
const handleRefresh = useCallback(() => {
|
||||
setRefreshIncidents(x => !x);
|
||||
}, []);
|
||||
@@ -89,6 +113,9 @@ export const SplunkOnCallCard = ({ entity }: Props) => {
|
||||
return { usersHashMap, userList: users };
|
||||
});
|
||||
|
||||
const incidentCreator =
|
||||
username && users?.userList.find(user => user.username === username);
|
||||
|
||||
if (error instanceof UnauthorizedError) {
|
||||
return <MissingTokenError />;
|
||||
}
|
||||
@@ -105,6 +132,33 @@ export const SplunkOnCallCard = ({ entity }: Props) => {
|
||||
return <Progress />;
|
||||
}
|
||||
|
||||
const Content = () => {
|
||||
if (!team) {
|
||||
return <MissingTeamAnnotation />;
|
||||
}
|
||||
if (!username) {
|
||||
return <MissingUsername />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Incidents team={team} refreshIncidents={refreshIncidents} />
|
||||
{users?.usersHashMap && team && (
|
||||
<EscalationPolicy team={team} users={users.usersHashMap} />
|
||||
)}
|
||||
{users && incidentCreator && (
|
||||
<TriggerDialog
|
||||
users={users.userList}
|
||||
incidentCreator={incidentCreator}
|
||||
showDialog={showDialog}
|
||||
handleDialog={handleDialog}
|
||||
onIncidentCreated={handleRefresh}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const triggerLink = {
|
||||
label: 'Create Incident',
|
||||
action: (
|
||||
@@ -126,23 +180,14 @@ export const SplunkOnCallCard = ({ entity }: Props) => {
|
||||
title="Splunk On-Call"
|
||||
subheader={[
|
||||
<Typography key="team_name">Team: {team}</Typography>,
|
||||
<HeaderIconLinkRow key="incident_trigger" links={[triggerLink]} />,
|
||||
username && (
|
||||
<HeaderIconLinkRow key="incident_trigger" links={[triggerLink]} />
|
||||
),
|
||||
]}
|
||||
/>
|
||||
<Divider />
|
||||
<CardContent>
|
||||
<Incidents team={team} refreshIncidents={refreshIncidents} />
|
||||
{users?.usersHashMap && (
|
||||
<EscalationPolicy team={team} users={users.usersHashMap} />
|
||||
)}
|
||||
{users && (
|
||||
<TriggerDialog
|
||||
users={users.userList}
|
||||
showDialog={showDialog}
|
||||
handleDialog={handleDialog}
|
||||
onIncidentCreated={handleRefresh}
|
||||
/>
|
||||
)}
|
||||
<Content />
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -34,8 +34,6 @@ import {
|
||||
FormControl,
|
||||
InputLabel,
|
||||
} from '@material-ui/core';
|
||||
import Card from '@material-ui/core/Card';
|
||||
import CardContent from '@material-ui/core/CardContent';
|
||||
import { useApi, alertApiRef, identityApiRef } from '@backstage/core';
|
||||
import { useAsync, useAsyncFn } from 'react-use';
|
||||
import { splunkOnCallApiRef } from '../../api';
|
||||
@@ -52,8 +50,8 @@ const MenuProps = {
|
||||
};
|
||||
|
||||
type Props = {
|
||||
// name: string;
|
||||
users: User[];
|
||||
incidentCreator: User;
|
||||
showDialog: boolean;
|
||||
handleDialog: () => void;
|
||||
onIncidentCreated: () => void;
|
||||
@@ -82,6 +80,7 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
|
||||
export const TriggerDialog = ({
|
||||
users,
|
||||
incidentCreator,
|
||||
showDialog,
|
||||
handleDialog,
|
||||
onIncidentCreated: onIncidentCreated,
|
||||
@@ -118,10 +117,6 @@ export const TriggerDialog = ({
|
||||
}),
|
||||
);
|
||||
|
||||
const username = api.getUsername();
|
||||
|
||||
const currentUser = users.find(user => user.username === username);
|
||||
|
||||
const {
|
||||
value: policies,
|
||||
loading: policiesLoaading,
|
||||
@@ -187,7 +182,7 @@ export const TriggerDialog = ({
|
||||
<Typography variant="subtitle1" gutterBottom align="justify">
|
||||
Created by:{' '}
|
||||
<b>
|
||||
{currentUser?.firstName} {currentUser?.lastName}
|
||||
{incidentCreator?.firstName} {incidentCreator?.lastName}
|
||||
</b>
|
||||
</Typography>
|
||||
<Alert severity="info">
|
||||
@@ -257,7 +252,9 @@ export const TriggerDialog = ({
|
||||
value={policyTargets}
|
||||
onChange={handlePolicyTargets}
|
||||
input={<Input />}
|
||||
inputProps={{ 'data-testid': 'trigger-select-policies-target' }}
|
||||
inputProps={{
|
||||
'data-testid': 'trigger-select-policies-target',
|
||||
}}
|
||||
renderValue={selected => (
|
||||
<div className={classes.chips}>
|
||||
{(selected as string[]).map(value => {
|
||||
|
||||
Reference in New Issue
Block a user