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. 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)_ |
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.alertId, 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.alertId))
: 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', `${AlertProvidedActionVariables.alertId}`);
}
// 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 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.alertId, 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.alertId))
: 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', `${AlertProvidedActionVariables.alertId}`);
}
// 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.alertId, 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.alertId))
: 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', `${AlertProvidedActionVariables.alertId}`);
}
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,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.',
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -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',
});
});
});
Original file line number Diff line number Diff line change
@@ -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';

export const getDefaultsForActionParams = (
actionTypeId: string,
actionGroupId: string
): Record<string, unknown> | undefined => {
switch (actionTypeId) {
case '.pagerduty':
const pagerDutyDefaults = {
dedupKey: `{{${AlertProvidedActionVariables.alertId}}}:{{${AlertProvidedActionVariables.alertInstanceId}}}`,
eventAction: 'trigger',
};
if (actionGroupId === ResolvedActionGroup.id) {
pagerDutyDefaults.eventAction = 'resolve';
}
return pagerDutyDefaults;
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { hasSaveActionsCapability } from '../../lib/capabilities';
import { ActionAccordionFormProps } from './action_form';
import { transformActionVariables } from '../../lib/action_variables';
import { resolvedActionGroupMessage } from '../../constants';
import { getDefaultsForActionParams } from '../../lib/get_defaults_for_action_params';

export type ActionTypeFormProps = {
actionItem: AlertAction;
Expand Down Expand Up @@ -110,6 +111,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]);

Expand Down