[SIEM][CASE] Dynamic fields mapping based on connector (#64412)

This commit is contained in:
Christos Nasikas 2020-05-05 16:32:23 +03:00 committed by GitHub
parent 7cca2dacf0
commit 7d15c2103c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 662 additions and 239 deletions

View file

@ -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,

View 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';

View 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>;

View 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>;

View file

@ -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 = {

View file

@ -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}
/>

View file

@ -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',
},
];

View file

@ -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',
},
},
};

View file

@ -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

Before After
Before After

View file

@ -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',
}
);

View file

@ -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;

View file

@ -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',
},
},
};

View file

@ -80,4 +80,5 @@ const ServiceNowConnectorForm: React.FC<ConnectorFlyoutFormProps<ServiceNowActio
export const ServiceNowConnectorFlyout = withConnectorFlyout<ServiceNowActionConnector>({
ConnectorFormComponent: ServiceNowConnectorForm,
secretKeys: ['username', 'password'],
connectorActionTypeId: '.servicenow',
});

View file

@ -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',
}
);

View file

@ -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;

View file

@ -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',
}
);

View file

@ -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[];
}

View file

@ -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)
);

View file

@ -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');
});
});

View file

@ -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,
});

View file

@ -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,
});

View file

@ -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);

View file

@ -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}

View file

@ -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);

View file

@ -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>

View file

@ -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');
});
});

View file

@ -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}
/>

View file

@ -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();
});
});
});
});

View file

@ -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"

View file

@ -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',
});

View file

@ -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,

View file

@ -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": "編集時と更新時",

View file

@ -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": "编辑和更新时",