feat(splunk-on-call-plugin): add REST endpoint
Signed-off-by: Remi <remi.d45@gmail.com>
This commit is contained in:
@@ -56,6 +56,7 @@ In `app-config.yaml`:
|
||||
```yaml
|
||||
splunkOnCall:
|
||||
username: <SPLUNK_ON_CALL_USERNAME>
|
||||
eventsRestEndpoint: <SPLUNK_ON_CALL_REST_ENDPOINT>
|
||||
```
|
||||
|
||||
The user supplied must be a valid Splunk On-Call user and a member of your organization.
|
||||
|
||||
@@ -31,7 +31,6 @@ import {
|
||||
RequestOptions,
|
||||
ListUserResponse,
|
||||
EscalationPolicyResponse,
|
||||
PatchIncidentRequest,
|
||||
} from './types';
|
||||
|
||||
export class UnauthorizedError extends Error {}
|
||||
@@ -45,8 +44,11 @@ export class SplunkOnCallClient implements SplunkOnCallApi {
|
||||
static fromConfig(configApi: ConfigApi, discoveryApi: DiscoveryApi) {
|
||||
const usernameFromConfig: string | null =
|
||||
configApi.getOptionalString('splunkOnCall.username') || null;
|
||||
const eventsRestEndpoint: string | null =
|
||||
configApi.getOptionalString('splunkOnCall.eventsRestEndpoint') || null;
|
||||
return new SplunkOnCallClient({
|
||||
username: usernameFromConfig,
|
||||
eventsRestEndpoint,
|
||||
discoveryApi,
|
||||
});
|
||||
}
|
||||
@@ -80,50 +82,6 @@ export class SplunkOnCallClient implements SplunkOnCallApi {
|
||||
return teams;
|
||||
}
|
||||
|
||||
async acknowledgeIncident({
|
||||
incidentNames,
|
||||
}: PatchIncidentRequest): Promise<Response> {
|
||||
const options = {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
userName: this.config.username,
|
||||
incidentNames,
|
||||
}),
|
||||
};
|
||||
|
||||
const url = `${await this.config.discoveryApi.getBaseUrl(
|
||||
'proxy',
|
||||
)}/splunk-on-call/v1/incidents/ack`;
|
||||
|
||||
return this.request(url, options);
|
||||
}
|
||||
|
||||
async resolveIncident({
|
||||
incidentNames,
|
||||
}: PatchIncidentRequest): Promise<Response> {
|
||||
const options = {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
userName: this.config.username,
|
||||
incidentNames,
|
||||
}),
|
||||
};
|
||||
|
||||
const url = `${await this.config.discoveryApi.getBaseUrl(
|
||||
'proxy',
|
||||
)}/splunk-on-call/v1/incidents/resolve`;
|
||||
|
||||
return this.request(url, options);
|
||||
}
|
||||
|
||||
async getUsers(): Promise<User[]> {
|
||||
const url = `${await this.config.discoveryApi.getBaseUrl(
|
||||
'proxy',
|
||||
@@ -142,19 +100,22 @@ export class SplunkOnCallClient implements SplunkOnCallApi {
|
||||
return policies;
|
||||
}
|
||||
|
||||
async triggerAlarm({
|
||||
summary,
|
||||
details,
|
||||
userName,
|
||||
targets,
|
||||
isMultiResponder,
|
||||
async incidentAction({
|
||||
routingKey,
|
||||
incidentType,
|
||||
incidentId,
|
||||
incidentDisplayName,
|
||||
incidentMessage,
|
||||
incidentStartTime,
|
||||
}: TriggerAlarmRequest): Promise<Response> {
|
||||
const body = JSON.stringify({
|
||||
summary,
|
||||
details,
|
||||
userName: this.config.username || userName,
|
||||
targets,
|
||||
isMultiResponder,
|
||||
message_type: incidentType,
|
||||
...(incidentId ? { entity_id: incidentId } : {}),
|
||||
...(incidentDisplayName
|
||||
? { entity_display_name: incidentDisplayName }
|
||||
: {}),
|
||||
...(incidentMessage ? { state_message: incidentMessage } : {}),
|
||||
...(incidentStartTime ? { state_start_time: incidentStartTime } : {}),
|
||||
});
|
||||
|
||||
const options = {
|
||||
@@ -166,9 +127,7 @@ export class SplunkOnCallClient implements SplunkOnCallApi {
|
||||
body,
|
||||
};
|
||||
|
||||
const url = `${await this.config.discoveryApi.getBaseUrl(
|
||||
'proxy',
|
||||
)}/splunk-on-call/v1/incidents`;
|
||||
const url = `${this.config.eventsRestEndpoint}/${routingKey}`;
|
||||
|
||||
return this.request(url, options);
|
||||
}
|
||||
|
||||
@@ -23,22 +23,20 @@ import {
|
||||
} from '../components/types';
|
||||
import { DiscoveryApi } from '@backstage/core';
|
||||
|
||||
export enum TargetType {
|
||||
UserValue = 'User',
|
||||
EscalationPolicyValue = 'EscalationPolicy',
|
||||
}
|
||||
|
||||
export type IncidentTarget = {
|
||||
type: TargetType;
|
||||
slug: string;
|
||||
};
|
||||
export type MessageType =
|
||||
| 'CRITICAL'
|
||||
| 'WARNING'
|
||||
| 'ACKNOWLEDGEMENT'
|
||||
| 'INFO'
|
||||
| 'RECOVERY';
|
||||
|
||||
export type TriggerAlarmRequest = {
|
||||
targets: IncidentTarget[];
|
||||
details: string;
|
||||
summary: string;
|
||||
userName: string;
|
||||
isMultiResponder?: boolean;
|
||||
routingKey?: string;
|
||||
incidentType: MessageType;
|
||||
incidentId?: string;
|
||||
incidentDisplayName?: string;
|
||||
incidentMessage?: string;
|
||||
incidentStartTime?: number;
|
||||
};
|
||||
|
||||
export interface SplunkOnCallApi {
|
||||
@@ -53,19 +51,9 @@ export interface SplunkOnCallApi {
|
||||
getOnCallUsers(): Promise<OnCall[]>;
|
||||
|
||||
/**
|
||||
* Triggers an incident to specific users and/or specific teams.
|
||||
* Triggers-Resolves-Acknowledge an incident.
|
||||
*/
|
||||
triggerAlarm(request: TriggerAlarmRequest): Promise<Response>;
|
||||
|
||||
/**
|
||||
* Resolves an incident.
|
||||
*/
|
||||
resolveIncident(request: PatchIncidentRequest): Promise<Response>;
|
||||
|
||||
/**
|
||||
* Acknowledge an incident.
|
||||
*/
|
||||
acknowledgeIncident(request: PatchIncidentRequest): Promise<Response>;
|
||||
incidentAction(request: TriggerAlarmRequest): Promise<Response>;
|
||||
|
||||
/**
|
||||
* Get a list of users for your organization.
|
||||
@@ -83,11 +71,6 @@ export interface SplunkOnCallApi {
|
||||
getEscalationPolicies(): Promise<EscalationPolicyInfo[]>;
|
||||
}
|
||||
|
||||
export type PatchIncidentRequest = {
|
||||
incidentNames: string[];
|
||||
message?: string;
|
||||
};
|
||||
|
||||
export type EscalationPolicyResponse = {
|
||||
policies: EscalationPolicyInfo[];
|
||||
};
|
||||
@@ -107,6 +90,7 @@ export type OnCallsResponse = {
|
||||
|
||||
export type ClientApiConfig = {
|
||||
username: string | null;
|
||||
eventsRestEndpoint: string | null;
|
||||
discoveryApi: DiscoveryApi;
|
||||
};
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ import { Incident, IncidentPhase } from '../types';
|
||||
import OpenInBrowserIcon from '@material-ui/icons/OpenInBrowser';
|
||||
import { splunkOnCallApiRef } from '../../api/client';
|
||||
import { useAsyncFn } from 'react-use';
|
||||
import { PatchIncidentRequest } from '../../api/types';
|
||||
import { TriggerAlarmRequest } from '../../api/types';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
denseListIcon: {
|
||||
@@ -61,6 +61,7 @@ const useStyles = makeStyles({
|
||||
});
|
||||
|
||||
type Props = {
|
||||
team: string;
|
||||
incident: Incident;
|
||||
onIncidentAction: () => void;
|
||||
};
|
||||
@@ -93,20 +94,24 @@ const incidentPhaseTooltip = (currentPhase: IncidentPhase) => {
|
||||
|
||||
const IncidentAction = ({
|
||||
currentPhase,
|
||||
incidentNames,
|
||||
incidentId,
|
||||
resolveAction,
|
||||
acknowledgeAction,
|
||||
}: {
|
||||
currentPhase: string;
|
||||
incidentNames: string[];
|
||||
resolveAction: (args: PatchIncidentRequest) => void;
|
||||
acknowledgeAction: (args: PatchIncidentRequest) => void;
|
||||
incidentId: string;
|
||||
resolveAction: (args: TriggerAlarmRequest) => void;
|
||||
acknowledgeAction: (args: TriggerAlarmRequest) => void;
|
||||
}) => {
|
||||
switch (currentPhase) {
|
||||
case 'UNACKED':
|
||||
return (
|
||||
<Tooltip title="Aknowledge" placement="top">
|
||||
<IconButton onClick={() => acknowledgeAction({ incidentNames })}>
|
||||
<IconButton
|
||||
onClick={() =>
|
||||
acknowledgeAction({ incidentId, incidentType: 'ACKNOWLEDGEMENT' })
|
||||
}
|
||||
>
|
||||
<DoneIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
@@ -114,7 +119,11 @@ const IncidentAction = ({
|
||||
case 'ACKED':
|
||||
return (
|
||||
<Tooltip title="Resolve" placement="top">
|
||||
<IconButton onClick={() => resolveAction({ incidentNames })}>
|
||||
<IconButton
|
||||
onClick={() =>
|
||||
resolveAction({ incidentId, incidentType: 'RECOVERY' })
|
||||
}
|
||||
>
|
||||
<DoneAllIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
@@ -124,7 +133,11 @@ const IncidentAction = ({
|
||||
}
|
||||
};
|
||||
|
||||
export const IncidentListItem = ({ incident, onIncidentAction }: Props) => {
|
||||
export const IncidentListItem = ({
|
||||
incident,
|
||||
onIncidentAction,
|
||||
team,
|
||||
}: Props) => {
|
||||
const classes = useStyles();
|
||||
const duration =
|
||||
new Date().getTime() - new Date(incident.startTime!).getTime();
|
||||
@@ -136,17 +149,26 @@ export const IncidentListItem = ({ incident, onIncidentAction }: Props) => {
|
||||
|
||||
const hasBeenManuallyTriggered = incident.monitorName?.includes('vouser-');
|
||||
|
||||
const user = hasBeenManuallyTriggered
|
||||
? incident.monitorName?.replace('vouser-', '')
|
||||
: incident.monitorName;
|
||||
const source = () => {
|
||||
if (hasBeenManuallyTriggered) {
|
||||
return incident.monitorName?.replace('vouser-', '');
|
||||
}
|
||||
if (incident.monitorType === 'API') {
|
||||
return '{ REST }';
|
||||
}
|
||||
|
||||
return incident.monitorName;
|
||||
};
|
||||
|
||||
const [
|
||||
{ value: resolveValue, error: resolveError },
|
||||
handleResolveIncident,
|
||||
] = useAsyncFn(
|
||||
async ({ incidentNames }: PatchIncidentRequest) =>
|
||||
await api.resolveIncident({
|
||||
incidentNames,
|
||||
async ({ incidentId, incidentType }: TriggerAlarmRequest) =>
|
||||
await api.incidentAction({
|
||||
routingKey: team,
|
||||
incidentType,
|
||||
incidentId,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -154,9 +176,11 @@ export const IncidentListItem = ({ incident, onIncidentAction }: Props) => {
|
||||
{ value: acknowledgeValue, error: acknowledgeError },
|
||||
handleAcknowledgeIncident,
|
||||
] = useAsyncFn(
|
||||
async ({ incidentNames }: PatchIncidentRequest) =>
|
||||
await api.acknowledgeIncident({
|
||||
incidentNames,
|
||||
async ({ incidentId, incidentType }: TriggerAlarmRequest) =>
|
||||
await api.incidentAction({
|
||||
routingKey: team,
|
||||
incidentType,
|
||||
incidentId,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -211,7 +235,8 @@ export const IncidentListItem = ({ incident, onIncidentAction }: Props) => {
|
||||
}}
|
||||
secondary={
|
||||
<Typography noWrap variant="body2" color="textSecondary">
|
||||
Created {createdAt} {user && `by ${user}`}
|
||||
#{incident.incidentNumber} - Created {createdAt}{' '}
|
||||
{source() && `by ${source()}`}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
@@ -220,7 +245,7 @@ export const IncidentListItem = ({ incident, onIncidentAction }: Props) => {
|
||||
<ListItemSecondaryAction>
|
||||
<IncidentAction
|
||||
currentPhase={incident.currentPhase || ''}
|
||||
incidentNames={[incident.incidentNumber]}
|
||||
incidentId={incident.entityId}
|
||||
resolveAction={handleResolveIncident}
|
||||
acknowledgeAction={handleAcknowledgeIncident}
|
||||
/>
|
||||
|
||||
@@ -33,6 +33,10 @@ export const Incidents = ({ refreshIncidents, team }: Props) => {
|
||||
|
||||
const [{ value: incidents, loading, error }, getIncidents] = useAsyncFn(
|
||||
async () => {
|
||||
// For some reason the changes applied to incidents (trigger-resolve-acknowledge)
|
||||
// may take some time to actually be applied after receiving the response from the server.
|
||||
// The timeout compensates for this latency.
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
const allIncidents = await api.getIncidents();
|
||||
const teams = await api.getTeams();
|
||||
const teamSlug = teams.find(teamValue => teamValue.name === team)?.slug;
|
||||
@@ -71,6 +75,7 @@ export const Incidents = ({ refreshIncidents, team }: Props) => {
|
||||
<IncidentListItem
|
||||
onIncidentAction={() => getIncidents()}
|
||||
key={index}
|
||||
team={team}
|
||||
incident={incident}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -74,6 +74,16 @@ export const MissingUsername = () => (
|
||||
</CardContent>
|
||||
);
|
||||
|
||||
export const MissingEventsRestEndpoint = () => (
|
||||
<CardContent>
|
||||
<EmptyState
|
||||
title="No Splunk On-Call REST endpoint available."
|
||||
missing="info"
|
||||
description="You need to add a valid REST endpoint to your 'app-config.yaml' if you want to enable Splunk On-Call."
|
||||
/>
|
||||
</CardContent>
|
||||
);
|
||||
|
||||
export const isPluginApplicableToEntity = (entity: Entity) =>
|
||||
Boolean(entity.metadata.annotations?.[SPLUNK_ON_CALL_TEAM]);
|
||||
|
||||
@@ -89,7 +99,10 @@ export const SplunkOnCallCard = ({ entity }: Props) => {
|
||||
const [refreshIncidents, setRefreshIncidents] = useState<boolean>(false);
|
||||
const team = entity.metadata.annotations![SPLUNK_ON_CALL_TEAM];
|
||||
|
||||
const username = config.getOptionalString('splunkOnCall.username');
|
||||
const username = config.getOptionalString('splunkOnCall.username') || null;
|
||||
|
||||
const eventsRestEndpoint =
|
||||
config.getOptionalString('splunkOnCall.eventsRestEndpoint') || null;
|
||||
|
||||
const handleRefresh = useCallback(() => {
|
||||
setRefreshIncidents(x => !x);
|
||||
@@ -140,15 +153,19 @@ export const SplunkOnCallCard = ({ entity }: Props) => {
|
||||
return <MissingUsername />;
|
||||
}
|
||||
|
||||
if (!eventsRestEndpoint) {
|
||||
return <MissingEventsRestEndpoint />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Incidents team={team} refreshIncidents={refreshIncidents} />
|
||||
{users?.usersHashMap && team && (
|
||||
<EscalationPolicy team={team} users={users.usersHashMap} />
|
||||
)}
|
||||
{users && incidentCreator && (
|
||||
{incidentCreator && (
|
||||
<TriggerDialog
|
||||
users={users.userList}
|
||||
team={team}
|
||||
incidentCreator={incidentCreator}
|
||||
showDialog={showDialog}
|
||||
handleDialog={handleDialog}
|
||||
|
||||
@@ -26,8 +26,6 @@ import {
|
||||
CircularProgress,
|
||||
Select,
|
||||
MenuItem,
|
||||
Input,
|
||||
Chip,
|
||||
createStyles,
|
||||
makeStyles,
|
||||
Theme,
|
||||
@@ -35,22 +33,14 @@ import {
|
||||
InputLabel,
|
||||
} from '@material-ui/core';
|
||||
import { useApi, alertApiRef } from '@backstage/core';
|
||||
import { useAsync, useAsyncFn } from 'react-use';
|
||||
import { useAsyncFn } from 'react-use';
|
||||
import { splunkOnCallApiRef } from '../../api';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import { User } from '../types';
|
||||
import { IncidentTarget, TargetType } from '../../api/types';
|
||||
|
||||
const MenuProps = {
|
||||
PaperProps: {
|
||||
style: {
|
||||
width: 250,
|
||||
},
|
||||
},
|
||||
};
|
||||
import { TriggerAlarmRequest } from '../../api/types';
|
||||
|
||||
type Props = {
|
||||
users: User[];
|
||||
team: string;
|
||||
incidentCreator: User;
|
||||
showDialog: boolean;
|
||||
handleDialog: () => void;
|
||||
@@ -79,7 +69,7 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
);
|
||||
|
||||
export const TriggerDialog = ({
|
||||
users,
|
||||
team,
|
||||
incidentCreator,
|
||||
showDialog,
|
||||
handleDialog,
|
||||
@@ -89,72 +79,46 @@ export const TriggerDialog = ({
|
||||
const api = useApi(splunkOnCallApiRef);
|
||||
const classes = useStyles();
|
||||
|
||||
const [userTargets, setUserTargets] = useState<string[]>([]);
|
||||
const [policyTargets, setPolicyTargets] = useState<string[]>([]);
|
||||
const [detailsValue, setDetails] = useState<string>('');
|
||||
const [summaryValue, setSummary] = useState<string>('');
|
||||
const [isMultiResponderValue, setIsMultiResponder] = useState<string>('1');
|
||||
const [incidentType, setIncidentType] = useState<string>('');
|
||||
const [incidentId, setIncidentId] = useState<string>();
|
||||
const [incidentDisplayName, setIncidentDisplayName] = useState<string>('');
|
||||
const [incidentMessage, setIncidentMessage] = useState<string>('');
|
||||
const [incidentStartTime, setIncidentStartTime] = useState<number>();
|
||||
|
||||
const [
|
||||
{ value, loading: triggerLoading, error: triggerError },
|
||||
handleTriggerAlarm,
|
||||
] = useAsyncFn(
|
||||
async (
|
||||
summary: string,
|
||||
details: string,
|
||||
userName: string,
|
||||
targets: IncidentTarget[],
|
||||
isMultiResponder: boolean,
|
||||
) =>
|
||||
await api.triggerAlarm({
|
||||
summary,
|
||||
details,
|
||||
userName,
|
||||
targets,
|
||||
isMultiResponder,
|
||||
}),
|
||||
async (params: TriggerAlarmRequest) => await api.incidentAction(params),
|
||||
);
|
||||
|
||||
const {
|
||||
value: policies,
|
||||
loading: policiesLoaading,
|
||||
error: policiesError,
|
||||
} = useAsync(async () => {
|
||||
const allPolicies = await api.getEscalationPolicies();
|
||||
return allPolicies;
|
||||
});
|
||||
|
||||
const handleUserTargets = (event: React.ChangeEvent<{ value: unknown }>) => {
|
||||
setUserTargets(event.target.value as string[]);
|
||||
const handleIncidentType = (event: React.ChangeEvent<{ value: unknown }>) => {
|
||||
setIncidentType(event.target.value as string);
|
||||
};
|
||||
|
||||
const handlePolicyTargets = (
|
||||
event: React.ChangeEvent<{ value: unknown }>,
|
||||
const handleIncidentId = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setIncidentId(event.target.value as string);
|
||||
};
|
||||
|
||||
const handleIncidentDisplayName = (
|
||||
event: React.ChangeEvent<HTMLTextAreaElement>,
|
||||
) => {
|
||||
setPolicyTargets(event.target.value as string[]);
|
||||
setIncidentDisplayName(event.target.value);
|
||||
};
|
||||
|
||||
const detailsChanged = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setDetails(event.target.value);
|
||||
};
|
||||
|
||||
const summaryChanged = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setSummary(event.target.value);
|
||||
};
|
||||
|
||||
const isMultiResponderChanged = (
|
||||
event: React.ChangeEvent<{ value: unknown }>,
|
||||
const handleIncidentMessage = (
|
||||
event: React.ChangeEvent<HTMLTextAreaElement>,
|
||||
) => {
|
||||
setIsMultiResponder(event.target.value as string);
|
||||
setIncidentMessage(event.target.value);
|
||||
};
|
||||
|
||||
const targets = (): IncidentTarget[] => [
|
||||
...userTargets.map(user => ({ slug: user, type: TargetType.UserValue })),
|
||||
...policyTargets.map(user => ({
|
||||
slug: user,
|
||||
type: TargetType.EscalationPolicyValue,
|
||||
})),
|
||||
];
|
||||
const handleIncidentStartTime = (
|
||||
event: React.ChangeEvent<HTMLTextAreaElement>,
|
||||
) => {
|
||||
const dateTime = new Date(event.target.value).getTime();
|
||||
const dateTimeInSeconds = Math.floor(dateTime / 1000);
|
||||
setIncidentStartTime(dateTimeInSeconds);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (value) {
|
||||
@@ -203,132 +167,58 @@ export const TriggerDialog = ({
|
||||
automatically be amended to the alarm so that the receiver can reach
|
||||
out to you if necessary.
|
||||
</Typography>
|
||||
<div style={{ marginTop: '1em' }}>
|
||||
<Typography color="textSecondary" gutterBottom>
|
||||
Select the targets
|
||||
</Typography>
|
||||
<div className={classes.targets}>
|
||||
<FormControl className={classes.formControl}>
|
||||
<InputLabel>Select Users</InputLabel>
|
||||
<Select
|
||||
id="user-targets"
|
||||
multiple
|
||||
value={userTargets}
|
||||
onChange={handleUserTargets}
|
||||
input={<Input />}
|
||||
renderValue={selected => (
|
||||
<div className={classes.chips}>
|
||||
{(selected as string[]).map(selectedUser => {
|
||||
const element = users.find(
|
||||
user => user.username === selectedUser,
|
||||
);
|
||||
return (
|
||||
<Chip
|
||||
key={selectedUser}
|
||||
label={`${element?.firstName} ${element?.lastName}`}
|
||||
className={classes.chip}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
MenuProps={MenuProps}
|
||||
>
|
||||
{users.map(user => (
|
||||
<MenuItem key={user.email} value={user.username}>
|
||||
{user.firstName} {user.lastName}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl className={classes.formControl}>
|
||||
<InputLabel>Select Teams / Policies</InputLabel>
|
||||
<Select
|
||||
id="policy-targets"
|
||||
multiple
|
||||
value={policyTargets}
|
||||
onChange={handlePolicyTargets}
|
||||
input={<Input />}
|
||||
renderValue={selected => (
|
||||
<div className={classes.chips}>
|
||||
{(selected as string[]).map(selectedPolicy => {
|
||||
const element = policies?.find(
|
||||
policy => policy.policy.slug === selectedPolicy,
|
||||
);
|
||||
return (
|
||||
<Chip
|
||||
key={selectedPolicy}
|
||||
label={element?.policy.name}
|
||||
className={classes.chip}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
MenuProps={MenuProps}
|
||||
>
|
||||
{!policiesError &&
|
||||
!policiesLoaading &&
|
||||
policies &&
|
||||
policies.map(policy => (
|
||||
<MenuItem
|
||||
key={policy.policy.slug}
|
||||
value={policy.policy.slug}
|
||||
>
|
||||
{policy.policy.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
<Typography
|
||||
style={{ marginTop: '1em' }}
|
||||
color="textSecondary"
|
||||
gutterBottom
|
||||
>
|
||||
Acknowledge Behavior
|
||||
</Typography>
|
||||
<FormControl className={classes.formControl}>
|
||||
<InputLabel id="demo-simple-select-label">Incident type</InputLabel>
|
||||
<Select
|
||||
id="multi-responder"
|
||||
value={isMultiResponderValue}
|
||||
onChange={isMultiResponderChanged}
|
||||
inputProps={{ 'data-testid': 'trigger-select-behavior' }}
|
||||
id="incident-type"
|
||||
value={incidentType}
|
||||
onChange={handleIncidentType}
|
||||
inputProps={{ 'data-testid': 'trigger-incident-type' }}
|
||||
>
|
||||
<MenuItem value="1">
|
||||
Stop paging after a single escalation policy or user has
|
||||
acknowledged
|
||||
</MenuItem>
|
||||
<MenuItem value="0">
|
||||
Continue paging until each escalation policy or user above has
|
||||
acknowledged
|
||||
</MenuItem>
|
||||
<MenuItem value="CRITICAL">Critical</MenuItem>
|
||||
<MenuItem value="WARNING">Warning</MenuItem>
|
||||
<MenuItem value="INFO">Info</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<TextField
|
||||
required
|
||||
inputProps={{ 'data-testid': 'trigger-summary-input' }}
|
||||
inputProps={{ 'data-testid': 'trigger-incident-id' }}
|
||||
id="summary"
|
||||
multiline
|
||||
fullWidth
|
||||
rows="4"
|
||||
margin="normal"
|
||||
label="Incident summary"
|
||||
label="Incident id"
|
||||
variant="outlined"
|
||||
onChange={summaryChanged}
|
||||
onChange={handleIncidentId}
|
||||
/>
|
||||
<TextField
|
||||
required
|
||||
inputProps={{ 'data-testid': 'trigger-body-input' }}
|
||||
inputProps={{ 'data-testid': 'trigger-incident-displayName' }}
|
||||
id="summary"
|
||||
fullWidth
|
||||
margin="normal"
|
||||
label="Incident display name"
|
||||
variant="outlined"
|
||||
onChange={handleIncidentDisplayName}
|
||||
/>
|
||||
<TextField
|
||||
required
|
||||
inputProps={{ 'data-testid': 'trigger-incident-message' }}
|
||||
id="details"
|
||||
multiline
|
||||
fullWidth
|
||||
rows="2"
|
||||
margin="normal"
|
||||
label="Incident body"
|
||||
label="Incident message"
|
||||
variant="outlined"
|
||||
onChange={detailsChanged}
|
||||
onChange={handleIncidentMessage}
|
||||
/>
|
||||
<TextField
|
||||
id="datetime-local"
|
||||
label="Incident start time"
|
||||
type="datetime-local"
|
||||
onChange={handleIncidentStartTime}
|
||||
InputLabelProps={{
|
||||
shrink: true,
|
||||
}}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
@@ -337,20 +227,21 @@ export const TriggerDialog = ({
|
||||
id="trigger"
|
||||
color="secondary"
|
||||
disabled={
|
||||
!detailsValue ||
|
||||
!summaryValue ||
|
||||
(!userTargets.length && !policyTargets.length) ||
|
||||
!incidentType.length ||
|
||||
!incidentDisplayName ||
|
||||
!incidentMessage ||
|
||||
triggerLoading
|
||||
}
|
||||
variant="contained"
|
||||
onClick={() =>
|
||||
handleTriggerAlarm(
|
||||
summaryValue,
|
||||
detailsValue,
|
||||
incidentCreator.username!,
|
||||
targets(),
|
||||
!!Number(isMultiResponderValue),
|
||||
)
|
||||
handleTriggerAlarm({
|
||||
routingKey: team,
|
||||
incidentType,
|
||||
incidentDisplayName,
|
||||
incidentMessage,
|
||||
...(incidentId ? { incidentId } : {}),
|
||||
...(incidentStartTime ? { incidentStartTime } : {}),
|
||||
} as TriggerAlarmRequest)
|
||||
}
|
||||
endIcon={triggerLoading && <CircularProgress size={16} />}
|
||||
>
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { IncidentTarget } from '../api/types';
|
||||
|
||||
export type Team = {
|
||||
name?: string;
|
||||
slug?: string;
|
||||
@@ -68,14 +66,6 @@ export type User = {
|
||||
_selfUrl?: string;
|
||||
};
|
||||
|
||||
export type CreateIncidentRequest = {
|
||||
summary: string;
|
||||
details: string;
|
||||
userName: string;
|
||||
targets: IncidentTarget;
|
||||
isMultiResponder: boolean;
|
||||
};
|
||||
|
||||
export type IncidentPhase = 'UNACKED' | 'ACKED' | 'RESOLVED';
|
||||
|
||||
export type Incident = {
|
||||
@@ -88,7 +78,7 @@ export type Incident = {
|
||||
alertCount?: number;
|
||||
lastAlertTime?: string;
|
||||
lastAlertId?: string;
|
||||
entityId?: string;
|
||||
entityId: string;
|
||||
host?: string;
|
||||
service?: string;
|
||||
pagedUsers?: string[];
|
||||
|
||||
Reference in New Issue
Block a user