Skip to content

Commit

Permalink
[ResponseOps][Connectors] Add support of additional fields for Servic…
Browse files Browse the repository at this point in the history
…eNow ITSM and SecOps (#184023)

## Summary

This PR adds support for additional fields for the ServiceNow ITSM and
SecOps connector. The additional fields will not be available to the
recovered action.

<img width="607" alt="Screenshot 2024-05-27 at 6 29 26 PM"
src="https://github.com/elastic/kibana/assets/7871006/7d397d7b-2b0b-4399-8d3a-0725ad04a10d">

## Testing

Verify that:

1. Existing rules with ITSM and SecOps configured continue working as
expected.
2. Can create rules with an ITSM action and set some additional fields
supported by ITSM. You can find the available in the Elastic
transformation map inside ServiceNow.
3. The "additional fields" verification in the UI is working as
expected.
4. The "additional fields" are not shown when you set a recovered
action.

Fixes: #183609

### Checklist

Delete any items that are not applicable to this PR.

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

### For maintainers

- [x] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

## Release notes

Pass any field to ServiceNow using the ServiceNow ITSM and SecOps
connectors with a JSON field called "additional fields".

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
cnasikas and kibanamachine authored Jun 13, 2024
1 parent ad646ca commit 75f3af5
Show file tree
Hide file tree
Showing 41 changed files with 1,174 additions and 52 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ function getServiceNowActionParams({ defaultActionMessage }: Translations): Serv
externalId: null,
correlation_id: null,
correlation_display: null,
additional_fields: null,
},
comments: [],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ function getServiceNowActionParams({ defaultActionMessage }: Translations): Serv
externalId: null,
correlation_id: null,
correlation_display: null,
additional_fields: null,
},
comments: [],
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

export const MAX_ADDITIONAL_FIELDS_LENGTH = 20;
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,7 @@ describe('jira action params validation', () => {
errors: {
'subActionParams.incident.summary': [],
'subActionParams.incident.labels': [],
'subActionParams.incident.otherFields': [
'Additional fields field must be a valid JSON object.',
],
'subActionParams.incident.otherFields': ['Invalid JSON.'],
},
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
} from '@kbn/triggers-actions-ui-plugin/public';
import { MAX_OTHER_FIELDS_LENGTH } from '../../../common/jira/constants';
import { JiraConfig, JiraSecrets, JiraActionParams } from './types';
import { validateJSON } from '../lib/validate_json';

export const JIRA_DESC = i18n.translate('xpack.stackConnectors.components.jira.selectMessageText', {
defaultMessage: 'Create an incident in Jira.',
Expand Down Expand Up @@ -58,19 +59,15 @@ export function getConnectorType(): ConnectorTypeModel<JiraConfig, JiraSecrets,
errors['subActionParams.incident.labels'].push(translations.LABELS_WHITE_SPACES);
}

try {
const otherFields = actionParams.subActionParams?.incident?.otherFields;
if (otherFields) {
const parsedOtherFields = JSON.parse(otherFields);
if (Object.keys(parsedOtherFields).length > MAX_OTHER_FIELDS_LENGTH) {
errors['subActionParams.incident.otherFields'] = [
translations.OTHER_FIELDS_LENGTH_ERROR(MAX_OTHER_FIELDS_LENGTH),
];
}
}
} catch (error) {
errors['subActionParams.incident.otherFields'] = [translations.INVALID_JSON_FORMAT];
const jsonErrors = validateJSON({
value: actionParams.subActionParams?.incident?.otherFields,
maxProperties: MAX_OTHER_FIELDS_LENGTH,
});

if (jsonErrors) {
errors['subActionParams.incident.otherFields'] = [jsonErrors];
}

return validationResult;
},
actionParamsFields: lazy(() => import('./jira_params')),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
import { AdditionalFields } from './additional_fields';
import userEvent from '@testing-library/user-event';

describe('Credentials', () => {
const onChange = jest.fn();
const value = JSON.stringify({ foo: 'test' });
const props = { value, errors: [], onChange };

it('renders the additional fields correctly', async () => {
render(
<IntlProvider locale="en">
<AdditionalFields {...props} />
</IntlProvider>
);

expect(await screen.findByTestId('additionalFields')).toBeInTheDocument();
});

it('sets the value correctly', async () => {
render(
<IntlProvider locale="en">
<AdditionalFields {...props} />
</IntlProvider>
);

expect(await screen.findByText(value)).toBeInTheDocument();
});

/**
* Test for the intermediate release process
*/
it('does not show the component if the value is undefined', async () => {
render(
<IntlProvider locale="en">
<AdditionalFields {...props} value={undefined} />
</IntlProvider>
);

expect(screen.queryByTestId('additional_fieldsJsonEditor')).not.toBeInTheDocument();
});

it('changes the value correctly', async () => {
const newValue = JSON.stringify({ bar: 'test' });

render(
<IntlProvider locale="en">
<AdditionalFields {...props} />
</IntlProvider>
);

const editor = await screen.findByTestId('additional_fieldsJsonEditor');

userEvent.clear(editor);
userEvent.paste(editor, newValue);

await waitFor(() => {
expect(onChange).toHaveBeenCalledWith(newValue);
});

expect(await screen.findByText(newValue)).toBeInTheDocument();
});

it('updating wth an empty string sets its value to null', async () => {
const newValue = JSON.stringify({ bar: 'test' });

render(
<IntlProvider locale="en">
<AdditionalFields {...props} />
</IntlProvider>
);

const editor = await screen.findByTestId('additional_fieldsJsonEditor');

userEvent.paste(editor, newValue);
userEvent.clear(editor);

await waitFor(() => {
expect(onChange).toHaveBeenCalledWith(null);
});
});
});
Loading

0 comments on commit 75f3af5

Please sign in to comment.