Added default dedupKey value as an {{alertInstanceId}} to provide grouping functionality for PagerDuty incidents. (#84598)

* Added default dedupKey value as an {{alertInstanceId}} to provide grouping functionality for PagerDuty incidents.

* fixed type check
This commit is contained in:
Yuliia Naumenko 2020-11-30 19:23:26 -08:00 committed by GitHub
parent 5ed39f2fe1
commit 67564b9776
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 92 additions and 14 deletions

View file

@ -529,7 +529,7 @@ The PagerDuty action uses the [V2 Events API](https://v2.developer.pagerduty.com
| Property | Description | Type |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- |
| eventAction | One of `trigger` _(default)_, `resolve`, or `acknowlege`. See [event action](https://v2.developer.pagerduty.com/docs/events-api-v2#event-action) for more details. | string _(optional)_ |
| dedupKey | All actions sharing this key will be associated with the same PagerDuty alert. Used to correlate trigger and resolution. Defaults to `action:<action id>`. The maximum length is **255** characters. See [alert deduplication](https://v2.developer.pagerduty.com/docs/events-api-v2#alert-de-duplication) for details. | string _(optional)_ |
| dedupKey | All actions sharing this key will be associated with the same PagerDuty alert. Used to correlate trigger and resolution. The maximum length is **255** characters. See [alert deduplication](https://v2.developer.pagerduty.com/docs/events-api-v2#alert-de-duplication) for details. | string _(optional)_ |
| summary | A text summary of the event, defaults to `No summary provided`. The maximum length is **1024** characters. | string _(optional)_ |
| source | The affected system, preferably a hostname or fully qualified domain name. Defaults to `Kibana Action <action id>`. | string _(optional)_ |
| severity | The perceived severity of on the affected system. This can be one of `critical`, `error`, `warning` or `info`_(default)_. | string _(optional)_ |

View file

@ -9,6 +9,7 @@ import JiraParamsFields from './jira_params';
import { useGetIssueTypes } from './use_get_issue_types';
import { useGetFieldsByIssueType } from './use_get_fields_by_issue_type';
import { ActionConnector } from '../../../../types';
import { AlertProvidedActionVariables } from '../../../lib/action_variables';
jest.mock('../../../../common/lib/kibana');
jest.mock('./use_get_issue_types');
@ -86,7 +87,7 @@ describe('JiraParamsFields renders', () => {
errors={{ title: [] }}
editAction={() => {}}
index={0}
messageVariables={[{ name: 'alertId', description: '' }]}
messageVariables={[{ name: AlertProvidedActionVariables.alertId, description: '' }]}
actionConnector={connector}
/>
);

View file

@ -29,6 +29,7 @@ import { useGetIssueTypes } from './use_get_issue_types';
import { useGetFieldsByIssueType } from './use_get_fields_by_issue_type';
import { SearchIssues } from './search_issues';
import { extractActionVariable } from '../extract_action_variable';
import { AlertProvidedActionVariables } from '../../../lib/action_variables';
import { useKibana } from '../../../../common/lib/kibana';
const JiraParamsFields: React.FunctionComponent<ActionParamsProps<JiraActionParams>> = ({
@ -51,7 +52,7 @@ const JiraParamsFields: React.FunctionComponent<ActionParamsProps<JiraActionPara
const [prioritiesSelectOptions, setPrioritiesSelectOptions] = useState<EuiSelectOption[]>([]);
const isActionBeingConfiguredByAnAlert = messageVariables
? isSome(extractActionVariable(messageVariables, 'alertId'))
? isSome(extractActionVariable(messageVariables, AlertProvidedActionVariables.alertId))
: false;
useEffect(() => {
@ -144,7 +145,7 @@ const JiraParamsFields: React.FunctionComponent<ActionParamsProps<JiraActionPara
editAction('subAction', 'pushToService', index);
}
if (!savedObjectId && isActionBeingConfiguredByAnAlert) {
editSubActionProperty('savedObjectId', '{{alertId}}');
editSubActionProperty('savedObjectId', `${AlertProvidedActionVariables.alertId}`);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [

View file

@ -34,6 +34,10 @@ describe('PagerDutyParamsFields renders', () => {
expect(wrapper.find('[data-test-subj="severitySelect"]').first().prop('value')).toStrictEqual(
'critical'
);
expect(wrapper.find('[data-test-subj="dedupKeyInput"]').length > 0).toBeTruthy();
expect(wrapper.find('[data-test-subj="dedupKeyInput"]').first().prop('value')).toStrictEqual(
'test'
);
expect(wrapper.find('[data-test-subj="eventActionSelect"]').length > 0).toBeTruthy();
expect(wrapper.find('[data-test-subj="dedupKeyInput"]').length > 0).toBeTruthy();
expect(wrapper.find('[data-test-subj="timestampInput"]').length > 0).toBeTruthy();

View file

@ -8,6 +8,7 @@ import { mountWithIntl } from '@kbn/test/jest';
import ResilientParamsFields from './resilient_params';
import { useGetIncidentTypes } from './use_get_incident_types';
import { useGetSeverity } from './use_get_severity';
import { AlertProvidedActionVariables } from '../../../lib/action_variables';
jest.mock('./use_get_incident_types');
jest.mock('./use_get_severity');
@ -82,7 +83,7 @@ describe('ResilientParamsFields renders', () => {
errors={{ title: [] }}
editAction={() => {}}
index={0}
messageVariables={[{ name: 'alertId', description: '' }]}
messageVariables={[{ name: AlertProvidedActionVariables.alertId, description: '' }]}
actionConnector={connector}
/>
);

View file

@ -27,6 +27,7 @@ import { TextFieldWithMessageVariables } from '../../text_field_with_message_var
import { useGetIncidentTypes } from './use_get_incident_types';
import { useGetSeverity } from './use_get_severity';
import { extractActionVariable } from '../extract_action_variable';
import { AlertProvidedActionVariables } from '../../../lib/action_variables';
import { useKibana } from '../../../../common/lib/kibana';
const ResilientParamsFields: React.FunctionComponent<ActionParamsProps<ResilientActionParams>> = ({
@ -46,7 +47,7 @@ const ResilientParamsFields: React.FunctionComponent<ActionParamsProps<Resilient
actionParams.subActionParams || {};
const isActionBeingConfiguredByAnAlert = messageVariables
? isSome(extractActionVariable(messageVariables, 'alertId'))
? isSome(extractActionVariable(messageVariables, AlertProvidedActionVariables.alertId))
: false;
const [incidentTypesComboBoxOptions, setIncidentTypesComboBoxOptions] = useState<
@ -110,7 +111,7 @@ const ResilientParamsFields: React.FunctionComponent<ActionParamsProps<Resilient
editAction('subAction', 'pushToService', index);
}
if (!savedObjectId && isActionBeingConfiguredByAnAlert) {
editSubActionProperty('savedObjectId', '{{alertId}}');
editSubActionProperty('savedObjectId', `${AlertProvidedActionVariables.alertId}`);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [actionConnector, savedObjectId]);

View file

@ -6,6 +6,7 @@
import React from 'react';
import { mountWithIntl } from '@kbn/test/jest';
import ServiceNowParamsFields from './servicenow_params';
import { AlertProvidedActionVariables } from '../../../lib/action_variables';
describe('ServiceNowParamsFields renders', () => {
test('all params fields is rendered', () => {
@ -29,7 +30,7 @@ describe('ServiceNowParamsFields renders', () => {
errors={{ title: [] }}
editAction={() => {}}
index={0}
messageVariables={[{ name: 'alertId', description: '' }]}
messageVariables={[{ name: AlertProvidedActionVariables.alertId, description: '' }]}
/>
);
expect(wrapper.find('[data-test-subj="urgencySelect"]').length > 0).toBeTruthy();

View file

@ -22,6 +22,7 @@ import { ServiceNowActionParams } from './types';
import { TextAreaWithMessageVariables } from '../../text_area_with_message_variables';
import { TextFieldWithMessageVariables } from '../../text_field_with_message_variables';
import { extractActionVariable } from '../extract_action_variable';
import { AlertProvidedActionVariables } from '../../../lib/action_variables';
const ServiceNowParamsFields: React.FunctionComponent<
ActionParamsProps<ServiceNowActionParams>
@ -30,7 +31,7 @@ const ServiceNowParamsFields: React.FunctionComponent<
actionParams.subActionParams || {};
const isActionBeingConfiguredByAnAlert = messageVariables
? isSome(extractActionVariable(messageVariables, 'alertId'))
? isSome(extractActionVariable(messageVariables, AlertProvidedActionVariables.alertId))
: false;
const selectOptions = [
@ -73,7 +74,7 @@ const ServiceNowParamsFields: React.FunctionComponent<
editAction('subAction', 'pushToService', index);
}
if (!savedObjectId && isActionBeingConfiguredByAnAlert) {
editSubActionProperty('savedObjectId', '{{alertId}}');
editSubActionProperty('savedObjectId', `${AlertProvidedActionVariables.alertId}`);
}
if (!urgency) {
editSubActionProperty('urgency', '3');

View file

@ -16,6 +16,7 @@ interface Props {
inputTargetValue?: string;
editAction: (property: string, value: any, index: number) => void;
errors?: string[];
defaultValue?: string | number | string[];
}
export const TextFieldWithMessageVariables: React.FunctionComponent<Props> = ({
@ -25,6 +26,7 @@ export const TextFieldWithMessageVariables: React.FunctionComponent<Props> = ({
inputTargetValue,
editAction,
errors,
defaultValue,
}) => {
const [currentTextElement, setCurrentTextElement] = useState<HTMLInputElement | null>(null);
@ -51,6 +53,7 @@ export const TextFieldWithMessageVariables: React.FunctionComponent<Props> = ({
isInvalid={errors && errors.length > 0 && inputTargetValue !== undefined}
data-test-subj={`${paramsProperty}Input`}
value={inputTargetValue || ''}
defaultValue={defaultValue}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onChangeWithMessageVariable(e)}
onFocus={(e: React.FocusEvent<HTMLInputElement>) => {
setCurrentTextElement(e.target);

View file

@ -19,6 +19,14 @@ export function transformActionVariables(actionVariables: ActionVariables): Acti
return alwaysProvidedVars.concat(contextVars, paramsVars, stateVars);
}
export enum AlertProvidedActionVariables {
alertId = 'alertId',
alertName = 'alertName',
spaceId = 'spaceId',
tags = 'tags',
alertInstanceId = 'alertInstanceId',
}
function prefixKeys(actionVariables: ActionVariable[], prefix: string): ActionVariable[] {
return actionVariables.map((actionVariable) => {
return { name: `${prefix}${actionVariable.name}`, description: actionVariable.description };
@ -31,28 +39,28 @@ function getAlwaysProvidedActionVariables(): ActionVariable[] {
const result: ActionVariable[] = [];
result.push({
name: 'alertId',
name: AlertProvidedActionVariables.alertId,
description: i18n.translate('xpack.triggersActionsUI.actionVariables.alertIdLabel', {
defaultMessage: 'The id of the alert.',
}),
});
result.push({
name: 'alertName',
name: AlertProvidedActionVariables.alertName,
description: i18n.translate('xpack.triggersActionsUI.actionVariables.alertNameLabel', {
defaultMessage: 'The name of the alert.',
}),
});
result.push({
name: 'spaceId',
name: AlertProvidedActionVariables.spaceId,
description: i18n.translate('xpack.triggersActionsUI.actionVariables.spaceIdLabel', {
defaultMessage: 'The spaceId of the alert.',
}),
});
result.push({
name: 'tags',
name: AlertProvidedActionVariables.tags,
description: i18n.translate('xpack.triggersActionsUI.actionVariables.tagsLabel', {
defaultMessage: 'The tags of the alert.',
}),

View file

@ -0,0 +1,25 @@
/*
* 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 { ResolvedActionGroup } from '../../../../alerts/common';
import { AlertProvidedActionVariables } from './action_variables';
import { getDefaultsForActionParams } from './get_defaults_for_action_params';
describe('getDefaultsForActionParams', () => {
test('pagerduty defaults', async () => {
expect(getDefaultsForActionParams('.pagerduty', 'test')).toEqual({
dedupKey: `{{${AlertProvidedActionVariables.alertId}}}:{{${AlertProvidedActionVariables.alertInstanceId}}}`,
eventAction: 'trigger',
});
});
test('pagerduty defaults for resolved action group', async () => {
expect(getDefaultsForActionParams('.pagerduty', ResolvedActionGroup.id)).toEqual({
dedupKey: `{{${AlertProvidedActionVariables.alertId}}}:{{${AlertProvidedActionVariables.alertInstanceId}}}`,
eventAction: 'resolve',
});
});
});

View file

@ -0,0 +1,25 @@
/*
* 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 { AlertActionParam, ResolvedActionGroup } from '../../../../alerts/common';
import { AlertProvidedActionVariables } from './action_variables';
export const getDefaultsForActionParams = (
actionTypeId: string,
actionGroupId: string
): Record<string, AlertActionParam> | undefined => {
switch (actionTypeId) {
case '.pagerduty':
const pagerDutyDefaults = {
dedupKey: `{{${AlertProvidedActionVariables.alertId}}}:{{${AlertProvidedActionVariables.alertInstanceId}}}`,
eventAction: 'trigger',
};
if (actionGroupId === ResolvedActionGroup.id) {
pagerDutyDefaults.eventAction = 'resolve';
}
return pagerDutyDefaults;
}
};

View file

@ -42,6 +42,7 @@ import { ActionAccordionFormProps } from './action_form';
import { transformActionVariables } from '../../lib/action_variables';
import { resolvedActionGroupMessage } from '../../constants';
import { useKibana } from '../../../common/lib/kibana';
import { getDefaultsForActionParams } from '../../lib/get_defaults_for_action_params';
export type ActionTypeFormProps = {
actionItem: AlertAction;
@ -108,6 +109,12 @@ export const ActionTypeForm = ({
? resolvedActionGroupMessage
: defaultActionMessage;
setAvailableDefaultActionMessage(res);
const paramsDefaults = getDefaultsForActionParams(actionItem.actionTypeId, actionItem.group);
if (paramsDefaults) {
for (const [key, paramValue] of Object.entries(paramsDefaults)) {
setActionParamsProperty(key, paramValue, index);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [actionItem.group]);