mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[SIEM][CASE] Dynamic fields mapping based on connector (#64412)
This commit is contained in:
parent
7cca2dacf0
commit
7d15c2103c
34 changed files with 662 additions and 239 deletions
|
@ -8,10 +8,12 @@ import * as rt from 'io-ts';
|
|||
|
||||
import { ActionResult } from '../../../../actions/common';
|
||||
import { UserRT } from '../user';
|
||||
import { JiraFieldsRT } from '../connectors/jira';
|
||||
import { ServiceNowFieldsRT } from '../connectors/servicenow';
|
||||
|
||||
/*
|
||||
* This types below are related to the service now configuration
|
||||
* mapping between our case and service-now
|
||||
* mapping between our case and [service-now, jira]
|
||||
*
|
||||
*/
|
||||
|
||||
|
@ -27,12 +29,7 @@ const CaseFieldRT = rt.union([
|
|||
rt.literal('comments'),
|
||||
]);
|
||||
|
||||
const ThirdPartyFieldRT = rt.union([
|
||||
rt.literal('comments'),
|
||||
rt.literal('description'),
|
||||
rt.literal('not_mapped'),
|
||||
rt.literal('short_description'),
|
||||
]);
|
||||
const ThirdPartyFieldRT = rt.union([JiraFieldsRT, ServiceNowFieldsRT, rt.literal('not_mapped')]);
|
||||
|
||||
export const CasesConfigurationMapsRT = rt.type({
|
||||
source: CaseFieldRT,
|
||||
|
|
8
x-pack/plugins/case/common/api/connectors/index.ts
Normal file
8
x-pack/plugins/case/common/api/connectors/index.ts
Normal file
|
@ -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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './jira';
|
||||
export * from './servicenow';
|
15
x-pack/plugins/case/common/api/connectors/jira.ts
Normal file
15
x-pack/plugins/case/common/api/connectors/jira.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 * as rt from 'io-ts';
|
||||
|
||||
export const JiraFieldsRT = rt.union([
|
||||
rt.literal('summary'),
|
||||
rt.literal('description'),
|
||||
rt.literal('comments'),
|
||||
]);
|
||||
|
||||
export type JiraFieldsType = rt.TypeOf<typeof JiraFieldsRT>;
|
15
x-pack/plugins/case/common/api/connectors/servicenow.ts
Normal file
15
x-pack/plugins/case/common/api/connectors/servicenow.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 * as rt from 'io-ts';
|
||||
|
||||
export const ServiceNowFieldsRT = rt.union([
|
||||
rt.literal('short_description'),
|
||||
rt.literal('description'),
|
||||
rt.literal('comments'),
|
||||
]);
|
||||
|
||||
export type ServiceNowFieldsType = rt.TypeOf<typeof ServiceNowFieldsRT>;
|
|
@ -30,7 +30,7 @@ export const mapping: CasesConfigurationMapping[] = [
|
|||
];
|
||||
export const connectorsMock: Connector[] = [
|
||||
{
|
||||
id: '123',
|
||||
id: 'servicenow-1',
|
||||
actionTypeId: '.servicenow',
|
||||
name: 'My Connector',
|
||||
config: {
|
||||
|
@ -42,7 +42,7 @@ export const connectorsMock: Connector[] = [
|
|||
isPreconfigured: false,
|
||||
},
|
||||
{
|
||||
id: '456',
|
||||
id: 'servicenow-2',
|
||||
actionTypeId: '.servicenow',
|
||||
name: 'My Connector 2',
|
||||
config: {
|
||||
|
@ -69,6 +69,34 @@ export const connectorsMock: Connector[] = [
|
|||
},
|
||||
isPreconfigured: false,
|
||||
},
|
||||
{
|
||||
id: 'jira-1',
|
||||
actionTypeId: '.jira',
|
||||
name: 'Jira',
|
||||
config: {
|
||||
apiUrl: 'https://instance.atlassian.ne',
|
||||
casesConfiguration: {
|
||||
mapping: [
|
||||
{
|
||||
source: 'title',
|
||||
target: 'summary',
|
||||
actionType: 'overwrite',
|
||||
},
|
||||
{
|
||||
source: 'description',
|
||||
target: 'description',
|
||||
actionType: 'overwrite',
|
||||
},
|
||||
{
|
||||
source: 'comments',
|
||||
target: 'comments',
|
||||
actionType: 'append',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
isPreconfigured: false,
|
||||
},
|
||||
];
|
||||
|
||||
export const caseConfigurationResposeMock: CasesConfigureResponse = {
|
||||
|
|
|
@ -13,14 +13,16 @@ import { isEmpty, get } from 'lodash/fp';
|
|||
import { ActionConnectorFieldsProps } from '../../../../../../triggers_actions_ui/public/types';
|
||||
import { FieldMapping } from '../../../../pages/case/components/configure_cases/field_mapping';
|
||||
|
||||
import { defaultMapping } from '../../config';
|
||||
import { CasesConfigurationMapping } from '../../../../containers/case/configure/types';
|
||||
|
||||
import * as i18n from '../../translations';
|
||||
import { ActionConnector, ConnectorFlyoutHOCProps } from '../../types';
|
||||
import { createDefaultMapping } from '../../utils';
|
||||
import { connectorsConfiguration } from '../../config';
|
||||
|
||||
export const withConnectorFlyout = <T extends ActionConnector>({
|
||||
ConnectorFormComponent,
|
||||
connectorActionTypeId,
|
||||
secretKeys = [],
|
||||
configKeys = [],
|
||||
}: ConnectorFlyoutHOCProps<T>) => {
|
||||
|
@ -56,7 +58,7 @@ export const withConnectorFlyout = <T extends ActionConnector>({
|
|||
if (isEmpty(mapping)) {
|
||||
editActionConfig('casesConfiguration', {
|
||||
...action.config.casesConfiguration,
|
||||
mapping: defaultMapping,
|
||||
mapping: createDefaultMapping(connectorsConfiguration[connectorActionTypeId].fields),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -135,6 +137,7 @@ export const withConnectorFlyout = <T extends ActionConnector>({
|
|||
<EuiFlexItem>
|
||||
<FieldMapping
|
||||
disabled={true}
|
||||
connectorActionTypeId={connectorActionTypeId}
|
||||
mapping={mapping as CasesConfigurationMapping[]}
|
||||
onChangeMapping={handleOnChangeMappingConfig}
|
||||
/>
|
||||
|
|
|
@ -4,31 +4,11 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { CasesConfigurationMapping } from '../../containers/case/configure/types';
|
||||
|
||||
import { Connector } from './types';
|
||||
import { connector as serviceNowConnectorConfig } from './servicenow/config';
|
||||
import { connector as jiraConnectorConfig } from './jira/config';
|
||||
import { ConnectorConfiguration } from './types';
|
||||
|
||||
export const connectorsConfiguration: Record<string, Connector> = {
|
||||
export const connectorsConfiguration: Record<string, ConnectorConfiguration> = {
|
||||
'.servicenow': serviceNowConnectorConfig,
|
||||
'.jira': jiraConnectorConfig,
|
||||
};
|
||||
|
||||
export const defaultMapping: CasesConfigurationMapping[] = [
|
||||
{
|
||||
source: 'title',
|
||||
target: 'short_description',
|
||||
actionType: 'overwrite',
|
||||
},
|
||||
{
|
||||
source: 'description',
|
||||
target: 'description',
|
||||
actionType: 'overwrite',
|
||||
},
|
||||
{
|
||||
source: 'comments',
|
||||
target: 'comments',
|
||||
actionType: 'append',
|
||||
},
|
||||
];
|
||||
|
|
|
@ -4,17 +4,37 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Connector } from '../types';
|
||||
import { ConnectorConfiguration } from './types';
|
||||
|
||||
import { JIRA_TITLE } from './translations';
|
||||
import * as i18n from './translations';
|
||||
import logo from './logo.svg';
|
||||
|
||||
export const connector: Connector = {
|
||||
export const connector: ConnectorConfiguration = {
|
||||
id: '.jira',
|
||||
name: JIRA_TITLE,
|
||||
name: i18n.JIRA_TITLE,
|
||||
logo,
|
||||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'platinum',
|
||||
fields: {
|
||||
summary: {
|
||||
label: i18n.MAPPING_FIELD_SUMMARY,
|
||||
validSourceFields: ['title', 'description'],
|
||||
defaultSourceField: 'title',
|
||||
defaultActionType: 'overwrite',
|
||||
},
|
||||
description: {
|
||||
label: i18n.MAPPING_FIELD_DESC,
|
||||
validSourceFields: ['title', 'description'],
|
||||
defaultSourceField: 'description',
|
||||
defaultActionType: 'overwrite',
|
||||
},
|
||||
comments: {
|
||||
label: i18n.MAPPING_FIELD_COMMENTS,
|
||||
validSourceFields: ['comments'],
|
||||
defaultSourceField: 'comments',
|
||||
defaultActionType: 'append',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -107,4 +107,5 @@ export const JiraConnectorFlyout = withConnectorFlyout<JiraActionConnector>({
|
|||
ConnectorFormComponent: JiraConnectorForm,
|
||||
secretKeys: ['email', 'apiToken'],
|
||||
configKeys: ['projectKey'],
|
||||
connectorActionTypeId: '.jira',
|
||||
});
|
||||
|
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 968 B After Width: | Height: | Size: 28 KiB |
|
@ -26,3 +26,10 @@ export const JIRA_PROJECT_KEY_REQUIRED = i18n.translate(
|
|||
defaultMessage: 'Project key is required',
|
||||
}
|
||||
);
|
||||
|
||||
export const MAPPING_FIELD_SUMMARY = i18n.translate(
|
||||
'xpack.siem.case.configureCases.mappingFieldSummary',
|
||||
{
|
||||
defaultMessage: 'Summary',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -12,6 +12,10 @@ import {
|
|||
JiraSecretConfigurationType,
|
||||
} from '../../../../../actions/server/builtin_action_types/jira/types';
|
||||
|
||||
export { JiraFieldsType } from '../../../../../case/common/api/connectors';
|
||||
|
||||
export * from '../types';
|
||||
|
||||
export interface JiraActionConnector {
|
||||
config: JiraPublicConfigurationType;
|
||||
secrets: JiraSecretConfigurationType;
|
||||
|
|
|
@ -4,17 +4,36 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Connector } from '../types';
|
||||
|
||||
import { SERVICENOW_TITLE } from './translations';
|
||||
import { ConnectorConfiguration } from './types';
|
||||
import * as i18n from './translations';
|
||||
import logo from './logo.svg';
|
||||
|
||||
export const connector: Connector = {
|
||||
export const connector: ConnectorConfiguration = {
|
||||
id: '.servicenow',
|
||||
name: SERVICENOW_TITLE,
|
||||
name: i18n.SERVICENOW_TITLE,
|
||||
logo,
|
||||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'platinum',
|
||||
fields: {
|
||||
short_description: {
|
||||
label: i18n.MAPPING_FIELD_SHORT_DESC,
|
||||
validSourceFields: ['title', 'description'],
|
||||
defaultSourceField: 'title',
|
||||
defaultActionType: 'overwrite',
|
||||
},
|
||||
description: {
|
||||
label: i18n.MAPPING_FIELD_DESC,
|
||||
validSourceFields: ['title', 'description'],
|
||||
defaultSourceField: 'description',
|
||||
defaultActionType: 'overwrite',
|
||||
},
|
||||
comments: {
|
||||
label: i18n.MAPPING_FIELD_COMMENTS,
|
||||
validSourceFields: ['comments'],
|
||||
defaultSourceField: 'comments',
|
||||
defaultActionType: 'append',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -80,4 +80,5 @@ const ServiceNowConnectorForm: React.FC<ConnectorFlyoutFormProps<ServiceNowActio
|
|||
export const ServiceNowConnectorFlyout = withConnectorFlyout<ServiceNowActionConnector>({
|
||||
ConnectorFormComponent: ServiceNowConnectorForm,
|
||||
secretKeys: ['username', 'password'],
|
||||
connectorActionTypeId: '.servicenow',
|
||||
});
|
||||
|
|
|
@ -21,3 +21,10 @@ export const SERVICENOW_TITLE = i18n.translate(
|
|||
defaultMessage: 'ServiceNow',
|
||||
}
|
||||
);
|
||||
|
||||
export const MAPPING_FIELD_SHORT_DESC = i18n.translate(
|
||||
'xpack.siem.case.configureCases.mappingFieldShortDescription',
|
||||
{
|
||||
defaultMessage: 'Short Description',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -12,6 +12,10 @@ import {
|
|||
ServiceNowSecretConfigurationType,
|
||||
} from '../../../../../actions/server/builtin_action_types/servicenow/types';
|
||||
|
||||
export { ServiceNowFieldsType } from '../../../../../case/common/api/connectors';
|
||||
|
||||
export * from '../types';
|
||||
|
||||
export interface ServiceNowActionConnector {
|
||||
config: ServiceNowPublicConfigurationType;
|
||||
secrets: ServiceNowSecretConfigurationType;
|
||||
|
|
|
@ -79,3 +79,17 @@ export const EMAIL_REQUIRED = i18n.translate(
|
|||
defaultMessage: 'Email is required',
|
||||
}
|
||||
);
|
||||
|
||||
export const MAPPING_FIELD_DESC = i18n.translate(
|
||||
'xpack.siem.case.configureCases.mappingFieldDescription',
|
||||
{
|
||||
defaultMessage: 'Description',
|
||||
}
|
||||
);
|
||||
|
||||
export const MAPPING_FIELD_COMMENTS = i18n.translate(
|
||||
'xpack.siem.case.configureCases.mappingFieldComments',
|
||||
{
|
||||
defaultMessage: 'Comments',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -10,8 +10,20 @@
|
|||
import { ActionType } from '../../../../triggers_actions_ui/public';
|
||||
import { ExternalIncidentServiceConfiguration } from '../../../../actions/server/builtin_action_types/case/types';
|
||||
|
||||
export interface Connector extends ActionType {
|
||||
import { ActionType as ThirdPartySupportedActions, CaseField } from '../../../../case/common/api';
|
||||
|
||||
export { ThirdPartyField as AllThirdPartyFields } from '../../../../case/common/api';
|
||||
|
||||
export interface ThirdPartyField {
|
||||
label: string;
|
||||
validSourceFields: CaseField[];
|
||||
defaultSourceField: CaseField;
|
||||
defaultActionType: ThirdPartySupportedActions;
|
||||
}
|
||||
|
||||
export interface ConnectorConfiguration extends ActionType {
|
||||
logo: string;
|
||||
fields: Record<string, ThirdPartyField>;
|
||||
}
|
||||
|
||||
export interface ActionConnector {
|
||||
|
@ -40,6 +52,7 @@ export interface ConnectorFlyoutFormProps<T> {
|
|||
|
||||
export interface ConnectorFlyoutHOCProps<T> {
|
||||
ConnectorFormComponent: React.FC<ConnectorFlyoutFormProps<T>>;
|
||||
connectorActionTypeId: string;
|
||||
configKeys?: string[];
|
||||
secretKeys?: string[];
|
||||
}
|
||||
|
|
|
@ -16,10 +16,12 @@ import {
|
|||
ActionConnectorParams,
|
||||
ActionConnectorValidationErrors,
|
||||
Optional,
|
||||
ThirdPartyField,
|
||||
} from './types';
|
||||
import { isUrlInvalid } from './validators';
|
||||
|
||||
import * as i18n from './translations';
|
||||
import { CasesConfigurationMapping } from '../../containers/case/configure/types';
|
||||
|
||||
export const createActionType = ({
|
||||
id,
|
||||
|
@ -69,3 +71,15 @@ const ConnectorParamsFields: React.FunctionComponent<ActionParamsProps<ActionCon
|
|||
const connectorParamsValidator = (actionParams: ActionConnectorParams): ValidationResult => {
|
||||
return { errors: {} };
|
||||
};
|
||||
|
||||
export const createDefaultMapping = (
|
||||
fields: Record<string, ThirdPartyField>
|
||||
): CasesConfigurationMapping[] =>
|
||||
Object.keys(fields).map(
|
||||
key =>
|
||||
({
|
||||
source: fields[key].defaultSourceField,
|
||||
target: key,
|
||||
actionType: fields[key].defaultActionType,
|
||||
} as CasesConfigurationMapping)
|
||||
);
|
||||
|
|
|
@ -61,7 +61,6 @@ describe('ClosureOptions', () => {
|
|||
test('the closure type is changed successfully', () => {
|
||||
wrapper.find('input[id="close-by-pushing"]').simulate('change');
|
||||
|
||||
expect(onChangeClosureType).toHaveBeenCalled();
|
||||
expect(onChangeClosureType).toHaveBeenCalledWith('close-by-pushing');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -69,15 +69,15 @@ describe('Connectors', () => {
|
|||
|
||||
test('the connector is changed successfully', () => {
|
||||
wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click');
|
||||
wrapper.find('button[data-test-subj="dropdown-connector-456"]').simulate('click');
|
||||
wrapper.find('button[data-test-subj="dropdown-connector-servicenow-2"]').simulate('click');
|
||||
|
||||
expect(onChangeConnector).toHaveBeenCalled();
|
||||
expect(onChangeConnector).toHaveBeenCalledWith('456');
|
||||
expect(onChangeConnector).toHaveBeenCalledWith('servicenow-2');
|
||||
});
|
||||
|
||||
test('the connector is changed successfully to none', () => {
|
||||
onChangeConnector.mockClear();
|
||||
const newWrapper = mount(<Connectors {...props} selectedConnector={'123'} />, {
|
||||
const newWrapper = mount(<Connectors {...props} selectedConnector={'servicenow-1'} />, {
|
||||
wrappingComponent: TestProviders,
|
||||
});
|
||||
|
||||
|
|
|
@ -44,8 +44,14 @@ describe('ConnectorsDropdown', () => {
|
|||
value: 'none',
|
||||
'data-test-subj': 'dropdown-connector-no-connector',
|
||||
}),
|
||||
expect.objectContaining({ value: '123', 'data-test-subj': 'dropdown-connector-123' }),
|
||||
expect.objectContaining({ value: '456', 'data-test-subj': 'dropdown-connector-456' }),
|
||||
expect.objectContaining({
|
||||
value: 'servicenow-1',
|
||||
'data-test-subj': 'dropdown-connector-servicenow-1',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
value: 'servicenow-2',
|
||||
'data-test-subj': 'dropdown-connector-servicenow-2',
|
||||
}),
|
||||
])
|
||||
);
|
||||
});
|
||||
|
@ -77,7 +83,7 @@ describe('ConnectorsDropdown', () => {
|
|||
});
|
||||
|
||||
test('it selects the correct connector', () => {
|
||||
const newWrapper = mount(<ConnectorsDropdown {...props} selectedConnector={'123'} />, {
|
||||
const newWrapper = mount(<ConnectorsDropdown {...props} selectedConnector={'servicenow-1'} />, {
|
||||
wrappingComponent: TestProviders,
|
||||
});
|
||||
|
||||
|
|
|
@ -7,10 +7,12 @@
|
|||
import React from 'react';
|
||||
import { mount, ReactWrapper } from 'enzyme';
|
||||
|
||||
import { connectorsConfiguration } from '../../../../lib/connectors/config';
|
||||
import { createDefaultMapping } from '../../../../lib/connectors/utils';
|
||||
|
||||
import { FieldMapping, FieldMappingProps } from './field_mapping';
|
||||
import { mapping } from './__mock__';
|
||||
import { FieldMappingRow } from './field_mapping_row';
|
||||
import { defaultMapping } from '../../../../lib/connectors/config';
|
||||
import { TestProviders } from '../../../../mock';
|
||||
|
||||
describe('FieldMappingRow', () => {
|
||||
|
@ -20,6 +22,7 @@ describe('FieldMappingRow', () => {
|
|||
disabled: false,
|
||||
mapping,
|
||||
onChangeMapping,
|
||||
connectorActionTypeId: '.servicenow',
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
|
@ -66,6 +69,9 @@ describe('FieldMappingRow', () => {
|
|||
wrappingComponent: TestProviders,
|
||||
});
|
||||
|
||||
const selectedConnector = connectorsConfiguration['.servicenow'];
|
||||
const defaultMapping = createDefaultMapping(selectedConnector.fields);
|
||||
|
||||
const rows = newWrapper.find(FieldMappingRow);
|
||||
rows.forEach((row, index) => {
|
||||
expect(row.prop('siemField')).toEqual(defaultMapping[index].source);
|
||||
|
|
|
@ -4,53 +4,83 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { EuiFormRow, EuiFlexItem, EuiFlexGroup, EuiSuperSelectOption } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import {
|
||||
CasesConfigurationMapping,
|
||||
ThirdPartyField,
|
||||
CaseField,
|
||||
ActionType,
|
||||
ThirdPartyField,
|
||||
} from '../../../../containers/case/configure/types';
|
||||
import { FieldMappingRow } from './field_mapping_row';
|
||||
import * as i18n from './translations';
|
||||
|
||||
import { defaultMapping } from '../../../../lib/connectors/config';
|
||||
import { connectorsConfiguration } from '../../../../lib/connectors/config';
|
||||
import { setActionTypeToMapping, setThirdPartyToMapping } from './utils';
|
||||
import {
|
||||
ThirdPartyField as ConnectorConfigurationThirdPartyField,
|
||||
AllThirdPartyFields,
|
||||
} from '../../../../lib/connectors/types';
|
||||
import { createDefaultMapping } from '../../../../lib/connectors/utils';
|
||||
|
||||
const FieldRowWrapper = styled.div`
|
||||
margin-top: 8px;
|
||||
font-size: 14px;
|
||||
`;
|
||||
|
||||
const supportedThirdPartyFields: Array<EuiSuperSelectOption<ThirdPartyField>> = [
|
||||
const actionTypeOptions: Array<EuiSuperSelectOption<ActionType>> = [
|
||||
{
|
||||
value: 'not_mapped',
|
||||
inputDisplay: <span>{i18n.FIELD_MAPPING_FIELD_NOT_MAPPED}</span>,
|
||||
'data-test-subj': 'third-party-field-not-mapped',
|
||||
value: 'nothing',
|
||||
inputDisplay: <>{i18n.FIELD_MAPPING_EDIT_NOTHING}</>,
|
||||
'data-test-subj': 'edit-update-option-nothing',
|
||||
},
|
||||
{
|
||||
value: 'short_description',
|
||||
inputDisplay: <span>{i18n.FIELD_MAPPING_FIELD_SHORT_DESC}</span>,
|
||||
'data-test-subj': 'third-party-field-short-description',
|
||||
value: 'overwrite',
|
||||
inputDisplay: <>{i18n.FIELD_MAPPING_EDIT_OVERWRITE}</>,
|
||||
'data-test-subj': 'edit-update-option-overwrite',
|
||||
},
|
||||
{
|
||||
value: 'comments',
|
||||
inputDisplay: <span>{i18n.FIELD_MAPPING_FIELD_COMMENTS}</span>,
|
||||
'data-test-subj': 'third-party-field-comments',
|
||||
},
|
||||
{
|
||||
value: 'description',
|
||||
inputDisplay: <span>{i18n.FIELD_MAPPING_FIELD_DESC}</span>,
|
||||
'data-test-subj': 'third-party-field-description',
|
||||
value: 'append',
|
||||
inputDisplay: <>{i18n.FIELD_MAPPING_EDIT_APPEND}</>,
|
||||
'data-test-subj': 'edit-update-option-append',
|
||||
},
|
||||
];
|
||||
|
||||
const getThirdPartyOptions = (
|
||||
caseField: CaseField,
|
||||
thirdPartyFields: Record<string, ConnectorConfigurationThirdPartyField>
|
||||
): Array<EuiSuperSelectOption<AllThirdPartyFields>> =>
|
||||
(Object.keys(thirdPartyFields) as AllThirdPartyFields[]).reduce<
|
||||
Array<EuiSuperSelectOption<AllThirdPartyFields>>
|
||||
>(
|
||||
(acc, key) => {
|
||||
if (thirdPartyFields[key].validSourceFields.includes(caseField)) {
|
||||
return [
|
||||
...acc,
|
||||
{
|
||||
value: key,
|
||||
inputDisplay: <span>{thirdPartyFields[key].label}</span>,
|
||||
'data-test-subj': `dropdown-mapping-${key}`,
|
||||
},
|
||||
];
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[
|
||||
{
|
||||
value: 'not_mapped',
|
||||
inputDisplay: i18n.MAPPING_FIELD_NOT_MAPPED,
|
||||
'data-test-subj': 'dropdown-mapping-not_mapped',
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
export interface FieldMappingProps {
|
||||
disabled: boolean;
|
||||
mapping: CasesConfigurationMapping[] | null;
|
||||
connectorActionTypeId: string;
|
||||
onChangeMapping: (newMapping: CasesConfigurationMapping[]) => void;
|
||||
}
|
||||
|
||||
|
@ -58,6 +88,7 @@ const FieldMappingComponent: React.FC<FieldMappingProps> = ({
|
|||
disabled,
|
||||
mapping,
|
||||
onChangeMapping,
|
||||
connectorActionTypeId,
|
||||
}) => {
|
||||
const onChangeActionType = useCallback(
|
||||
(caseField: CaseField, newActionType: ActionType) => {
|
||||
|
@ -74,6 +105,12 @@ const FieldMappingComponent: React.FC<FieldMappingProps> = ({
|
|||
},
|
||||
[mapping]
|
||||
);
|
||||
|
||||
const selectedConnector = connectorsConfiguration[connectorActionTypeId] ?? { fields: {} };
|
||||
const defaultMapping = useMemo(() => createDefaultMapping(selectedConnector.fields), [
|
||||
selectedConnector.fields,
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFormRow fullWidth data-test-subj="case-configure-field-mapping-cols">
|
||||
|
@ -92,10 +129,12 @@ const FieldMappingComponent: React.FC<FieldMappingProps> = ({
|
|||
<FieldRowWrapper data-test-subj="case-configure-field-mapping-row-wrapper">
|
||||
{(mapping ?? defaultMapping).map(item => (
|
||||
<FieldMappingRow
|
||||
key={item.source}
|
||||
key={`${item.source}`}
|
||||
id={`${item.source}`}
|
||||
disabled={disabled}
|
||||
siemField={item.source}
|
||||
thirdPartyOptions={supportedThirdPartyFields}
|
||||
thirdPartyOptions={getThirdPartyOptions(item.source, selectedConnector.fields)}
|
||||
actionTypeOptions={actionTypeOptions}
|
||||
onChangeActionType={onChangeActionType}
|
||||
onChangeThirdParty={onChangeThirdParty}
|
||||
selectedActionType={item.actionType}
|
||||
|
|
|
@ -10,7 +10,7 @@ import { EuiSuperSelectOption, EuiSuperSelect } from '@elastic/eui';
|
|||
|
||||
import { FieldMappingRow, RowProps } from './field_mapping_row';
|
||||
import { TestProviders } from '../../../../mock';
|
||||
import { ThirdPartyField } from '../../../../containers/case/configure/types';
|
||||
import { ThirdPartyField, ActionType } from '../../../../containers/case/configure/types';
|
||||
|
||||
const thirdPartyOptions: Array<EuiSuperSelectOption<ThirdPartyField>> = [
|
||||
{
|
||||
|
@ -25,15 +25,35 @@ const thirdPartyOptions: Array<EuiSuperSelectOption<ThirdPartyField>> = [
|
|||
},
|
||||
];
|
||||
|
||||
const actionTypeOptions: Array<EuiSuperSelectOption<ActionType>> = [
|
||||
{
|
||||
value: 'nothing',
|
||||
inputDisplay: <>{'Nothing'}</>,
|
||||
'data-test-subj': 'edit-update-option-nothing',
|
||||
},
|
||||
{
|
||||
value: 'overwrite',
|
||||
inputDisplay: <>{'Overwrite'}</>,
|
||||
'data-test-subj': 'edit-update-option-overwrite',
|
||||
},
|
||||
{
|
||||
value: 'append',
|
||||
inputDisplay: <>{'Append'}</>,
|
||||
'data-test-subj': 'edit-update-option-append',
|
||||
},
|
||||
];
|
||||
|
||||
describe('FieldMappingRow', () => {
|
||||
let wrapper: ReactWrapper;
|
||||
const onChangeActionType = jest.fn();
|
||||
const onChangeThirdParty = jest.fn();
|
||||
|
||||
const props: RowProps = {
|
||||
id: 'title',
|
||||
disabled: false,
|
||||
siemField: 'title',
|
||||
thirdPartyOptions,
|
||||
actionTypeOptions,
|
||||
onChangeActionType,
|
||||
onChangeThirdParty,
|
||||
selectedActionType: 'nothing',
|
||||
|
@ -47,14 +67,14 @@ describe('FieldMappingRow', () => {
|
|||
test('it renders', () => {
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="case-configure-third-party-select"]')
|
||||
.find('[data-test-subj="case-configure-third-party-select-title"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="case-configure-action-type-select"]')
|
||||
.find('[data-test-subj="case-configure-action-type-select-title"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
|
|
|
@ -14,45 +14,32 @@ import {
|
|||
} from '@elastic/eui';
|
||||
|
||||
import { capitalize } from 'lodash/fp';
|
||||
import * as i18n from './translations';
|
||||
|
||||
import {
|
||||
CaseField,
|
||||
ActionType,
|
||||
ThirdPartyField,
|
||||
} from '../../../../containers/case/configure/types';
|
||||
import { AllThirdPartyFields } from '../../../../lib/connectors/types';
|
||||
|
||||
export interface RowProps {
|
||||
id: string;
|
||||
disabled: boolean;
|
||||
siemField: CaseField;
|
||||
thirdPartyOptions: Array<EuiSuperSelectOption<ThirdPartyField>>;
|
||||
thirdPartyOptions: Array<EuiSuperSelectOption<AllThirdPartyFields>>;
|
||||
actionTypeOptions: Array<EuiSuperSelectOption<ActionType>>;
|
||||
onChangeActionType: (caseField: CaseField, newActionType: ActionType) => void;
|
||||
onChangeThirdParty: (caseField: CaseField, newThirdPartyField: ThirdPartyField) => void;
|
||||
selectedActionType: ActionType;
|
||||
selectedThirdParty: ThirdPartyField;
|
||||
}
|
||||
|
||||
const actionTypeOptions: Array<EuiSuperSelectOption<ActionType>> = [
|
||||
{
|
||||
value: 'nothing',
|
||||
inputDisplay: <>{i18n.FIELD_MAPPING_EDIT_NOTHING}</>,
|
||||
'data-test-subj': 'edit-update-option-nothing',
|
||||
},
|
||||
{
|
||||
value: 'overwrite',
|
||||
inputDisplay: <>{i18n.FIELD_MAPPING_EDIT_OVERWRITE}</>,
|
||||
'data-test-subj': 'edit-update-option-overwrite',
|
||||
},
|
||||
{
|
||||
value: 'append',
|
||||
inputDisplay: <>{i18n.FIELD_MAPPING_EDIT_APPEND}</>,
|
||||
'data-test-subj': 'edit-update-option-append',
|
||||
},
|
||||
];
|
||||
|
||||
const FieldMappingRowComponent: React.FC<RowProps> = ({
|
||||
id,
|
||||
disabled,
|
||||
siemField,
|
||||
thirdPartyOptions,
|
||||
actionTypeOptions,
|
||||
onChangeActionType,
|
||||
onChangeThirdParty,
|
||||
selectedActionType,
|
||||
|
@ -77,7 +64,7 @@ const FieldMappingRowComponent: React.FC<RowProps> = ({
|
|||
options={thirdPartyOptions}
|
||||
valueOfSelected={selectedThirdParty}
|
||||
onChange={onChangeThirdParty.bind(null, siemField)}
|
||||
data-test-subj={'case-configure-third-party-select'}
|
||||
data-test-subj={`case-configure-third-party-select-${id}`}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
|
@ -86,7 +73,7 @@ const FieldMappingRowComponent: React.FC<RowProps> = ({
|
|||
options={actionTypeOptions}
|
||||
valueOfSelected={selectedActionType}
|
||||
onChange={onChangeActionType.bind(null, siemField)}
|
||||
data-test-subj={'case-configure-action-type-select'}
|
||||
data-test-subj={`case-configure-action-type-select-${id}`}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -30,7 +30,6 @@ import {
|
|||
useCaseConfigureResponse,
|
||||
useConnectorsResponse,
|
||||
kibanaMockImplementationArgs,
|
||||
mapping,
|
||||
} from './__mock__';
|
||||
|
||||
jest.mock('../../../../lib/kibana');
|
||||
|
@ -140,13 +139,13 @@ describe('ConfigureCases', () => {
|
|||
jest.resetAllMocks();
|
||||
useCaseConfigureMock.mockImplementation(() => ({
|
||||
...useCaseConfigureResponse,
|
||||
mapping,
|
||||
mapping: connectors[0].config.casesConfiguration.mapping,
|
||||
closureType: 'close-by-user',
|
||||
connectorId: '123',
|
||||
connectorId: 'servicenow-1',
|
||||
connectorName: 'unchanged',
|
||||
currentConfiguration: {
|
||||
connectorName: 'unchanged',
|
||||
connectorId: '123',
|
||||
connectorId: 'servicenow-1',
|
||||
closureType: 'close-by-user',
|
||||
},
|
||||
}));
|
||||
|
@ -166,7 +165,7 @@ describe('ConfigureCases', () => {
|
|||
expect(wrapper.find(Connectors).prop('connectors')).toEqual(connectors);
|
||||
expect(wrapper.find(Connectors).prop('disabled')).toBe(false);
|
||||
expect(wrapper.find(Connectors).prop('isLoading')).toBe(false);
|
||||
expect(wrapper.find(Connectors).prop('selectedConnector')).toBe('123');
|
||||
expect(wrapper.find(Connectors).prop('selectedConnector')).toBe('servicenow-1');
|
||||
|
||||
// ClosureOptions
|
||||
expect(wrapper.find(ClosureOptions).prop('disabled')).toBe(false);
|
||||
|
@ -175,6 +174,7 @@ describe('ConfigureCases', () => {
|
|||
// Mapping
|
||||
expect(wrapper.find(Mapping).prop('disabled')).toBe(true);
|
||||
expect(wrapper.find(Mapping).prop('updateConnectorDisabled')).toBe(false);
|
||||
expect(wrapper.find(Mapping).prop('connectorActionTypeId')).toBe('.servicenow');
|
||||
expect(wrapper.find(Mapping).prop('mapping')).toEqual(
|
||||
connectors[0].config.casesConfiguration.mapping
|
||||
);
|
||||
|
@ -182,24 +182,12 @@ describe('ConfigureCases', () => {
|
|||
// Flyouts
|
||||
expect(wrapper.find(ConnectorAddFlyout).prop('addFlyoutVisible')).toBe(false);
|
||||
expect(wrapper.find(ConnectorAddFlyout).prop('actionTypes')).toEqual([
|
||||
{
|
||||
expect.objectContaining({
|
||||
id: '.servicenow',
|
||||
name: 'ServiceNow',
|
||||
enabled: true,
|
||||
logo: 'test-file-stub',
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'platinum',
|
||||
},
|
||||
{
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: '.jira',
|
||||
name: 'Jira',
|
||||
logo: 'test-file-stub',
|
||||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'platinum',
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(wrapper.find(ConnectorEditFlyout).prop('editFlyoutVisible')).toBe(false);
|
||||
|
@ -223,8 +211,6 @@ describe('ConfigureCases', () => {
|
|||
});
|
||||
|
||||
// TODO: When mapping is enabled the test.todo should be implemented.
|
||||
test.todo('the mapping is changed successfully when changing the third party');
|
||||
test.todo('the mapping is changed successfully when changing the action type');
|
||||
test.todo('it disables the update connector button when loading the configuration');
|
||||
|
||||
test('it disables correctly when the user cannot crud', () => {
|
||||
|
@ -274,13 +260,13 @@ describe('ConfigureCases', () => {
|
|||
test('it disables the buttons of action bar when loading connectors', () => {
|
||||
useCaseConfigureMock.mockImplementation(() => ({
|
||||
...useCaseConfigureResponse,
|
||||
mapping,
|
||||
mapping: connectors[1].config.casesConfiguration.mapping,
|
||||
closureType: 'close-by-user',
|
||||
connectorId: '456',
|
||||
connectorId: 'servicenow-2',
|
||||
connectorName: 'unchanged',
|
||||
currentConfiguration: {
|
||||
connectorName: 'unchanged',
|
||||
connectorId: '123',
|
||||
connectorId: 'servicenow-1',
|
||||
closureType: 'close-by-user',
|
||||
},
|
||||
}));
|
||||
|
@ -335,13 +321,13 @@ describe('ConfigureCases', () => {
|
|||
test('it disables the buttons of action bar when saving configuration', () => {
|
||||
useCaseConfigureMock.mockImplementation(() => ({
|
||||
...useCaseConfigureResponse,
|
||||
mapping,
|
||||
mapping: connectors[1].config.casesConfiguration.mapping,
|
||||
closureType: 'close-by-user',
|
||||
connectorId: '456',
|
||||
connectorId: 'servicenow-2',
|
||||
connectorName: 'unchanged',
|
||||
currentConfiguration: {
|
||||
connectorName: 'unchanged',
|
||||
connectorId: '123',
|
||||
connectorId: 'servicenow-1',
|
||||
closureType: 'close-by-user',
|
||||
},
|
||||
persistLoading: true,
|
||||
|
@ -369,13 +355,13 @@ describe('ConfigureCases', () => {
|
|||
test('it shows the loading spinner when saving configuration', () => {
|
||||
useCaseConfigureMock.mockImplementation(() => ({
|
||||
...useCaseConfigureResponse,
|
||||
mapping,
|
||||
mapping: connectors[1].config.casesConfiguration.mapping,
|
||||
closureType: 'close-by-user',
|
||||
connectorId: '456',
|
||||
connectorId: 'servicenow-2',
|
||||
connectorName: 'unchanged',
|
||||
currentConfiguration: {
|
||||
connectorName: 'unchanged',
|
||||
connectorId: '123',
|
||||
connectorId: 'servicenow-1',
|
||||
closureType: 'close-by-user',
|
||||
},
|
||||
persistLoading: true,
|
||||
|
@ -409,13 +395,13 @@ describe('ConfigureCases', () => {
|
|||
jest.resetAllMocks();
|
||||
useCaseConfigureMock.mockImplementation(() => ({
|
||||
...useCaseConfigureResponse,
|
||||
mapping,
|
||||
mapping: connectors[1].config.casesConfiguration.mapping,
|
||||
closureType: 'close-by-user',
|
||||
connectorId: '456',
|
||||
connectorId: 'servicenow-2',
|
||||
connectorName: 'unchanged',
|
||||
currentConfiguration: {
|
||||
connectorName: 'unchanged',
|
||||
connectorId: '123',
|
||||
connectorId: 'servicenow-1',
|
||||
closureType: 'close-by-user',
|
||||
},
|
||||
persistCaseConfigure,
|
||||
|
@ -437,7 +423,7 @@ describe('ConfigureCases', () => {
|
|||
|
||||
expect(persistCaseConfigure).toHaveBeenCalled();
|
||||
expect(persistCaseConfigure).toHaveBeenCalledWith({
|
||||
connectorId: '456',
|
||||
connectorId: 'servicenow-2',
|
||||
connectorName: 'My Connector 2',
|
||||
closureType: 'close-by-user',
|
||||
});
|
||||
|
@ -451,16 +437,17 @@ describe('ConfigureCases', () => {
|
|||
.prop('href')
|
||||
).toBe(`#/link-to/case${searchURL}`);
|
||||
});
|
||||
|
||||
test('it disables the buttons of action bar when loading configuration', () => {
|
||||
useCaseConfigureMock.mockImplementation(() => ({
|
||||
...useCaseConfigureResponse,
|
||||
mapping,
|
||||
mapping: connectors[1].config.casesConfiguration.mapping,
|
||||
closureType: 'close-by-user',
|
||||
connectorId: '456',
|
||||
connectorId: 'servicenow-2',
|
||||
connectorName: 'unchanged',
|
||||
currentConfiguration: {
|
||||
connectorName: 'unchanged',
|
||||
connectorId: '123',
|
||||
connectorId: 'servicenow-1',
|
||||
closureType: 'close-by-user',
|
||||
},
|
||||
loading: true,
|
||||
|
@ -490,13 +477,13 @@ describe('ConfigureCases', () => {
|
|||
jest.resetAllMocks();
|
||||
useCaseConfigureMock.mockImplementation(() => ({
|
||||
...useCaseConfigureResponse,
|
||||
mapping,
|
||||
mapping: connectors[1].config.casesConfiguration.mapping,
|
||||
closureType: 'close-by-user',
|
||||
connectorId: '456',
|
||||
connectorId: 'servicenow-2',
|
||||
connectorName: 'unchanged',
|
||||
currentConfiguration: {
|
||||
connectorName: 'unchanged',
|
||||
connectorId: '456',
|
||||
connectorId: 'servicenow-2',
|
||||
closureType: 'close-by-user',
|
||||
},
|
||||
}));
|
||||
|
@ -530,20 +517,20 @@ describe('ConfigureCases', () => {
|
|||
test('it tracks the changes successfully', () => {
|
||||
useCaseConfigureMock.mockImplementation(() => ({
|
||||
...useCaseConfigureResponse,
|
||||
mapping,
|
||||
mapping: connectors[1].config.casesConfiguration.mapping,
|
||||
closureType: 'close-by-user',
|
||||
connectorId: '456',
|
||||
connectorId: 'servicenow-2',
|
||||
connectorName: 'unchanged',
|
||||
currentConfiguration: {
|
||||
connectorName: 'unchanged',
|
||||
connectorId: '123',
|
||||
connectorId: 'servicenow-1',
|
||||
closureType: 'close-by-pushing',
|
||||
},
|
||||
}));
|
||||
const wrapper = mount(<ConfigureCases userCanCrud />, { wrappingComponent: TestProviders });
|
||||
wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click');
|
||||
wrapper.update();
|
||||
wrapper.find('button[data-test-subj="dropdown-connector-456"]').simulate('click');
|
||||
wrapper.find('button[data-test-subj="dropdown-connector-servicenow-2"]').simulate('click');
|
||||
wrapper.update();
|
||||
wrapper.find('input[id="close-by-pushing"]').simulate('change');
|
||||
wrapper.update();
|
||||
|
@ -558,23 +545,25 @@ describe('ConfigureCases', () => {
|
|||
.text()
|
||||
).toBe('2 unsaved changes');
|
||||
});
|
||||
|
||||
test('it tracks the changes successfully when name changes', () => {
|
||||
useCaseConfigureMock.mockImplementation(() => ({
|
||||
...useCaseConfigureResponse,
|
||||
mapping,
|
||||
mapping: connectors[1].config.casesConfiguration.mapping,
|
||||
closureType: 'close-by-user',
|
||||
connectorId: '456',
|
||||
connectorId: 'servicenow-2',
|
||||
connectorName: 'nameChange',
|
||||
currentConfiguration: {
|
||||
connectorId: '123',
|
||||
connectorId: 'servicenow-1',
|
||||
closureType: 'close-by-pushing',
|
||||
connectorName: 'before',
|
||||
},
|
||||
}));
|
||||
|
||||
const wrapper = mount(<ConfigureCases userCanCrud />, { wrappingComponent: TestProviders });
|
||||
wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click');
|
||||
wrapper.update();
|
||||
wrapper.find('button[data-test-subj="dropdown-connector-456"]').simulate('click');
|
||||
wrapper.find('button[data-test-subj="dropdown-connector-servicenow-2"]').simulate('click');
|
||||
wrapper.update();
|
||||
wrapper.find('input[id="close-by-pushing"]').simulate('change');
|
||||
wrapper.update();
|
||||
|
@ -595,7 +584,7 @@ describe('ConfigureCases', () => {
|
|||
// change settings
|
||||
wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click');
|
||||
wrapper.update();
|
||||
wrapper.find('button[data-test-subj="dropdown-connector-456"]').simulate('click');
|
||||
wrapper.find('button[data-test-subj="dropdown-connector-servicenow-2"]').simulate('click');
|
||||
wrapper.update();
|
||||
wrapper.find('input[id="close-by-pushing"]').simulate('change');
|
||||
wrapper.update();
|
||||
|
@ -603,7 +592,7 @@ describe('ConfigureCases', () => {
|
|||
// revert back to initial settings
|
||||
wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click');
|
||||
wrapper.update();
|
||||
wrapper.find('button[data-test-subj="dropdown-connector-123"]').simulate('click');
|
||||
wrapper.find('button[data-test-subj="dropdown-connector-servicenow-1"]').simulate('click');
|
||||
wrapper.update();
|
||||
wrapper.find('input[id="close-by-user"]').simulate('change');
|
||||
wrapper.update();
|
||||
|
@ -617,17 +606,17 @@ describe('ConfigureCases', () => {
|
|||
useCaseConfigureMock
|
||||
.mockImplementationOnce(() => ({
|
||||
...useCaseConfigureResponse,
|
||||
mapping,
|
||||
mapping: connectors[1].config.casesConfiguration.mapping,
|
||||
closureType: 'close-by-user',
|
||||
connectorId: '456',
|
||||
currentConfiguration: { connectorId: '456', closureType: 'close-by-user' },
|
||||
connectorId: 'servicenow-2',
|
||||
currentConfiguration: { connectorId: 'servicenow-2', closureType: 'close-by-user' },
|
||||
}))
|
||||
.mockImplementation(() => ({
|
||||
...useCaseConfigureResponse,
|
||||
mapping,
|
||||
mapping: connectors[1].config.casesConfiguration.mapping,
|
||||
closureType: 'close-by-pushing',
|
||||
connectorId: '456',
|
||||
currentConfiguration: { connectorId: '456', closureType: 'close-by-user' },
|
||||
connectorId: 'servicenow-2',
|
||||
currentConfiguration: { connectorId: 'servicenow-2', closureType: 'close-by-user' },
|
||||
}));
|
||||
const wrapper = mount(<ConfigureCases userCanCrud />, { wrappingComponent: TestProviders });
|
||||
// Change closure type
|
||||
|
@ -672,17 +661,17 @@ describe('ConfigureCases', () => {
|
|||
useCaseConfigureMock
|
||||
.mockImplementationOnce(() => ({
|
||||
...useCaseConfigureResponse,
|
||||
mapping,
|
||||
mapping: connectors[1].config.casesConfiguration.mapping,
|
||||
closureType: 'close-by-user',
|
||||
connectorId: '456',
|
||||
currentConfiguration: { connectorId: '456', closureType: 'close-by-user' },
|
||||
connectorId: 'servicenow-2',
|
||||
currentConfiguration: { connectorId: 'servicenow-2', closureType: 'close-by-user' },
|
||||
}))
|
||||
.mockImplementation(() => ({
|
||||
...useCaseConfigureResponse,
|
||||
mapping,
|
||||
mapping: connectors[1].config.casesConfiguration.mapping,
|
||||
closureType: 'close-by-pushing',
|
||||
connectorId: '456',
|
||||
currentConfiguration: { connectorId: '456', closureType: 'close-by-user' },
|
||||
connectorId: 'servicenow-2',
|
||||
currentConfiguration: { connectorId: 'servicenow-2', closureType: 'close-by-user' },
|
||||
}));
|
||||
const wrapper = mount(<ConfigureCases userCanCrud />, { wrappingComponent: TestProviders });
|
||||
|
||||
|
@ -724,22 +713,22 @@ describe('ConfigureCases', () => {
|
|||
useCaseConfigureMock
|
||||
.mockImplementationOnce(() => ({
|
||||
...useCaseConfigureResponse,
|
||||
mapping,
|
||||
mapping: connectors[0].config.casesConfiguration.mapping,
|
||||
closureType: 'close-by-user',
|
||||
connectorId: '123',
|
||||
currentConfiguration: { connectorId: '123', closureType: 'close-by-user' },
|
||||
connectorId: 'servicenow-1',
|
||||
currentConfiguration: { connectorId: 'servicenow-1', closureType: 'close-by-user' },
|
||||
}))
|
||||
.mockImplementation(() => ({
|
||||
...useCaseConfigureResponse,
|
||||
mapping,
|
||||
mapping: connectors[1].config.casesConfiguration.mapping,
|
||||
closureType: 'close-by-user',
|
||||
connectorId: '456',
|
||||
currentConfiguration: { connectorId: '123', closureType: 'close-by-user' },
|
||||
connectorId: 'servicenow-2',
|
||||
currentConfiguration: { connectorId: 'servicenow-1', closureType: 'close-by-user' },
|
||||
}));
|
||||
const wrapper = mount(<ConfigureCases userCanCrud />, { wrappingComponent: TestProviders });
|
||||
wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click');
|
||||
wrapper.update();
|
||||
wrapper.find('button[data-test-subj="dropdown-connector-456"]').simulate('click');
|
||||
wrapper.find('button[data-test-subj="dropdown-connector-servicenow-2"]').simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
expect(
|
||||
|
@ -757,17 +746,17 @@ describe('ConfigureCases', () => {
|
|||
useCaseConfigureMock
|
||||
.mockImplementationOnce(() => ({
|
||||
...useCaseConfigureResponse,
|
||||
mapping,
|
||||
mapping: connectors[1].config.casesConfiguration.mapping,
|
||||
closureType: 'close-by-user',
|
||||
connectorId: '456',
|
||||
currentConfiguration: { connectorId: '456', closureType: 'close-by-user' },
|
||||
connectorId: 'servicenow-2',
|
||||
currentConfiguration: { connectorId: 'servicenow-2', closureType: 'close-by-user' },
|
||||
}))
|
||||
.mockImplementation(() => ({
|
||||
...useCaseConfigureResponse,
|
||||
mapping,
|
||||
mapping: connectors[1].config.casesConfiguration.mapping,
|
||||
closureType: 'close-by-pushing',
|
||||
connectorId: '456',
|
||||
currentConfiguration: { connectorId: '456', closureType: 'close-by-user' },
|
||||
connectorId: 'servicenow-2',
|
||||
currentConfiguration: { connectorId: 'servicenow-2', closureType: 'close-by-user' },
|
||||
}));
|
||||
const wrapper = mount(<ConfigureCases userCanCrud />, { wrappingComponent: TestProviders });
|
||||
wrapper.find('input[id="close-by-pushing"]').simulate('change');
|
||||
|
@ -788,5 +777,30 @@ describe('ConfigureCases', () => {
|
|||
wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists()
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
test('it sets the mapping correctly when changing connector types', () => {
|
||||
useCaseConfigureMock.mockImplementation(() => ({
|
||||
...useCaseConfigureResponse,
|
||||
mapping: connectors[2].config.casesConfiguration.mapping,
|
||||
closureType: 'close-by-user',
|
||||
connectorId: 'jira-1',
|
||||
connectorName: 'unchanged',
|
||||
currentConfiguration: {
|
||||
connectorName: 'unchanged',
|
||||
connectorId: 'servicenow-1',
|
||||
closureType: 'close-by-user',
|
||||
},
|
||||
persistLoading: false,
|
||||
}));
|
||||
|
||||
const wrapper = mount(<ConfigureCases userCanCrud />, { wrappingComponent: TestProviders });
|
||||
expect(
|
||||
wrapper.find('button[data-test-subj="case-configure-third-party-select-title"]').text()
|
||||
).toBe('Summary');
|
||||
});
|
||||
|
||||
// TODO: When mapping is enabled the test.todo should be implemented.
|
||||
test.todo('the mapping is changed successfully when changing the third party');
|
||||
test.todo('the mapping is changed successfully when changing the action type');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useState, Dispatch, SetStateAction } from 'react';
|
||||
import React, { useCallback, useEffect, useState, Dispatch, SetStateAction, useMemo } from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
import {
|
||||
|
@ -200,6 +200,11 @@ const ConfigureCasesComponent: React.FC<ConfigureCasesComponentProps> = ({ userC
|
|||
currentConfiguration.closureType,
|
||||
]);
|
||||
|
||||
const connectorActionTypeId = useMemo(
|
||||
() => connectors.find(c => c.id === connectorId)?.actionTypeId ?? '.none',
|
||||
[connectorId, connectors]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormWrapper>
|
||||
{!connectorIsValid && (
|
||||
|
@ -236,6 +241,7 @@ const ConfigureCasesComponent: React.FC<ConfigureCasesComponentProps> = ({ userC
|
|||
disabled
|
||||
updateConnectorDisabled={updateConnectorDisabled || !userCanCrud}
|
||||
mapping={mapping}
|
||||
connectorActionTypeId={connectorActionTypeId}
|
||||
onChangeMapping={setMapping}
|
||||
setEditFlyoutVisibility={onClickUpdateConnector}
|
||||
/>
|
||||
|
|
|
@ -21,45 +21,264 @@ describe('Mapping', () => {
|
|||
updateConnectorDisabled: false,
|
||||
onChangeMapping,
|
||||
setEditFlyoutVisibility,
|
||||
connectorActionTypeId: '.servicenow',
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
wrapper = mount(<Mapping {...props} />, { wrappingComponent: TestProviders });
|
||||
});
|
||||
|
||||
test('it shows mapping form group', () => {
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="case-mapping-form-group"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
afterEach(() => {
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test('it shows mapping form row', () => {
|
||||
expect(
|
||||
describe('Common', () => {
|
||||
test('it shows mapping form group', () => {
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="case-mapping-form-group"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('it shows mapping form row', () => {
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="case-mapping-form-row"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('it shows the update button', () => {
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="case-mapping-update-connector-button"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('it shows the field mapping', () => {
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="case-mapping-field"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('it updates thirdParty correctly', () => {
|
||||
wrapper
|
||||
.find('[data-test-subj="case-mapping-form-row"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
.find('button[data-test-subj="case-configure-third-party-select-title"]')
|
||||
.simulate('click');
|
||||
wrapper.update();
|
||||
wrapper.find('button[data-test-subj="dropdown-mapping-description"]').simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
expect(onChangeMapping).toHaveBeenCalledWith([
|
||||
{ source: 'title', target: 'description', actionType: 'overwrite' },
|
||||
{ source: 'description', target: 'not_mapped', actionType: 'append' },
|
||||
{ source: 'comments', target: 'comments', actionType: 'append' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('it updates actionType correctly', () => {
|
||||
wrapper
|
||||
.find('button[data-test-subj="case-configure-action-type-select-title"]')
|
||||
.simulate('click');
|
||||
wrapper.update();
|
||||
wrapper.find('button[data-test-subj="edit-update-option-nothing"]').simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
expect(onChangeMapping).toHaveBeenCalledWith([
|
||||
{ source: 'title', target: 'short_description', actionType: 'nothing' },
|
||||
{ source: 'description', target: 'description', actionType: 'append' },
|
||||
{ source: 'comments', target: 'comments', actionType: 'append' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('it shows the correct action types', () => {
|
||||
wrapper
|
||||
.find('button[data-test-subj="case-configure-action-type-select-title"]')
|
||||
.simulate('click');
|
||||
wrapper.update();
|
||||
expect(
|
||||
wrapper
|
||||
.find('button[data-test-subj="edit-update-option-nothing"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
wrapper
|
||||
.find('button[data-test-subj="edit-update-option-overwrite"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
wrapper
|
||||
.find('button[data-test-subj="edit-update-option-append"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
test('it shows the update button', () => {
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="case-mapping-update-connector-button"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
});
|
||||
describe('Connectors', () => {
|
||||
describe('ServiceNow', () => {
|
||||
test('it shows the correct thirdParty fields for title', () => {
|
||||
wrapper
|
||||
.find('button[data-test-subj="case-configure-third-party-select-title"]')
|
||||
.simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
test('it shows the field mapping', () => {
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="case-mapping-field"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
expect(
|
||||
wrapper
|
||||
.find('button[data-test-subj="dropdown-mapping-short_description"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
wrapper
|
||||
.find('button[data-test-subj="dropdown-mapping-description"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
wrapper
|
||||
.find('button[data-test-subj="dropdown-mapping-not_mapped"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test('it shows the correct thirdParty fields for description', () => {
|
||||
wrapper
|
||||
.find('button[data-test-subj="case-configure-third-party-select-description"]')
|
||||
.simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('button[data-test-subj="dropdown-mapping-short_description"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
wrapper
|
||||
.find('button[data-test-subj="dropdown-mapping-description"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
wrapper
|
||||
.find('button[data-test-subj="dropdown-mapping-not_mapped"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test('it shows the correct thirdParty fields for comments', () => {
|
||||
wrapper
|
||||
.find('button[data-test-subj="case-configure-third-party-select-comments"]')
|
||||
.simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('button[data-test-subj="dropdown-mapping-comments"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
wrapper
|
||||
.find('button[data-test-subj="dropdown-mapping-not_mapped"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Jira', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = mount(<Mapping {...props} connectorActionTypeId={'.jira'} />, {
|
||||
wrappingComponent: TestProviders,
|
||||
});
|
||||
});
|
||||
|
||||
test('it shows the correct thirdParty fields for title', () => {
|
||||
wrapper
|
||||
.find('button[data-test-subj="case-configure-third-party-select-title"]')
|
||||
.simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('button[data-test-subj="dropdown-mapping-summary"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
wrapper
|
||||
.find('button[data-test-subj="dropdown-mapping-description"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
wrapper
|
||||
.find('button[data-test-subj="dropdown-mapping-not_mapped"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test('it shows the correct thirdParty fields for description', () => {
|
||||
wrapper
|
||||
.find('button[data-test-subj="case-configure-third-party-select-description"]')
|
||||
.simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('button[data-test-subj="dropdown-mapping-summary"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
wrapper
|
||||
.find('button[data-test-subj="dropdown-mapping-description"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
wrapper
|
||||
.find('button[data-test-subj="dropdown-mapping-not_mapped"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test('it shows the correct thirdParty fields for comments', () => {
|
||||
wrapper
|
||||
.find('button[data-test-subj="case-configure-third-party-select-comments"]')
|
||||
.simulate('click');
|
||||
wrapper.update();
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('button[data-test-subj="dropdown-mapping-comments"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
wrapper
|
||||
.find('button[data-test-subj="dropdown-mapping-not_mapped"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -24,6 +24,7 @@ export interface MappingProps {
|
|||
disabled: boolean;
|
||||
updateConnectorDisabled: boolean;
|
||||
mapping: CasesConfigurationMapping[] | null;
|
||||
connectorActionTypeId: string;
|
||||
onChangeMapping: (newMapping: CasesConfigurationMapping[]) => void;
|
||||
setEditFlyoutVisibility: () => void;
|
||||
}
|
||||
|
@ -39,6 +40,7 @@ const MappingComponent: React.FC<MappingProps> = ({
|
|||
mapping,
|
||||
onChangeMapping,
|
||||
setEditFlyoutVisibility,
|
||||
connectorActionTypeId,
|
||||
}) => {
|
||||
return (
|
||||
<EuiDescribedFormGroup
|
||||
|
@ -62,6 +64,7 @@ const MappingComponent: React.FC<MappingProps> = ({
|
|||
</EuiFormRow>
|
||||
<FieldMapping
|
||||
disabled={disabled}
|
||||
connectorActionTypeId={connectorActionTypeId}
|
||||
mapping={mapping}
|
||||
onChangeMapping={onChangeMapping}
|
||||
data-test-subj="case-mapping-field"
|
||||
|
|
|
@ -159,34 +159,13 @@ export const WARNING_NO_CONNECTOR_MESSAGE = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const FIELD_MAPPING_FIELD_NOT_MAPPED = i18n.translate(
|
||||
'xpack.siem.case.configureCases.fieldMappingFieldNotMapped',
|
||||
export const MAPPING_FIELD_NOT_MAPPED = i18n.translate(
|
||||
'xpack.siem.case.configureCases.mappingFieldNotMapped',
|
||||
{
|
||||
defaultMessage: 'Not mapped',
|
||||
}
|
||||
);
|
||||
|
||||
export const FIELD_MAPPING_FIELD_SHORT_DESC = i18n.translate(
|
||||
'xpack.siem.case.configureCases.fieldMappingFieldShortDescription',
|
||||
{
|
||||
defaultMessage: 'Short Description',
|
||||
}
|
||||
);
|
||||
|
||||
export const FIELD_MAPPING_FIELD_DESC = i18n.translate(
|
||||
'xpack.siem.case.configureCases.fieldMappingFieldDescription',
|
||||
{
|
||||
defaultMessage: 'Description',
|
||||
}
|
||||
);
|
||||
|
||||
export const FIELD_MAPPING_FIELD_COMMENTS = i18n.translate(
|
||||
'xpack.siem.case.configureCases.fieldMappingFieldComments',
|
||||
{
|
||||
defaultMessage: 'Comments',
|
||||
}
|
||||
);
|
||||
|
||||
export const UPDATE_CONNECTOR = i18n.translate('xpack.siem.case.configureCases.updateConnector', {
|
||||
defaultMessage: 'Update connector',
|
||||
});
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
CaseField,
|
||||
ActionType,
|
||||
|
|
|
@ -13190,10 +13190,6 @@
|
|||
"xpack.siem.case.configureCases.fieldMappingEditAppend": "末尾に追加",
|
||||
"xpack.siem.case.configureCases.fieldMappingEditNothing": "何もしない",
|
||||
"xpack.siem.case.configureCases.fieldMappingEditOverwrite": "上書き",
|
||||
"xpack.siem.case.configureCases.fieldMappingFieldComments": "コメント",
|
||||
"xpack.siem.case.configureCases.fieldMappingFieldDescription": "説明",
|
||||
"xpack.siem.case.configureCases.fieldMappingFieldNotMapped": "マップされません",
|
||||
"xpack.siem.case.configureCases.fieldMappingFieldShortDescription": "短い説明",
|
||||
"xpack.siem.case.configureCases.fieldMappingFirstCol": "SIEM ケースフィールド",
|
||||
"xpack.siem.case.configureCases.fieldMappingSecondCol": "サードパーティインシデントフィールド",
|
||||
"xpack.siem.case.configureCases.fieldMappingThirdCol": "編集時と更新時",
|
||||
|
|
|
@ -13197,10 +13197,6 @@
|
|||
"xpack.siem.case.configureCases.fieldMappingEditAppend": "追加",
|
||||
"xpack.siem.case.configureCases.fieldMappingEditNothing": "无内容",
|
||||
"xpack.siem.case.configureCases.fieldMappingEditOverwrite": "覆盖",
|
||||
"xpack.siem.case.configureCases.fieldMappingFieldComments": "注释",
|
||||
"xpack.siem.case.configureCases.fieldMappingFieldDescription": "描述",
|
||||
"xpack.siem.case.configureCases.fieldMappingFieldNotMapped": "未映射",
|
||||
"xpack.siem.case.configureCases.fieldMappingFieldShortDescription": "简短描述",
|
||||
"xpack.siem.case.configureCases.fieldMappingFirstCol": "SIEM 案例字段",
|
||||
"xpack.siem.case.configureCases.fieldMappingSecondCol": "第三方事件字段",
|
||||
"xpack.siem.case.configureCases.fieldMappingThirdCol": "编辑和更新时",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue