Skip to content

Commit

Permalink
[MGMTXP] [Connectors] Added the additional_info field to the ITOM c…
Browse files Browse the repository at this point in the history
…onnector UI (#183380)

Fixes #171320

## Summary

The additional_info field for the ServiceNow ITOM connector already
existed in the backend. When creating an action it was being
pre-populated with some rule and alert data and sent to the backend with
the form.

In this PR I am making that field visible and editable by the user while
preserving the original default values.

<img width="1959" alt="Screenshot 2024-05-14 at 12 28 25"
src="https://github.com/elastic/kibana/assets/1533137/74f81ff0-a141-4744-b86c-3a7146796342">

## Release notes

Added support for the additional info field in the ServiceNow ITOM
connector.
  • Loading branch information
adcoelho committed May 16, 2024
1 parent 62a0ce9 commit 78ff455
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -412,3 +412,32 @@ export const EVENT_ACTION_LABEL = i18n.translate(
defaultMessage: 'Event action',
}
);

export const ADDITIONAL_INFO = i18n.translate(
'xpack.stackConnectors.components.serviceNowITOM.additionalInfoLabel',
{
defaultMessage: 'Additional info',
}
);

export const ADDITIONAL_INFO_HELP = i18n.translate(
'xpack.stackConnectors.components.serviceNowITOM.additionalInfoHelpTooltip',
{
defaultMessage: 'Additional info help',
}
);

export const ADDITIONAL_INFO_HELP_TEXT = i18n.translate(
'xpack.stackConnectors.components.serviceNowITOM.additionalInfoHelpTooltipText',
{
defaultMessage:
'The rule automatically generates information about each event. You can change or add more custom fields in JSON format.',
}
);

export const ADDITIONAL_INFO_JSON_ERROR = i18n.translate(
'xpack.stackConnectors.components.serviceNowITOM.additionalInfoError',
{
defaultMessage: 'The additional info field does not have a valid JSON format.',
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,22 @@ describe('servicenow action params validation', () => {
const actionParams = { subActionParams: { severity: 'Critical' } };

expect(await connectorTypeModel.validateParams(actionParams)).toEqual({
errors: { ['severity']: [] },
errors: {
['severity']: [],
['additional_info']: [],
},
});
});

test(`${SERVICENOW_ITOM_CONNECTOR_TYPE_ID}: params validation succeeds when additional_info is an empty string`, async () => {
const connectorTypeModel = connectorTypeRegistry.get(SERVICENOW_ITOM_CONNECTOR_TYPE_ID);
const actionParams = { subActionParams: { severity: 'Critical', additional_info: '' } };

expect(await connectorTypeModel.validateParams(actionParams)).toEqual({
errors: {
['severity']: [],
['additional_info']: [],
},
});
});

Expand All @@ -42,7 +57,22 @@ describe('servicenow action params validation', () => {
const actionParams = { subActionParams: { severity: null } };

expect(await connectorTypeModel.validateParams(actionParams)).toEqual({
errors: { ['severity']: ['Severity is required.'] },
errors: {
['severity']: ['Severity is required.'],
['additional_info']: [],
},
});
});

test(`${SERVICENOW_ITOM_CONNECTOR_TYPE_ID}: params validation fails when additional_info is not valid JSON`, async () => {
const connectorTypeModel = connectorTypeRegistry.get(SERVICENOW_ITOM_CONNECTOR_TYPE_ID);
const actionParams = { subActionParams: { severity: 'Critical', additional_info: 'foobar' } };

expect(await connectorTypeModel.validateParams(actionParams)).toEqual({
errors: {
['severity']: [],
['additional_info']: ['The additional info field does not have a valid JSON format.'],
},
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,20 @@ export function getServiceNowITOMConnectorType(): ConnectorTypeModel<
const translations = await import('../lib/servicenow/translations');
const errors = {
severity: new Array<string>(),
additional_info: new Array<string>(),
};
const validationResult = { errors };

if (actionParams?.subActionParams?.severity == null) {
errors.severity.push(translations.SEVERITY_REQUIRED);
}

return validationResult;
try {
JSON.parse(actionParams.subActionParams?.additional_info || '{}');
} catch (error) {
errors.additional_info.push(translations.ADDITIONAL_INFO_JSON_ERROR);
}

return { errors };
},
actionParamsFields: lazy(() => import('./servicenow_itom_params')),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ describe('ServiceNowITOMParamsFields renders', () => {
expect(wrapper.find('[data-test-subj="message_keyInput"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="severitySelect"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="descriptionTextArea"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="additional_infoJsonEditor"]').exists()).toBeTruthy();
});

test('If severity has errors, form row is invalid', () => {
Expand All @@ -117,8 +118,24 @@ describe('ServiceNowITOMParamsFields renders', () => {
mount(<ServiceNowITOMParamsFields {...newProps} />);
expect(editAction.mock.calls[0][1]).toEqual({
message_key: '{{rule.id}}:{{alert.id}}',
additional_info:
'{"alert":{"id":"{{alert.id}}","actionGroup":"{{alert.actionGroup}}","actionSubgroup":"{{alert.actionSubgroup}}","actionGroupName":"{{alert.actionGroupName}}"},"rule":{"id":"{{rule.id}}","name":"{{rule.name}}","type":"{{rule.type}}"},"date":"{{date}}"}',
additional_info: JSON.stringify(
{
alert: {
id: '{{alert.id}}',
actionGroup: '{{alert.actionGroup}}',
actionSubgroup: '{{alert.actionSubgroup}}',
actionGroupName: '{{alert.actionGroupName}}',
},
rule: {
id: '{{rule.id}}',
name: '{{rule.name}}',
type: '{{rule.type}}',
},
date: '{{date}}',
},
null,
4
),
});
});

Expand All @@ -140,8 +157,24 @@ describe('ServiceNowITOMParamsFields renders', () => {
expect(editAction.mock.calls.length).toEqual(1);
expect(editAction.mock.calls[0][1]).toEqual({
message_key: '{{rule.id}}:{{alert.id}}',
additional_info:
'{"alert":{"id":"{{alert.id}}","actionGroup":"{{alert.actionGroup}}","actionSubgroup":"{{alert.actionSubgroup}}","actionGroupName":"{{alert.actionGroupName}}"},"rule":{"id":"{{rule.id}}","name":"{{rule.name}}","type":"{{rule.type}}"},"date":"{{date}}"}',
additional_info: JSON.stringify(
{
alert: {
id: '{{alert.id}}',
actionGroup: '{{alert.actionGroup}}',
actionSubgroup: '{{alert.actionSubgroup}}',
actionGroupName: '{{alert.actionGroupName}}',
},
rule: {
id: '{{rule.id}}',
name: '{{rule.name}}',
type: '{{rule.type}}',
},
date: '{{date}}',
},
null,
4
),
});
});

Expand Down Expand Up @@ -177,5 +210,15 @@ describe('ServiceNowITOMParamsFields renders', () => {
expect(editAction.mock.calls[0][1][field.key]).toEqual(changeEvent.target.value);
})
);

test('additional_info update triggers editAction correctly', () => {
const newValue = '{"foo": "bar"}' as unknown as React.ChangeEvent<HTMLSelectElement>;
const wrapper = mount(<ServiceNowITOMParamsFields {...defaultProps} />);
const theField = wrapper.find('[data-test-subj="additional_infoJsonEditor"]').first();

theField.prop('onChange')!(newValue);

expect(editAction.mock.calls[0][1].additional_info).toEqual(newValue);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
*/

import React, { useCallback, useEffect, useRef, useMemo } from 'react';
import { EuiFormRow, EuiSpacer, EuiTitle, EuiText, EuiSelect } from '@elastic/eui';
import type { ActionParamsProps } from '@kbn/triggers-actions-ui-plugin/public';
import { EuiFormRow, EuiSpacer, EuiTitle, EuiText, EuiSelect, EuiIconTip } from '@elastic/eui';
import {
ActionParamsProps,
JsonEditorWithMessageVariables,
} from '@kbn/triggers-actions-ui-plugin/public';
import {
TextAreaWithMessageVariables,
TextFieldWithMessageVariables,
Expand All @@ -34,20 +37,24 @@ const fields: Array<{
{ label: i18n.MESSAGE_KEY, fieldKey: 'message_key' },
];

const additionalInformation = JSON.stringify({
alert: {
id: '{{alert.id}}',
actionGroup: '{{alert.actionGroup}}',
actionSubgroup: '{{alert.actionSubgroup}}',
actionGroupName: '{{alert.actionGroupName}}',
},
rule: {
id: '{{rule.id}}',
name: '{{rule.name}}',
type: '{{rule.type}}',
const additionalInformation = JSON.stringify(
{
alert: {
id: '{{alert.id}}',
actionGroup: '{{alert.actionGroup}}',
actionSubgroup: '{{alert.actionSubgroup}}',
actionGroupName: '{{alert.actionGroupName}}',
},
rule: {
id: '{{rule.id}}',
name: '{{rule.name}}',
type: '{{rule.type}}',
},
date: '{{date}}',
},
date: '{{date}}',
});
null,
4
);

const ServiceNowITOMParamsFields: React.FunctionComponent<
ActionParamsProps<ServiceNowITOMActionParams>
Expand All @@ -57,8 +64,7 @@ const ServiceNowITOMParamsFields: React.FunctionComponent<
[actionParams.subActionParams]
);

const { description, severity } = params;

const { description, severity, additional_info: additionalInfo } = params;
const {
http,
notifications: { toasts },
Expand Down Expand Up @@ -159,6 +165,38 @@ const ServiceNowITOMParamsFields: React.FunctionComponent<
inputTargetValue={description ?? undefined}
label={i18n.DESCRIPTION_LABEL}
/>
<EuiFormRow
fullWidth
error={errors['subActionParams.additional_info']}
isInvalid={
errors['subActionParams.additional_info'] !== undefined &&
errors['subActionParams.additional_info'].length > 0
}
>
<JsonEditorWithMessageVariables
messageVariables={messageVariables}
paramsProperty={'additional_info'}
inputTargetValue={additionalInfo ?? ''}
errors={errors.additional_info as string[]}
label={
<>
{i18n.ADDITIONAL_INFO}
<EuiIconTip
size="s"
color="subdued"
type="questionInCircle"
className="eui-alignTop"
data-test-subj="otherFieldsHelpTooltip"
aria-label={i18n.ADDITIONAL_INFO_HELP}
content={i18n.ADDITIONAL_INFO_HELP_TEXT}
/>
</>
}
onDocumentsChange={(json: string) => {
editSubActionProperty('additional_info', json);
}}
/>
</EuiFormRow>
</>
);
};
Expand Down

0 comments on commit 78ff455

Please sign in to comment.