[Uptime] simple monitor status alert fix for page duty and other connectors (#87460)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
6ec868b54e
commit
c2fc58310a
|
@ -29,4 +29,5 @@ export enum API_URLS {
|
|||
CREATE_ALERT = '/api/alerts/alert',
|
||||
ALERT = '/api/alerts/alert/',
|
||||
ALERTS_FIND = '/api/alerts/_find',
|
||||
ACTION_TYPES = '/api/actions/list_action_types',
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ export const AtomicStatusCheckParamsType = t.intersection([
|
|||
filters: StatusCheckFiltersType,
|
||||
shouldCheckStatus: t.boolean,
|
||||
isAutoGenerated: t.boolean,
|
||||
shouldCheckAvailability: t.boolean,
|
||||
}),
|
||||
]);
|
||||
|
||||
|
|
|
@ -11,6 +11,10 @@ import { EuiButtonEmpty } from '@elastic/eui';
|
|||
import { TriggersAndActionsUIPublicPluginStart } from '../../../../triggers_actions_ui/public';
|
||||
import { getConnectorsAction } from '../../state/alerts/alerts';
|
||||
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
|
||||
import { useFetcher } from '../../../../observability/public';
|
||||
import { fetchActionTypes } from '../../state/api/alerts';
|
||||
|
||||
import { ActionTypeId } from './types';
|
||||
|
||||
interface Props {
|
||||
focusInput: () => void;
|
||||
|
@ -20,6 +24,17 @@ interface KibanaDeps {
|
|||
triggersActionsUi: TriggersAndActionsUIPublicPluginStart;
|
||||
}
|
||||
|
||||
export const ALLOWED_ACTION_TYPES: ActionTypeId[] = [
|
||||
'.slack',
|
||||
'.pagerduty',
|
||||
'.server-log',
|
||||
'.index',
|
||||
'.teams',
|
||||
'.servicenow',
|
||||
'.jira',
|
||||
'.webhook',
|
||||
];
|
||||
|
||||
export const AddConnectorFlyout = ({ focusInput }: Props) => {
|
||||
const [addFlyoutVisible, setAddFlyoutVisibility] = useState<boolean>(false);
|
||||
const {
|
||||
|
@ -30,6 +45,8 @@ export const AddConnectorFlyout = ({ focusInput }: Props) => {
|
|||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { data: actionTypes } = useFetcher(() => fetchActionTypes(), []);
|
||||
|
||||
const ConnectorAddFlyout = useMemo(
|
||||
() =>
|
||||
getAddConnectorFlyout({
|
||||
|
@ -39,9 +56,12 @@ export const AddConnectorFlyout = ({ focusInput }: Props) => {
|
|||
setAddFlyoutVisibility(false);
|
||||
focusInput();
|
||||
},
|
||||
actionTypes: (actionTypes ?? []).filter((actionType) =>
|
||||
ALLOWED_ACTION_TYPES.includes(actionType.id as ActionTypeId)
|
||||
),
|
||||
}),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[]
|
||||
[actionTypes]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -19,12 +19,13 @@ import { useSelector } from 'react-redux';
|
|||
import styled from 'styled-components';
|
||||
import { SettingsFormProps } from '../../pages/settings';
|
||||
import { connectorsSelector } from '../../state/alerts/alerts';
|
||||
import { AddConnectorFlyout } from './add_connector_flyout';
|
||||
import { AddConnectorFlyout, ALLOWED_ACTION_TYPES } from './add_connector_flyout';
|
||||
import { useGetUrlParams, useUrlParams } from '../../hooks';
|
||||
import { alertFormI18n } from './translations';
|
||||
import { useInitApp } from '../../hooks/use_init_app';
|
||||
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
|
||||
import { TriggersAndActionsUIPublicPluginStart } from '../../../../triggers_actions_ui/public/';
|
||||
import { ActionTypeId } from './types';
|
||||
|
||||
type ConnectorOption = EuiComboBoxOptionOption<string>;
|
||||
|
||||
|
@ -88,11 +89,13 @@ export const AlertDefaultsForm: React.FC<SettingsFormProps> = ({
|
|||
);
|
||||
};
|
||||
|
||||
const options = (data ?? []).map((connectorAction) => ({
|
||||
value: connectorAction.id,
|
||||
label: connectorAction.name,
|
||||
'data-test-subj': connectorAction.name,
|
||||
}));
|
||||
const options = (data ?? [])
|
||||
.filter((action) => ALLOWED_ACTION_TYPES.includes(action.actionTypeId as ActionTypeId))
|
||||
.map((connectorAction) => ({
|
||||
value: connectorAction.id,
|
||||
label: connectorAction.name,
|
||||
'data-test-subj': connectorAction.name,
|
||||
}));
|
||||
|
||||
const renderOption = (option: ConnectorOption) => {
|
||||
const { label, value } = option;
|
||||
|
|
27
x-pack/plugins/uptime/public/components/settings/types.ts
Normal file
27
x-pack/plugins/uptime/public/components/settings/types.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
IndexActionTypeId,
|
||||
JiraActionTypeId,
|
||||
PagerDutyActionTypeId,
|
||||
ServerLogActionTypeId,
|
||||
ServiceNowActionTypeId,
|
||||
SlackActionTypeId,
|
||||
TeamsActionTypeId,
|
||||
WebhookActionTypeId,
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
} from '../../../../actions/server/builtin_action_types';
|
||||
|
||||
export type ActionTypeId =
|
||||
| typeof SlackActionTypeId
|
||||
| typeof PagerDutyActionTypeId
|
||||
| typeof ServerLogActionTypeId
|
||||
| typeof IndexActionTypeId
|
||||
| typeof TeamsActionTypeId
|
||||
| typeof ServiceNowActionTypeId
|
||||
| typeof JiraActionTypeId
|
||||
| typeof WebhookActionTypeId;
|
|
@ -26,9 +26,9 @@ describe('monitor status alert type', () => {
|
|||
"errors": Object {
|
||||
"typeCheckFailure": "Provided parameters do not conform to the expected type.",
|
||||
"typeCheckParsingMessage": Array [
|
||||
"Invalid value undefined supplied to : ({ numTimes: number, timerangeCount: number, timerangeUnit: string } & Partial<{ search: string, filters: { monitor.type: Array<string>, observer.geo.name: Array<string>, tags: Array<string>, url.port: Array<string> }, shouldCheckStatus: boolean, isAutoGenerated: boolean }>)/0: { numTimes: number, timerangeCount: number, timerangeUnit: string }/numTimes: number",
|
||||
"Invalid value undefined supplied to : ({ numTimes: number, timerangeCount: number, timerangeUnit: string } & Partial<{ search: string, filters: { monitor.type: Array<string>, observer.geo.name: Array<string>, tags: Array<string>, url.port: Array<string> }, shouldCheckStatus: boolean, isAutoGenerated: boolean }>)/0: { numTimes: number, timerangeCount: number, timerangeUnit: string }/timerangeCount: number",
|
||||
"Invalid value undefined supplied to : ({ numTimes: number, timerangeCount: number, timerangeUnit: string } & Partial<{ search: string, filters: { monitor.type: Array<string>, observer.geo.name: Array<string>, tags: Array<string>, url.port: Array<string> }, shouldCheckStatus: boolean, isAutoGenerated: boolean }>)/0: { numTimes: number, timerangeCount: number, timerangeUnit: string }/timerangeUnit: string",
|
||||
"Invalid value undefined supplied to : ({ numTimes: number, timerangeCount: number, timerangeUnit: string } & Partial<{ search: string, filters: { monitor.type: Array<string>, observer.geo.name: Array<string>, tags: Array<string>, url.port: Array<string> }, shouldCheckStatus: boolean, isAutoGenerated: boolean, shouldCheckAvailability: boolean }>)/0: { numTimes: number, timerangeCount: number, timerangeUnit: string }/numTimes: number",
|
||||
"Invalid value undefined supplied to : ({ numTimes: number, timerangeCount: number, timerangeUnit: string } & Partial<{ search: string, filters: { monitor.type: Array<string>, observer.geo.name: Array<string>, tags: Array<string>, url.port: Array<string> }, shouldCheckStatus: boolean, isAutoGenerated: boolean, shouldCheckAvailability: boolean }>)/0: { numTimes: number, timerangeCount: number, timerangeUnit: string }/timerangeCount: number",
|
||||
"Invalid value undefined supplied to : ({ numTimes: number, timerangeCount: number, timerangeUnit: string } & Partial<{ search: string, filters: { monitor.type: Array<string>, observer.geo.name: Array<string>, tags: Array<string>, url.port: Array<string> }, shouldCheckStatus: boolean, isAutoGenerated: boolean, shouldCheckAvailability: boolean }>)/0: { numTimes: number, timerangeCount: number, timerangeUnit: string }/timerangeUnit: string",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
@ -151,7 +151,7 @@ describe('monitor status alert type', () => {
|
|||
"errors": Object {
|
||||
"typeCheckFailure": "Provided parameters do not conform to the expected type.",
|
||||
"typeCheckParsingMessage": Array [
|
||||
"Invalid value undefined supplied to : ({ numTimes: number, timerangeCount: number, timerangeUnit: string } & Partial<{ search: string, filters: { monitor.type: Array<string>, observer.geo.name: Array<string>, tags: Array<string>, url.port: Array<string> }, shouldCheckStatus: boolean, isAutoGenerated: boolean }>)/0: { numTimes: number, timerangeCount: number, timerangeUnit: string }/numTimes: number",
|
||||
"Invalid value undefined supplied to : ({ numTimes: number, timerangeCount: number, timerangeUnit: string } & Partial<{ search: string, filters: { monitor.type: Array<string>, observer.geo.name: Array<string>, tags: Array<string>, url.port: Array<string> }, shouldCheckStatus: boolean, isAutoGenerated: boolean, shouldCheckAvailability: boolean }>)/0: { numTimes: number, timerangeCount: number, timerangeUnit: string }/numTimes: number",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
@ -164,7 +164,7 @@ describe('monitor status alert type', () => {
|
|||
"errors": Object {
|
||||
"typeCheckFailure": "Provided parameters do not conform to the expected type.",
|
||||
"typeCheckParsingMessage": Array [
|
||||
"Invalid value \\"this isn't a number\\" supplied to : ({ numTimes: number, timerangeCount: number, timerangeUnit: string } & Partial<{ search: string, filters: { monitor.type: Array<string>, observer.geo.name: Array<string>, tags: Array<string>, url.port: Array<string> }, shouldCheckStatus: boolean, isAutoGenerated: boolean }>)/0: { numTimes: number, timerangeCount: number, timerangeUnit: string }/numTimes: number",
|
||||
"Invalid value \\"this isn't a number\\" supplied to : ({ numTimes: number, timerangeCount: number, timerangeUnit: string } & Partial<{ search: string, filters: { monitor.type: Array<string>, observer.geo.name: Array<string>, tags: Array<string>, url.port: Array<string> }, shouldCheckStatus: boolean, isAutoGenerated: boolean, shouldCheckAvailability: boolean }>)/0: { numTimes: number, timerangeCount: number, timerangeUnit: string }/numTimes: number",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
|
144
x-pack/plugins/uptime/public/state/api/alert_actions.ts
Normal file
144
x-pack/plugins/uptime/public/state/api/alert_actions.ts
Normal file
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { NewAlertParams } from './alerts';
|
||||
import { AlertAction } from '../../../../triggers_actions_ui/public';
|
||||
import { ACTION_GROUP_DEFINITIONS } from '../../../common/constants/alerts';
|
||||
import { MonitorStatusTranslations } from '../../../common/translations';
|
||||
import {
|
||||
IndexActionParams,
|
||||
PagerDutyActionParams,
|
||||
ServerLogActionParams,
|
||||
ServiceNowActionParams,
|
||||
JiraActionParams,
|
||||
WebhookActionParams,
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
} from '../../../../actions/server';
|
||||
import { ActionTypeId } from '../../components/settings/types';
|
||||
|
||||
export const SLACK_ACTION_ID: ActionTypeId = '.slack';
|
||||
export const PAGER_DUTY_ACTION_ID: ActionTypeId = '.pagerduty';
|
||||
export const SERVER_LOG_ACTION_ID: ActionTypeId = '.server-log';
|
||||
export const INDEX_ACTION_ID: ActionTypeId = '.index';
|
||||
export const TEAMS_ACTION_ID: ActionTypeId = '.teams';
|
||||
export const SERVICE_NOW_ACTION_ID: ActionTypeId = '.servicenow';
|
||||
export const JIRA_ACTION_ID: ActionTypeId = '.jira';
|
||||
export const WEBHOOK_ACTION_ID: ActionTypeId = '.webhook';
|
||||
|
||||
const { MONITOR_STATUS } = ACTION_GROUP_DEFINITIONS;
|
||||
|
||||
export function populateAlertActions({ defaultActions, monitorId, monitorName }: NewAlertParams) {
|
||||
const actions: AlertAction[] = [];
|
||||
defaultActions.forEach((aId) => {
|
||||
const action: AlertAction = {
|
||||
id: aId.id,
|
||||
actionTypeId: aId.actionTypeId,
|
||||
group: MONITOR_STATUS.id,
|
||||
params: {},
|
||||
};
|
||||
switch (aId.actionTypeId) {
|
||||
case PAGER_DUTY_ACTION_ID:
|
||||
action.params = getPagerDutyActionParams(monitorId);
|
||||
break;
|
||||
case SERVER_LOG_ACTION_ID:
|
||||
action.params = getServerLogActionParams();
|
||||
break;
|
||||
case INDEX_ACTION_ID:
|
||||
action.params = getIndexActionParams();
|
||||
break;
|
||||
case SERVICE_NOW_ACTION_ID:
|
||||
action.params = getServiceNowActionParams();
|
||||
break;
|
||||
case JIRA_ACTION_ID:
|
||||
action.params = getJiraActionParams();
|
||||
break;
|
||||
case WEBHOOK_ACTION_ID:
|
||||
action.params = getWebhookActionParams();
|
||||
break;
|
||||
case SLACK_ACTION_ID:
|
||||
case TEAMS_ACTION_ID:
|
||||
default:
|
||||
action.params = {
|
||||
message: MonitorStatusTranslations.defaultActionMessage,
|
||||
};
|
||||
}
|
||||
|
||||
actions.push(action);
|
||||
});
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
function getIndexActionParams(): IndexActionParams {
|
||||
return {
|
||||
documents: [
|
||||
{
|
||||
monitorName: '{{state.monitorName}}',
|
||||
monitorUrl: '{{{state.monitorUrl}}}',
|
||||
statusMessage: '{{state.statusMessage}}',
|
||||
latestErrorMessage: '{{{state.latestErrorMessage}}}',
|
||||
observerLocation: '{{state.observerLocation}}',
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function getServerLogActionParams(): ServerLogActionParams {
|
||||
return {
|
||||
level: 'warn',
|
||||
message: MonitorStatusTranslations.defaultActionMessage,
|
||||
};
|
||||
}
|
||||
|
||||
function getWebhookActionParams(): WebhookActionParams {
|
||||
return {
|
||||
body: MonitorStatusTranslations.defaultActionMessage,
|
||||
};
|
||||
}
|
||||
|
||||
function getPagerDutyActionParams(monitorId: string): PagerDutyActionParams {
|
||||
return {
|
||||
dedupKey: monitorId + MONITOR_STATUS.id,
|
||||
eventAction: 'trigger',
|
||||
severity: 'error',
|
||||
summary: MonitorStatusTranslations.defaultActionMessage,
|
||||
};
|
||||
}
|
||||
|
||||
function getServiceNowActionParams(): ServiceNowActionParams {
|
||||
return {
|
||||
subAction: 'pushToService',
|
||||
subActionParams: {
|
||||
incident: {
|
||||
short_description: MonitorStatusTranslations.defaultActionMessage,
|
||||
description: MonitorStatusTranslations.defaultActionMessage,
|
||||
impact: '2',
|
||||
severity: '2',
|
||||
urgency: '2',
|
||||
externalId: null,
|
||||
},
|
||||
comments: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getJiraActionParams(): JiraActionParams {
|
||||
return {
|
||||
subAction: 'pushToService',
|
||||
subActionParams: {
|
||||
incident: {
|
||||
summary: MonitorStatusTranslations.defaultActionMessage,
|
||||
externalId: null,
|
||||
description: MonitorStatusTranslations.defaultActionMessage,
|
||||
issueType: null,
|
||||
priority: '2',
|
||||
labels: null,
|
||||
parent: null,
|
||||
},
|
||||
comments: [],
|
||||
},
|
||||
};
|
||||
}
|
|
@ -4,17 +4,17 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { ACTION_GROUP_DEFINITIONS, CLIENT_ALERT_TYPES } from '../../../common/constants/alerts';
|
||||
import { CLIENT_ALERT_TYPES } from '../../../common/constants/alerts';
|
||||
import { apiService } from './utils';
|
||||
import { ActionConnector } from '../alerts/alerts';
|
||||
|
||||
import { AlertsResult, MonitorIdParam } from '../actions/types';
|
||||
import { AlertAction } from '../../../../triggers_actions_ui/public';
|
||||
import { ActionType, AlertAction } from '../../../../triggers_actions_ui/public';
|
||||
import { API_URLS } from '../../../common/constants';
|
||||
import { MonitorStatusTranslations } from '../../../common/translations';
|
||||
import { Alert, AlertTypeParams } from '../../../../alerts/common';
|
||||
import { AtomicStatusCheckParams } from '../../../common/runtime_types/alerts';
|
||||
|
||||
const { MONITOR_STATUS } = ACTION_GROUP_DEFINITIONS;
|
||||
import { populateAlertActions } from './alert_actions';
|
||||
|
||||
const UPTIME_AUTO_ALERT = 'UPTIME_AUTO';
|
||||
|
||||
|
@ -28,24 +28,28 @@ export interface NewAlertParams extends AlertTypeParams {
|
|||
defaultActions: ActionConnector[];
|
||||
}
|
||||
|
||||
type NewMonitorStatusAlert = Omit<
|
||||
Alert<AtomicStatusCheckParams>,
|
||||
| 'id'
|
||||
| 'createdBy'
|
||||
| 'updatedBy'
|
||||
| 'createdAt'
|
||||
| 'updatedAt'
|
||||
| 'apiKey'
|
||||
| 'apiKeyOwner'
|
||||
| 'muteAll'
|
||||
| 'mutedInstanceIds'
|
||||
| 'executionStatus'
|
||||
>;
|
||||
|
||||
export const createAlert = async ({
|
||||
defaultActions,
|
||||
monitorId,
|
||||
monitorName,
|
||||
}: NewAlertParams): Promise<Alert> => {
|
||||
const actions: AlertAction[] = [];
|
||||
defaultActions.forEach((aId) => {
|
||||
actions.push({
|
||||
id: aId.id,
|
||||
actionTypeId: aId.actionTypeId,
|
||||
group: MONITOR_STATUS.id,
|
||||
params: {
|
||||
message: MonitorStatusTranslations.defaultActionMessage,
|
||||
},
|
||||
});
|
||||
});
|
||||
const actions: AlertAction[] = populateAlertActions({ defaultActions, monitorId, monitorName });
|
||||
|
||||
const data = {
|
||||
const data: NewMonitorStatusAlert = {
|
||||
actions,
|
||||
params: {
|
||||
numTimes: 1,
|
||||
|
@ -60,8 +64,11 @@ export const createAlert = async ({
|
|||
consumer: 'uptime',
|
||||
alertTypeId: CLIENT_ALERT_TYPES.MONITOR_STATUS,
|
||||
schedule: { interval: '1m' },
|
||||
notifyWhen: 'onActionGroupChange',
|
||||
tags: [UPTIME_AUTO_ALERT],
|
||||
name: `${monitorName} (Simple status alert)`,
|
||||
enabled: true,
|
||||
throttle: null,
|
||||
};
|
||||
|
||||
return await apiService.post(API_URLS.CREATE_ALERT, data);
|
||||
|
@ -99,3 +106,7 @@ export const fetchAlertRecords = async ({
|
|||
export const disableAlertById = async ({ alertId }: { alertId: string }) => {
|
||||
return await apiService.delete(API_URLS.ALERT + alertId);
|
||||
};
|
||||
|
||||
export const fetchActionTypes = async (): Promise<ActionType[]> => {
|
||||
return await apiService.get(API_URLS.ACTION_TYPES);
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue