Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

2 changes: 1 addition & 1 deletion x-pack/plugins/actions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. Defaults to `{{alertInstanceId}}`. 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)_ |
YulNaumenko marked this conversation as resolved.
Show resolved Hide resolved
| 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)_ |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { coreMock } from 'src/core/public/mocks';
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('./use_get_issue_types');
jest.mock('./use_get_fields_by_issue_type');
Expand Down Expand Up @@ -90,7 +91,7 @@ describe('JiraParamsFields renders', () => {
errors={{ title: [] }}
editAction={() => {}}
index={0}
messageVariables={[{ name: 'alertId', description: '' }]}
messageVariables={[{ name: AlertProvidedActionVariables.alertInstanceId, description: '' }]}
docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart}
toastNotifications={mocks.notifications.toasts}
http={mocks.http}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

const JiraParamsFields: React.FunctionComponent<ActionParamsProps<JiraActionParams>> = ({
actionParams,
Expand All @@ -48,7 +49,7 @@ const JiraParamsFields: React.FunctionComponent<ActionParamsProps<JiraActionPara
const [prioritiesSelectOptions, setPrioritiesSelectOptions] = useState<EuiSelectOption[]>([]);

const isActionBeingConfiguredByAnAlert = messageVariables
? isSome(extractActionVariable(messageVariables, 'alertId'))
? isSome(extractActionVariable(messageVariables, AlertProvidedActionVariables.alertInstanceId))
: false;

useEffect(() => {
Expand Down Expand Up @@ -141,7 +142,7 @@ const JiraParamsFields: React.FunctionComponent<ActionParamsProps<JiraActionPara
editAction('subAction', 'pushToService', index);
}
if (!savedObjectId && isActionBeingConfiguredByAnAlert) {
editSubActionProperty('savedObjectId', '{{alertId}}');
editSubActionProperty('savedObjectId', '{{alertInstanceId}}');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,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();
Expand All @@ -49,4 +53,29 @@ describe('PagerDutyParamsFields renders', () => {
expect(wrapper.find('[data-test-subj="summaryInput"]').length > 0).toBeTruthy();
expect(wrapper.find('[data-test-subj="dedupKeyAddVariableButton"]').length > 0).toBeTruthy();
});

test('dedupKey field is set default as {{alertInstanceId}} if not defined', async () => {
const mocks = coreMock.createSetup();
const actionParams = {
eventAction: EventActionOptions.TRIGGER,
summary: '2323',
};

const wrapper = mountWithIntl(
<PagerDutyParamsFields
actionParams={actionParams}
errors={{ summary: [], timestamp: [], dedupKey: [] }}
editAction={() => {}}
index={0}
docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart}
toastNotifications={mocks.notifications.toasts}
http={mocks.http}
/>
);

expect(wrapper.find('[data-test-subj="dedupKeyInput"]').length > 0).toBeTruthy();
expect(
wrapper.find('[data-test-subj="dedupKeyInput"]').first().props().defaultValue
).toStrictEqual('{{alertInstanceId}}');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React, { Fragment } from 'react';
import React, { Fragment, useEffect } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSelect, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ActionParamsProps } from '../../../../types';
import { PagerDutyActionParams } from '.././types';
import { TextFieldWithMessageVariables } from '../../text_field_with_message_variables';
import { AlertProvidedActionVariables } from '../../../lib/action_variables';

const PagerDutyParamsFields: React.FunctionComponent<ActionParamsProps<PagerDutyActionParams>> = ({
actionParams,
Expand Down Expand Up @@ -97,6 +98,11 @@ const PagerDutyParamsFields: React.FunctionComponent<ActionParamsProps<PagerDuty

const isDedupeKeyRequired = eventAction !== 'trigger';

useEffect(() => {
editAction('dedupKey', `{{${AlertProvidedActionVariables.alertInstanceId}}}`, index);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
YulNaumenko marked this conversation as resolved.
Show resolved Hide resolved

return (
<Fragment>
<EuiFlexGroup>
Expand Down Expand Up @@ -194,6 +200,7 @@ const PagerDutyParamsFields: React.FunctionComponent<ActionParamsProps<PagerDuty
messageVariables={messageVariables}
paramsProperty={'dedupKey'}
inputTargetValue={dedupKey}
defaultValue={`{{${AlertProvidedActionVariables.alertInstanceId}}}`}
YulNaumenko marked this conversation as resolved.
Show resolved Hide resolved
/>
</EuiFormRow>
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { DocLinksStart } from 'kibana/public';
import { useGetIncidentTypes } from './use_get_incident_types';
import { useGetSeverity } from './use_get_severity';
import { coreMock } from 'src/core/public/mocks';
import { AlertProvidedActionVariables } from '../../../lib/action_variables';

const mocks = coreMock.createSetup();

Expand Down Expand Up @@ -86,7 +87,7 @@ describe('ResilientParamsFields renders', () => {
errors={{ title: [] }}
editAction={() => {}}
index={0}
messageVariables={[{ name: 'alertId', description: '' }]}
messageVariables={[{ name: AlertProvidedActionVariables.alertInstanceId, description: '' }]}
docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart}
toastNotifications={mocks.notifications.toasts}
http={mocks.http}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

const ResilientParamsFields: React.FunctionComponent<ActionParamsProps<ResilientActionParams>> = ({
actionParams,
Expand All @@ -43,7 +44,7 @@ const ResilientParamsFields: React.FunctionComponent<ActionParamsProps<Resilient
actionParams.subActionParams || {};

const isActionBeingConfiguredByAnAlert = messageVariables
? isSome(extractActionVariable(messageVariables, 'alertId'))
? isSome(extractActionVariable(messageVariables, AlertProvidedActionVariables.alertInstanceId))
: false;

const [incidentTypesComboBoxOptions, setIncidentTypesComboBoxOptions] = useState<
Expand Down Expand Up @@ -107,7 +108,7 @@ const ResilientParamsFields: React.FunctionComponent<ActionParamsProps<Resilient
editAction('subAction', 'pushToService', index);
}
if (!savedObjectId && isActionBeingConfiguredByAnAlert) {
editSubActionProperty('savedObjectId', '{{alertId}}');
editSubActionProperty('savedObjectId', '{{alertInstanceId}}');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [actionConnector, savedObjectId]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { mountWithIntl } from '@kbn/test/jest';
import ServiceNowParamsFields from './servicenow_params';
import { DocLinksStart } from 'kibana/public';
import { coreMock } from 'src/core/public/mocks';
import { AlertProvidedActionVariables } from '../../../lib/action_variables';

describe('ServiceNowParamsFields renders', () => {
test('all params fields is rendered', () => {
Expand All @@ -32,7 +33,7 @@ describe('ServiceNowParamsFields renders', () => {
errors={{ title: [] }}
editAction={() => {}}
index={0}
messageVariables={[{ name: 'alertId', description: '' }]}
messageVariables={[{ name: AlertProvidedActionVariables.alertInstanceId, description: '' }]}
docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart}
toastNotifications={mocks.notifications.toasts}
http={mocks.http}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -30,7 +31,7 @@ const ServiceNowParamsFields: React.FunctionComponent<ActionParamsProps<
actionParams.subActionParams || {};

const isActionBeingConfiguredByAnAlert = messageVariables
? isSome(extractActionVariable(messageVariables, 'alertId'))
? isSome(extractActionVariable(messageVariables, AlertProvidedActionVariables.alertInstanceId))
: false;

const selectOptions = [
Expand Down Expand Up @@ -73,7 +74,7 @@ const ServiceNowParamsFields: React.FunctionComponent<ActionParamsProps<
editAction('subAction', 'pushToService', index);
}
if (!savedObjectId && isActionBeingConfiguredByAnAlert) {
editSubActionProperty('savedObjectId', '{{alertId}}');
editSubActionProperty('savedObjectId', '{{alertInstanceId}}');
}
if (!urgency) {
editSubActionProperty('urgency', '3');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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> = ({
Expand All @@ -25,6 +26,7 @@ export const TextFieldWithMessageVariables: React.FunctionComponent<Props> = ({
inputTargetValue,
editAction,
errors,
defaultValue,
}) => {
const [currentTextElement, setCurrentTextElement] = useState<HTMLInputElement | null>(null);

Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand All @@ -31,35 +39,35 @@ 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.',
}),
});

result.push({
name: 'alertInstanceId',
name: AlertProvidedActionVariables.alertInstanceId,
description: i18n.translate('xpack.triggersActionsUI.actionVariables.alertInstanceIdLabel', {
defaultMessage: 'The alert instance id that scheduled actions for the alert.',
}),
Expand Down