From a1a55ad071a7a6cf385cf2fdb398684436ca4f14 Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Thu, 23 Apr 2020 15:52:13 +0300 Subject: [PATCH 01/10] Create connector's configuration --- .../case/common/api/cases/configure.ts | 3 +- .../case/common/api/connectors/index.ts | 8 ++ .../case/common/api/connectors/jira.ts | 15 ++++ .../case/common/api/connectors/servicenow.ts | 15 ++++ .../siem/public/lib/connectors/config.ts | 23 +---- .../siem/public/lib/connectors/jira/config.ts | 24 +++++- .../siem/public/lib/connectors/jira/types.ts | 4 + .../lib/connectors/servicenow/config.ts | 25 +++++- .../public/lib/connectors/servicenow/types.ts | 4 + .../siem/public/lib/connectors/types.ts | 22 ++++- .../configure_cases/field_mapping.tsx | 84 ++++++++++++++----- .../configure_cases/field_mapping_row.tsx | 25 ++---- .../case/components/configure_cases/utils.ts | 15 +++- 13 files changed, 198 insertions(+), 69 deletions(-) create mode 100644 x-pack/plugins/case/common/api/connectors/index.ts create mode 100644 x-pack/plugins/case/common/api/connectors/jira.ts create mode 100644 x-pack/plugins/case/common/api/connectors/servicenow.ts diff --git a/x-pack/plugins/case/common/api/cases/configure.ts b/x-pack/plugins/case/common/api/cases/configure.ts index d92af587d0e92..39fca95220dea 100644 --- a/x-pack/plugins/case/common/api/cases/configure.ts +++ b/x-pack/plugins/case/common/api/cases/configure.ts @@ -11,7 +11,7 @@ import { UserRT } from '../user'; /* * 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] * */ @@ -32,6 +32,7 @@ const ThirdPartyFieldRT = rt.union([ rt.literal('description'), rt.literal('not_mapped'), rt.literal('short_description'), + rt.literal('summary'), ]); export const CasesConfigurationMapsRT = rt.type({ diff --git a/x-pack/plugins/case/common/api/connectors/index.ts b/x-pack/plugins/case/common/api/connectors/index.ts new file mode 100644 index 0000000000000..c1fc284c938b7 --- /dev/null +++ b/x-pack/plugins/case/common/api/connectors/index.ts @@ -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'; diff --git a/x-pack/plugins/case/common/api/connectors/jira.ts b/x-pack/plugins/case/common/api/connectors/jira.ts new file mode 100644 index 0000000000000..4ffd3b6e7d01f --- /dev/null +++ b/x-pack/plugins/case/common/api/connectors/jira.ts @@ -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'; + +const JiraFieldsRT = rt.union([ + rt.literal('summary'), + rt.literal('description'), + rt.literal('comments'), +]); + +export type JiraFieldsType = rt.TypeOf; diff --git a/x-pack/plugins/case/common/api/connectors/servicenow.ts b/x-pack/plugins/case/common/api/connectors/servicenow.ts new file mode 100644 index 0000000000000..551f96e1ab8e2 --- /dev/null +++ b/x-pack/plugins/case/common/api/connectors/servicenow.ts @@ -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'; + +const ServiceNowFieldsRT = rt.union([ + rt.literal('short_description'), + rt.literal('description'), + rt.literal('comments'), +]); + +export type ServiceNowFieldsType = rt.TypeOf; diff --git a/x-pack/plugins/siem/public/lib/connectors/config.ts b/x-pack/plugins/siem/public/lib/connectors/config.ts index 98473e49622a9..24553e9178ef5 100644 --- a/x-pack/plugins/siem/public/lib/connectors/config.ts +++ b/x-pack/plugins/siem/public/lib/connectors/config.ts @@ -4,31 +4,10 @@ * 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'; -export const connectorsConfiguration: Record = { +export const connectorsConfiguration = { '.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', - }, -]; diff --git a/x-pack/plugins/siem/public/lib/connectors/jira/config.ts b/x-pack/plugins/siem/public/lib/connectors/jira/config.ts index 42bd1b9cdc191..c07dc6b10bdc7 100644 --- a/x-pack/plugins/siem/public/lib/connectors/jira/config.ts +++ b/x-pack/plugins/siem/public/lib/connectors/jira/config.ts @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Connector } from '../types'; +import { ConnectorConfiguration, JiraFieldsType } from './types'; import { JIRA_TITLE } from './translations'; import logo from './logo.svg'; -export const connector: Connector = { +export const connector: ConnectorConfiguration = { id: '.jira', name: JIRA_TITLE, logo, @@ -17,4 +17,24 @@ export const connector: Connector = { enabledInConfig: true, enabledInLicense: true, minimumLicenseRequired: 'platinum', + fields: { + summary: { + label: 'Summary', + validSourceFields: ['title', 'description'], + defaultSourceField: 'title', + defaultActionType: 'overwrite', + }, + description: { + label: 'Description', + validSourceFields: ['title', 'description'], + defaultSourceField: 'description', + defaultActionType: 'overwrite', + }, + comments: { + label: 'Comments', + validSourceFields: ['comments'], + defaultSourceField: 'comments', + defaultActionType: 'append', + }, + }, }; diff --git a/x-pack/plugins/siem/public/lib/connectors/jira/types.ts b/x-pack/plugins/siem/public/lib/connectors/jira/types.ts index 13e4e8f6a289e..beba07a4af04f 100644 --- a/x-pack/plugins/siem/public/lib/connectors/jira/types.ts +++ b/x-pack/plugins/siem/public/lib/connectors/jira/types.ts @@ -12,6 +12,10 @@ import { JiraSecretConfigurationType, } from '../../../../../actions/server/builtin_action_types/jira/types'; +export { JiraFieldsType } from '../../../../../../../plugins/case/common/api/connectors'; + +export * from '../types'; + export interface JiraActionConnector { config: JiraPublicConfigurationType; secrets: JiraSecretConfigurationType; diff --git a/x-pack/plugins/siem/public/lib/connectors/servicenow/config.ts b/x-pack/plugins/siem/public/lib/connectors/servicenow/config.ts index 7bc1b117b3422..379106516d6ee 100644 --- a/x-pack/plugins/siem/public/lib/connectors/servicenow/config.ts +++ b/x-pack/plugins/siem/public/lib/connectors/servicenow/config.ts @@ -4,12 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Connector } from '../types'; - +import { ConnectorConfiguration, ServiceNowFieldsType } from './types'; import { SERVICENOW_TITLE } from './translations'; import logo from './logo.svg'; -export const connector: Connector = { +export const connector: ConnectorConfiguration = { id: '.servicenow', name: SERVICENOW_TITLE, logo, @@ -17,4 +16,24 @@ export const connector: Connector = { enabledInConfig: true, enabledInLicense: true, minimumLicenseRequired: 'platinum', + fields: { + short_description: { + label: 'Short Description', + validSourceFields: ['title', 'description'], + defaultSourceField: 'title', + defaultActionType: 'overwrite', + }, + description: { + label: 'Description', + validSourceFields: ['title', 'description'], + defaultSourceField: 'description', + defaultActionType: 'overwrite', + }, + comments: { + label: 'Comments', + validSourceFields: ['comments'], + defaultSourceField: 'comments', + defaultActionType: 'append', + }, + }, }; diff --git a/x-pack/plugins/siem/public/lib/connectors/servicenow/types.ts b/x-pack/plugins/siem/public/lib/connectors/servicenow/types.ts index b7f0e79eb37e3..89005d7e502be 100644 --- a/x-pack/plugins/siem/public/lib/connectors/servicenow/types.ts +++ b/x-pack/plugins/siem/public/lib/connectors/servicenow/types.ts @@ -12,6 +12,10 @@ import { ServiceNowSecretConfigurationType, } from '../../../../../actions/server/builtin_action_types/servicenow/types'; +export { ServiceNowFieldsType } from '../../../../../../../plugins/case/common/api/connectors'; + +export * from '../types'; + export interface ServiceNowActionConnector { config: ServiceNowPublicConfigurationType; secrets: ServiceNowSecretConfigurationType; diff --git a/x-pack/plugins/siem/public/lib/connectors/types.ts b/x-pack/plugins/siem/public/lib/connectors/types.ts index 9af60f4995e54..546be9fa47b5a 100644 --- a/x-pack/plugins/siem/public/lib/connectors/types.ts +++ b/x-pack/plugins/siem/public/lib/connectors/types.ts @@ -10,8 +10,28 @@ import { ActionType } from '../../../../triggers_actions_ui/public'; import { ExternalIncidentServiceConfiguration } from '../../../../actions/server/builtin_action_types/case/types'; -export interface Connector extends ActionType { +import { + ServiceNowFieldsType, + JiraFieldsType, +} from '../../../../../../plugins/case/common/api/connectors'; + +import { + ActionType as ThirdPartySupportedActions, + CaseField, +} from '../../../../../../plugins/case/common/api'; + +export type AllThirdPartyFields = ServiceNowFieldsType | JiraFieldsType; + +export interface ThirdPartyField { + label: string; + validSourceFields: CaseField[]; + defaultSourceField: CaseField; + defaultActionType: ThirdPartySupportedActions; +} + +export interface ConnectorConfiguration extends ActionType { logo: string; + fields: Record; } export interface ActionConnector { diff --git a/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx b/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx index 2934b1056e29c..9e137b9fcc143 100644 --- a/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx +++ b/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx @@ -10,44 +10,85 @@ 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 { setActionTypeToMapping, setThirdPartyToMapping } from './utils'; +import { connectorsConfiguration } from '../../../../lib/connectors/config'; +import { setActionTypeToMapping, setThirdPartyToMapping, createDefaultMapping } from './utils'; +import { + ThirdPartyField as ConnectorConfigurationThirdPartyField, + AllThirdPartyFields, +} from '../../../../lib/connectors/types'; const FieldRowWrapper = styled.div` margin-top: 8px; font-size: 14px; `; -const supportedThirdPartyFields: Array> = [ - { - value: 'not_mapped', - inputDisplay: {i18n.FIELD_MAPPING_FIELD_NOT_MAPPED}, - 'data-test-subj': 'third-party-field-not-mapped', - }, +// const supportedThirdPartyFields: Array> = [ +// { +// value: 'not_mapped', +// inputDisplay: {i18n.FIELD_MAPPING_FIELD_NOT_MAPPED}, +// 'data-test-subj': 'third-party-field-not-mapped', +// }, +// { +// value: 'short_description', +// inputDisplay: {i18n.FIELD_MAPPING_FIELD_SHORT_DESC}, +// 'data-test-subj': 'third-party-field-short-description', +// }, +// { +// value: 'comments', +// inputDisplay: {i18n.FIELD_MAPPING_FIELD_COMMENTS}, +// 'data-test-subj': 'third-party-field-comments', +// }, +// { +// value: 'description', +// inputDisplay: {i18n.FIELD_MAPPING_FIELD_DESC}, +// 'data-test-subj': 'third-party-field-description', +// }, +// ]; + +const actionTypeOptions: Array> = [ { - value: 'short_description', - inputDisplay: {i18n.FIELD_MAPPING_FIELD_SHORT_DESC}, - 'data-test-subj': 'third-party-field-short-description', + value: 'nothing', + inputDisplay: <>{i18n.FIELD_MAPPING_EDIT_NOTHING}, + 'data-test-subj': 'edit-update-option-nothing', }, { - value: 'comments', - inputDisplay: {i18n.FIELD_MAPPING_FIELD_COMMENTS}, - 'data-test-subj': 'third-party-field-comments', + value: 'overwrite', + inputDisplay: <>{i18n.FIELD_MAPPING_EDIT_OVERWRITE}, + 'data-test-subj': 'edit-update-option-overwrite', }, { - value: 'description', - inputDisplay: {i18n.FIELD_MAPPING_FIELD_DESC}, - 'data-test-subj': 'third-party-field-description', + value: 'append', + inputDisplay: <>{i18n.FIELD_MAPPING_EDIT_APPEND}, + 'data-test-subj': 'edit-update-option-append', }, ]; +const createSuperSelectOptions = ( + options: Array<{ key: T; label: string }> +): Array> => { + return options.map(option => ({ + value: option.key, + inputDisplay: {option.label}, + })); +}; + +const getThirdPartyOptions = ( + caseField: CaseField, + thirdPartyFields: Record +) => + createSuperSelectOptions( + Object.keys(thirdPartyFields) + .filter(key => thirdPartyFields[key].validSourceFields.includes(caseField)) + .map(key => ({ key: key as AllThirdPartyFields, label: thirdPartyFields[key].label })) + ); + export interface FieldMappingProps { disabled: boolean; mapping: CasesConfigurationMapping[] | null; @@ -74,6 +115,10 @@ const FieldMappingComponent: React.FC = ({ }, [mapping] ); + + const serviceNow = connectorsConfiguration['.servicenow']; + const defaultMapping = createDefaultMapping(serviceNow.fields); + return ( <> @@ -95,7 +140,8 @@ const FieldMappingComponent: React.FC = ({ key={item.source} disabled={disabled} siemField={item.source} - thirdPartyOptions={supportedThirdPartyFields} + thirdPartyOptions={getThirdPartyOptions(item.source, serviceNow.fields)} + actionTypeOptions={actionTypeOptions} onChangeActionType={onChangeActionType} onChangeThirdParty={onChangeThirdParty} selectedActionType={item.actionType} diff --git a/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.tsx b/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.tsx index 732a11a58d35a..87223b0e40f8e 100644 --- a/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.tsx +++ b/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.tsx @@ -14,45 +14,30 @@ 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 { disabled: boolean; siemField: CaseField; - thirdPartyOptions: Array>; + thirdPartyOptions: Array>; + actionTypeOptions: Array>; onChangeActionType: (caseField: CaseField, newActionType: ActionType) => void; onChangeThirdParty: (caseField: CaseField, newThirdPartyField: ThirdPartyField) => void; selectedActionType: ActionType; selectedThirdParty: ThirdPartyField; } -const actionTypeOptions: Array> = [ - { - 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 = ({ disabled, siemField, thirdPartyOptions, + actionTypeOptions, onChangeActionType, onChangeThirdParty, selectedActionType, diff --git a/x-pack/plugins/siem/public/pages/case/components/configure_cases/utils.ts b/x-pack/plugins/siem/public/pages/case/components/configure_cases/utils.ts index 2ac6cc1a38587..ba8b42847f825 100644 --- a/x-pack/plugins/siem/public/pages/case/components/configure_cases/utils.ts +++ b/x-pack/plugins/siem/public/pages/case/components/configure_cases/utils.ts @@ -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, @@ -11,6 +10,8 @@ import { ThirdPartyField, } from '../../../../containers/case/configure/types'; +import { ThirdPartyField as ConnectorConfigurationThirdPartyField } from '../../../../lib/connectors/types'; + export const setActionTypeToMapping = ( caseField: CaseField, newActionType: ActionType, @@ -42,3 +43,15 @@ export const setThirdPartyToMapping = ( } return item; }); + +export const createDefaultMapping = ( + fields: Record +): CasesConfigurationMapping[] => + Object.keys(fields).map( + key => + ({ + source: fields[key].defaultSourceField, + target: key, + actionType: fields[key].defaultActionType, + } as CasesConfigurationMapping) + ); From c894936aadcb6c6355d815ee8cb98a2ec08942a6 Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Fri, 24 Apr 2020 12:37:29 +0300 Subject: [PATCH 02/10] Change mapping based on connector --- .../components/connector_flyout/index.tsx | 7 +++++-- .../plugins/siem/public/lib/connectors/config.ts | 3 ++- .../siem/public/lib/connectors/jira/config.ts | 4 ++-- .../siem/public/lib/connectors/jira/flyout.tsx | 3 +++ .../public/lib/connectors/servicenow/config.ts | 4 ++-- .../public/lib/connectors/servicenow/flyout.tsx | 3 +++ .../plugins/siem/public/lib/connectors/types.ts | 5 +++-- .../plugins/siem/public/lib/connectors/utils.ts | 14 ++++++++++++++ .../components/configure_cases/field_mapping.tsx | 15 ++++++++++----- .../case/components/configure_cases/index.tsx | 6 ++++++ .../case/components/configure_cases/mapping.tsx | 3 +++ .../case/components/configure_cases/utils.ts | 14 -------------- 12 files changed, 53 insertions(+), 28 deletions(-) diff --git a/x-pack/plugins/siem/public/lib/connectors/components/connector_flyout/index.tsx b/x-pack/plugins/siem/public/lib/connectors/components/connector_flyout/index.tsx index c5a35da56284d..10b1e75c6ea84 100644 --- a/x-pack/plugins/siem/public/lib/connectors/components/connector_flyout/index.tsx +++ b/x-pack/plugins/siem/public/lib/connectors/components/connector_flyout/index.tsx @@ -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 = ({ ConnectorFormComponent, + connectorActionTypeId, secretKeys = [], configKeys = [], }: ConnectorFlyoutHOCProps) => { @@ -56,7 +58,7 @@ export const withConnectorFlyout = ({ if (isEmpty(mapping)) { editActionConfig('casesConfiguration', { ...action.config.casesConfiguration, - mapping: defaultMapping, + mapping: createDefaultMapping(connectorsConfiguration[connectorActionTypeId].fields), }); } @@ -135,6 +137,7 @@ export const withConnectorFlyout = ({ diff --git a/x-pack/plugins/siem/public/lib/connectors/config.ts b/x-pack/plugins/siem/public/lib/connectors/config.ts index 24553e9178ef5..d8b55665f7768 100644 --- a/x-pack/plugins/siem/public/lib/connectors/config.ts +++ b/x-pack/plugins/siem/public/lib/connectors/config.ts @@ -6,8 +6,9 @@ import { connector as serviceNowConnectorConfig } from './servicenow/config'; import { connector as jiraConnectorConfig } from './jira/config'; +import { ConnectorConfiguration } from './types'; -export const connectorsConfiguration = { +export const connectorsConfiguration: Record = { '.servicenow': serviceNowConnectorConfig, '.jira': jiraConnectorConfig, }; diff --git a/x-pack/plugins/siem/public/lib/connectors/jira/config.ts b/x-pack/plugins/siem/public/lib/connectors/jira/config.ts index c07dc6b10bdc7..229feaf5f90ee 100644 --- a/x-pack/plugins/siem/public/lib/connectors/jira/config.ts +++ b/x-pack/plugins/siem/public/lib/connectors/jira/config.ts @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ConnectorConfiguration, JiraFieldsType } from './types'; +import { ConnectorConfiguration } from './types'; import { JIRA_TITLE } from './translations'; import logo from './logo.svg'; -export const connector: ConnectorConfiguration = { +export const connector: ConnectorConfiguration = { id: '.jira', name: JIRA_TITLE, logo, diff --git a/x-pack/plugins/siem/public/lib/connectors/jira/flyout.tsx b/x-pack/plugins/siem/public/lib/connectors/jira/flyout.tsx index 482808fca53b1..a2e3ba2471cc1 100644 --- a/x-pack/plugins/siem/public/lib/connectors/jira/flyout.tsx +++ b/x-pack/plugins/siem/public/lib/connectors/jira/flyout.tsx @@ -17,6 +17,8 @@ import * as i18n from './translations'; import { ConnectorFlyoutFormProps } from '../types'; import { JiraActionConnector } from './types'; import { withConnectorFlyout } from '../components/connector_flyout'; +import { createDefaultMapping } from '../utils'; +import { connector } from './config'; const JiraConnectorForm: React.FC> = ({ errors, @@ -107,4 +109,5 @@ export const JiraConnectorFlyout = withConnectorFlyout({ ConnectorFormComponent: JiraConnectorForm, secretKeys: ['email', 'apiToken'], configKeys: ['projectKey'], + connectorActionTypeId: '.jira', }); diff --git a/x-pack/plugins/siem/public/lib/connectors/servicenow/config.ts b/x-pack/plugins/siem/public/lib/connectors/servicenow/config.ts index 379106516d6ee..7882dc3b87fda 100644 --- a/x-pack/plugins/siem/public/lib/connectors/servicenow/config.ts +++ b/x-pack/plugins/siem/public/lib/connectors/servicenow/config.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ConnectorConfiguration, ServiceNowFieldsType } from './types'; +import { ConnectorConfiguration } from './types'; import { SERVICENOW_TITLE } from './translations'; import logo from './logo.svg'; -export const connector: ConnectorConfiguration = { +export const connector: ConnectorConfiguration = { id: '.servicenow', name: SERVICENOW_TITLE, logo, diff --git a/x-pack/plugins/siem/public/lib/connectors/servicenow/flyout.tsx b/x-pack/plugins/siem/public/lib/connectors/servicenow/flyout.tsx index bcde802e7bd1e..f7353040ff7e3 100644 --- a/x-pack/plugins/siem/public/lib/connectors/servicenow/flyout.tsx +++ b/x-pack/plugins/siem/public/lib/connectors/servicenow/flyout.tsx @@ -17,6 +17,8 @@ import * as i18n from './translations'; import { ConnectorFlyoutFormProps } from '../types'; import { ServiceNowActionConnector } from './types'; import { withConnectorFlyout } from '../components/connector_flyout'; +import { createDefaultMapping } from '../utils'; +import { connector } from './config'; const ServiceNowConnectorForm: React.FC> = ({ errors, @@ -80,4 +82,5 @@ const ServiceNowConnectorForm: React.FC({ ConnectorFormComponent: ServiceNowConnectorForm, secretKeys: ['username', 'password'], + connectorActionTypeId: '.servicenow', }); diff --git a/x-pack/plugins/siem/public/lib/connectors/types.ts b/x-pack/plugins/siem/public/lib/connectors/types.ts index 546be9fa47b5a..3471be0f1ea7a 100644 --- a/x-pack/plugins/siem/public/lib/connectors/types.ts +++ b/x-pack/plugins/siem/public/lib/connectors/types.ts @@ -29,9 +29,9 @@ export interface ThirdPartyField { defaultActionType: ThirdPartySupportedActions; } -export interface ConnectorConfiguration extends ActionType { +export interface ConnectorConfiguration extends ActionType { logo: string; - fields: Record; + fields: Record; } export interface ActionConnector { @@ -60,6 +60,7 @@ export interface ConnectorFlyoutFormProps { export interface ConnectorFlyoutHOCProps { ConnectorFormComponent: React.FC>; + connectorActionTypeId: string; configKeys?: string[]; secretKeys?: string[]; } diff --git a/x-pack/plugins/siem/public/lib/connectors/utils.ts b/x-pack/plugins/siem/public/lib/connectors/utils.ts index 5b5270ade5a65..169b4758876e8 100644 --- a/x-pack/plugins/siem/public/lib/connectors/utils.ts +++ b/x-pack/plugins/siem/public/lib/connectors/utils.ts @@ -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 { return { errors: {} }; }; + +export const createDefaultMapping = ( + fields: Record +): CasesConfigurationMapping[] => + Object.keys(fields).map( + key => + ({ + source: fields[key].defaultSourceField, + target: key, + actionType: fields[key].defaultActionType, + } as CasesConfigurationMapping) + ); diff --git a/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx b/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx index 9e137b9fcc143..f39cf1f1a5783 100644 --- a/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx +++ b/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx @@ -4,7 +4,7 @@ * 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'; @@ -18,11 +18,12 @@ import { FieldMappingRow } from './field_mapping_row'; import * as i18n from './translations'; import { connectorsConfiguration } from '../../../../lib/connectors/config'; -import { setActionTypeToMapping, setThirdPartyToMapping, createDefaultMapping } from './utils'; +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; @@ -92,6 +93,7 @@ const getThirdPartyOptions = ( export interface FieldMappingProps { disabled: boolean; mapping: CasesConfigurationMapping[] | null; + connectorActionTypeId: string; onChangeMapping: (newMapping: CasesConfigurationMapping[]) => void; } @@ -99,6 +101,7 @@ const FieldMappingComponent: React.FC = ({ disabled, mapping, onChangeMapping, + connectorActionTypeId, }) => { const onChangeActionType = useCallback( (caseField: CaseField, newActionType: ActionType) => { @@ -116,8 +119,10 @@ const FieldMappingComponent: React.FC = ({ [mapping] ); - const serviceNow = connectorsConfiguration['.servicenow']; - const defaultMapping = createDefaultMapping(serviceNow.fields); + const selectedConnector = connectorsConfiguration[connectorActionTypeId] ?? { fields: {} }; + const defaultMapping = useMemo(() => createDefaultMapping(selectedConnector.fields), [ + selectedConnector.fields, + ]); return ( <> @@ -140,7 +145,7 @@ const FieldMappingComponent: React.FC = ({ key={item.source} disabled={disabled} siemField={item.source} - thirdPartyOptions={getThirdPartyOptions(item.source, serviceNow.fields)} + thirdPartyOptions={getThirdPartyOptions(item.source, selectedConnector.fields)} actionTypeOptions={actionTypeOptions} onChangeActionType={onChangeActionType} onChangeThirdParty={onChangeThirdParty} diff --git a/x-pack/plugins/siem/public/pages/case/components/configure_cases/index.tsx b/x-pack/plugins/siem/public/pages/case/components/configure_cases/index.tsx index 66eef9e3ec7bf..7936c895f5af9 100644 --- a/x-pack/plugins/siem/public/pages/case/components/configure_cases/index.tsx +++ b/x-pack/plugins/siem/public/pages/case/components/configure_cases/index.tsx @@ -200,6 +200,11 @@ const ConfigureCasesComponent: React.FC = ({ userC currentConfiguration.closureType, ]); + const connectorActionTypeId = useMemo( + () => connectors.find(c => c.id === connectorId)?.actionTypeId ?? '.none', + [connectorId, connectors] + ); + return ( {!connectorIsValid && ( @@ -236,6 +241,7 @@ const ConfigureCasesComponent: React.FC = ({ userC disabled updateConnectorDisabled={updateConnectorDisabled || !userCanCrud} mapping={mapping} + connectorActionTypeId={connectorActionTypeId} onChangeMapping={setMapping} setEditFlyoutVisibility={onClickUpdateConnector} /> diff --git a/x-pack/plugins/siem/public/pages/case/components/configure_cases/mapping.tsx b/x-pack/plugins/siem/public/pages/case/components/configure_cases/mapping.tsx index 7340a49f6d0bb..acbcdac68a134 100644 --- a/x-pack/plugins/siem/public/pages/case/components/configure_cases/mapping.tsx +++ b/x-pack/plugins/siem/public/pages/case/components/configure_cases/mapping.tsx @@ -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 = ({ mapping, onChangeMapping, setEditFlyoutVisibility, + connectorActionTypeId, }) => { return ( = ({ -): CasesConfigurationMapping[] => - Object.keys(fields).map( - key => - ({ - source: fields[key].defaultSourceField, - target: key, - actionType: fields[key].defaultActionType, - } as CasesConfigurationMapping) - ); From 56e113c24f6b15c78fc869e4473ad78a15a583e6 Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Fri, 24 Apr 2020 12:57:17 +0300 Subject: [PATCH 03/10] Fix types --- x-pack/plugins/case/common/api/cases/configure.ts | 10 +++------- x-pack/plugins/case/common/api/connectors/jira.ts | 2 +- .../plugins/case/common/api/connectors/servicenow.ts | 2 +- .../plugins/siem/public/lib/connectors/jira/flyout.tsx | 2 -- .../siem/public/lib/connectors/servicenow/flyout.tsx | 2 -- x-pack/plugins/siem/public/lib/connectors/types.ts | 7 +------ 6 files changed, 6 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/case/common/api/cases/configure.ts b/x-pack/plugins/case/common/api/cases/configure.ts index 39fca95220dea..7d20011a428cf 100644 --- a/x-pack/plugins/case/common/api/cases/configure.ts +++ b/x-pack/plugins/case/common/api/cases/configure.ts @@ -8,6 +8,8 @@ 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 @@ -27,13 +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'), - rt.literal('summary'), -]); +const ThirdPartyFieldRT = rt.union([JiraFieldsRT, ServiceNowFieldsRT, rt.literal('not_mapped')]); export const CasesConfigurationMapsRT = rt.type({ source: CaseFieldRT, diff --git a/x-pack/plugins/case/common/api/connectors/jira.ts b/x-pack/plugins/case/common/api/connectors/jira.ts index 4ffd3b6e7d01f..4e4674318ddd8 100644 --- a/x-pack/plugins/case/common/api/connectors/jira.ts +++ b/x-pack/plugins/case/common/api/connectors/jira.ts @@ -6,7 +6,7 @@ import * as rt from 'io-ts'; -const JiraFieldsRT = rt.union([ +export const JiraFieldsRT = rt.union([ rt.literal('summary'), rt.literal('description'), rt.literal('comments'), diff --git a/x-pack/plugins/case/common/api/connectors/servicenow.ts b/x-pack/plugins/case/common/api/connectors/servicenow.ts index 551f96e1ab8e2..fc124bfd46094 100644 --- a/x-pack/plugins/case/common/api/connectors/servicenow.ts +++ b/x-pack/plugins/case/common/api/connectors/servicenow.ts @@ -6,7 +6,7 @@ import * as rt from 'io-ts'; -const ServiceNowFieldsRT = rt.union([ +export const ServiceNowFieldsRT = rt.union([ rt.literal('short_description'), rt.literal('description'), rt.literal('comments'), diff --git a/x-pack/plugins/siem/public/lib/connectors/jira/flyout.tsx b/x-pack/plugins/siem/public/lib/connectors/jira/flyout.tsx index a2e3ba2471cc1..9c3d1c90e67d7 100644 --- a/x-pack/plugins/siem/public/lib/connectors/jira/flyout.tsx +++ b/x-pack/plugins/siem/public/lib/connectors/jira/flyout.tsx @@ -17,8 +17,6 @@ import * as i18n from './translations'; import { ConnectorFlyoutFormProps } from '../types'; import { JiraActionConnector } from './types'; import { withConnectorFlyout } from '../components/connector_flyout'; -import { createDefaultMapping } from '../utils'; -import { connector } from './config'; const JiraConnectorForm: React.FC> = ({ errors, diff --git a/x-pack/plugins/siem/public/lib/connectors/servicenow/flyout.tsx b/x-pack/plugins/siem/public/lib/connectors/servicenow/flyout.tsx index f7353040ff7e3..5d5d08dacf90c 100644 --- a/x-pack/plugins/siem/public/lib/connectors/servicenow/flyout.tsx +++ b/x-pack/plugins/siem/public/lib/connectors/servicenow/flyout.tsx @@ -17,8 +17,6 @@ import * as i18n from './translations'; import { ConnectorFlyoutFormProps } from '../types'; import { ServiceNowActionConnector } from './types'; import { withConnectorFlyout } from '../components/connector_flyout'; -import { createDefaultMapping } from '../utils'; -import { connector } from './config'; const ServiceNowConnectorForm: React.FC> = ({ errors, diff --git a/x-pack/plugins/siem/public/lib/connectors/types.ts b/x-pack/plugins/siem/public/lib/connectors/types.ts index 3471be0f1ea7a..1bbc5d79e3528 100644 --- a/x-pack/plugins/siem/public/lib/connectors/types.ts +++ b/x-pack/plugins/siem/public/lib/connectors/types.ts @@ -10,17 +10,12 @@ import { ActionType } from '../../../../triggers_actions_ui/public'; import { ExternalIncidentServiceConfiguration } from '../../../../actions/server/builtin_action_types/case/types'; -import { - ServiceNowFieldsType, - JiraFieldsType, -} from '../../../../../../plugins/case/common/api/connectors'; - import { ActionType as ThirdPartySupportedActions, CaseField, } from '../../../../../../plugins/case/common/api'; -export type AllThirdPartyFields = ServiceNowFieldsType | JiraFieldsType; +export { ThirdPartyField as AllThirdPartyFields } from '../../../../../../plugins/case/common/api'; export interface ThirdPartyField { label: string; From 4c502b4c81f85bbbd45dfaf79b67fba3043c27aa Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Fri, 24 Apr 2020 14:18:28 +0300 Subject: [PATCH 04/10] Fix translations --- .../siem/public/lib/connectors/jira/config.ts | 10 ++++---- .../lib/connectors/jira/translations.ts | 7 ++++++ .../lib/connectors/servicenow/config.ts | 10 ++++---- .../lib/connectors/servicenow/translations.ts | 7 ++++++ .../public/lib/connectors/translations.ts | 14 +++++++++++ .../configure_cases/field_mapping.tsx | 24 +----------------- .../configure_cases/translations.ts | 25 ++----------------- 7 files changed, 41 insertions(+), 56 deletions(-) diff --git a/x-pack/plugins/siem/public/lib/connectors/jira/config.ts b/x-pack/plugins/siem/public/lib/connectors/jira/config.ts index 229feaf5f90ee..e6151a54bff74 100644 --- a/x-pack/plugins/siem/public/lib/connectors/jira/config.ts +++ b/x-pack/plugins/siem/public/lib/connectors/jira/config.ts @@ -6,12 +6,12 @@ import { ConnectorConfiguration } from './types'; -import { JIRA_TITLE } from './translations'; +import * as i18n from './translations'; import logo from './logo.svg'; export const connector: ConnectorConfiguration = { id: '.jira', - name: JIRA_TITLE, + name: i18n.JIRA_TITLE, logo, enabled: true, enabledInConfig: true, @@ -19,19 +19,19 @@ export const connector: ConnectorConfiguration = { minimumLicenseRequired: 'platinum', fields: { summary: { - label: 'Summary', + label: i18n.MAPPING_FIELD_SUMMARY, validSourceFields: ['title', 'description'], defaultSourceField: 'title', defaultActionType: 'overwrite', }, description: { - label: 'Description', + label: i18n.MAPPING_FIELD_DESC, validSourceFields: ['title', 'description'], defaultSourceField: 'description', defaultActionType: 'overwrite', }, comments: { - label: 'Comments', + label: i18n.MAPPING_FIELD_COMMENTS, validSourceFields: ['comments'], defaultSourceField: 'comments', defaultActionType: 'append', diff --git a/x-pack/plugins/siem/public/lib/connectors/jira/translations.ts b/x-pack/plugins/siem/public/lib/connectors/jira/translations.ts index 751aaecdad964..f95663d402604 100644 --- a/x-pack/plugins/siem/public/lib/connectors/jira/translations.ts +++ b/x-pack/plugins/siem/public/lib/connectors/jira/translations.ts @@ -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', + } +); diff --git a/x-pack/plugins/siem/public/lib/connectors/servicenow/config.ts b/x-pack/plugins/siem/public/lib/connectors/servicenow/config.ts index 7882dc3b87fda..35c677c9574e3 100644 --- a/x-pack/plugins/siem/public/lib/connectors/servicenow/config.ts +++ b/x-pack/plugins/siem/public/lib/connectors/servicenow/config.ts @@ -5,12 +5,12 @@ */ import { ConnectorConfiguration } from './types'; -import { SERVICENOW_TITLE } from './translations'; +import * as i18n from './translations'; import logo from './logo.svg'; export const connector: ConnectorConfiguration = { id: '.servicenow', - name: SERVICENOW_TITLE, + name: i18n.SERVICENOW_TITLE, logo, enabled: true, enabledInConfig: true, @@ -18,19 +18,19 @@ export const connector: ConnectorConfiguration = { minimumLicenseRequired: 'platinum', fields: { short_description: { - label: 'Short Description', + label: i18n.MAPPING_FIELD_SHORT_DESC, validSourceFields: ['title', 'description'], defaultSourceField: 'title', defaultActionType: 'overwrite', }, description: { - label: 'Description', + label: i18n.MAPPING_FIELD_DESC, validSourceFields: ['title', 'description'], defaultSourceField: 'description', defaultActionType: 'overwrite', }, comments: { - label: 'Comments', + label: i18n.MAPPING_FIELD_COMMENTS, validSourceFields: ['comments'], defaultSourceField: 'comments', defaultActionType: 'append', diff --git a/x-pack/plugins/siem/public/lib/connectors/servicenow/translations.ts b/x-pack/plugins/siem/public/lib/connectors/servicenow/translations.ts index 5dac9eddd1536..39d0ee96513a2 100644 --- a/x-pack/plugins/siem/public/lib/connectors/servicenow/translations.ts +++ b/x-pack/plugins/siem/public/lib/connectors/servicenow/translations.ts @@ -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', + } +); diff --git a/x-pack/plugins/siem/public/lib/connectors/translations.ts b/x-pack/plugins/siem/public/lib/connectors/translations.ts index b9c1d0fa2a17f..071fd8ef12645 100644 --- a/x-pack/plugins/siem/public/lib/connectors/translations.ts +++ b/x-pack/plugins/siem/public/lib/connectors/translations.ts @@ -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', + } +); diff --git a/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx b/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx index f39cf1f1a5783..6a6a28a083111 100644 --- a/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx +++ b/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx @@ -30,29 +30,6 @@ const FieldRowWrapper = styled.div` font-size: 14px; `; -// const supportedThirdPartyFields: Array> = [ -// { -// value: 'not_mapped', -// inputDisplay: {i18n.FIELD_MAPPING_FIELD_NOT_MAPPED}, -// 'data-test-subj': 'third-party-field-not-mapped', -// }, -// { -// value: 'short_description', -// inputDisplay: {i18n.FIELD_MAPPING_FIELD_SHORT_DESC}, -// 'data-test-subj': 'third-party-field-short-description', -// }, -// { -// value: 'comments', -// inputDisplay: {i18n.FIELD_MAPPING_FIELD_COMMENTS}, -// 'data-test-subj': 'third-party-field-comments', -// }, -// { -// value: 'description', -// inputDisplay: {i18n.FIELD_MAPPING_FIELD_DESC}, -// 'data-test-subj': 'third-party-field-description', -// }, -// ]; - const actionTypeOptions: Array> = [ { value: 'nothing', @@ -88,6 +65,7 @@ const getThirdPartyOptions = ( Object.keys(thirdPartyFields) .filter(key => thirdPartyFields[key].validSourceFields.includes(caseField)) .map(key => ({ key: key as AllThirdPartyFields, label: thirdPartyFields[key].label })) + .concat([{ key: 'not_mapped', label: i18n.MAPPING_FIELD_NOT_MAPPED }]) ); export interface FieldMappingProps { diff --git a/x-pack/plugins/siem/public/pages/case/components/configure_cases/translations.ts b/x-pack/plugins/siem/public/pages/case/components/configure_cases/translations.ts index 49caeae1c3a34..0d4cf94dc9f6f 100644 --- a/x-pack/plugins/siem/public/pages/case/components/configure_cases/translations.ts +++ b/x-pack/plugins/siem/public/pages/case/components/configure_cases/translations.ts @@ -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', }); From a92edbf354401ee282b4806628a5d7e1a0d2c37f Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Tue, 28 Apr 2020 14:01:01 +0300 Subject: [PATCH 05/10] Fix types --- .../siem/public/lib/connectors/jira/types.ts | 2 +- .../public/lib/connectors/servicenow/types.ts | 2 +- .../siem/public/lib/connectors/types.ts | 7 +--- .../configure_cases/field_mapping.test.tsx | 8 +++- .../field_mapping_row.test.tsx | 21 +++++++++- .../components/configure_cases/index.test.tsx | 40 +++++++++++++++++++ .../case/components/configure_cases/index.tsx | 2 +- .../configure_cases/mapping.test.tsx | 1 + 8 files changed, 73 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/siem/public/lib/connectors/jira/types.ts b/x-pack/plugins/siem/public/lib/connectors/jira/types.ts index beba07a4af04f..d6b8a6cadcb90 100644 --- a/x-pack/plugins/siem/public/lib/connectors/jira/types.ts +++ b/x-pack/plugins/siem/public/lib/connectors/jira/types.ts @@ -12,7 +12,7 @@ import { JiraSecretConfigurationType, } from '../../../../../actions/server/builtin_action_types/jira/types'; -export { JiraFieldsType } from '../../../../../../../plugins/case/common/api/connectors'; +export { JiraFieldsType } from '../../../../../case/common/api/connectors'; export * from '../types'; diff --git a/x-pack/plugins/siem/public/lib/connectors/servicenow/types.ts b/x-pack/plugins/siem/public/lib/connectors/servicenow/types.ts index 89005d7e502be..43da5624a497b 100644 --- a/x-pack/plugins/siem/public/lib/connectors/servicenow/types.ts +++ b/x-pack/plugins/siem/public/lib/connectors/servicenow/types.ts @@ -12,7 +12,7 @@ import { ServiceNowSecretConfigurationType, } from '../../../../../actions/server/builtin_action_types/servicenow/types'; -export { ServiceNowFieldsType } from '../../../../../../../plugins/case/common/api/connectors'; +export { ServiceNowFieldsType } from '../../../../../case/common/api/connectors'; export * from '../types'; diff --git a/x-pack/plugins/siem/public/lib/connectors/types.ts b/x-pack/plugins/siem/public/lib/connectors/types.ts index 1bbc5d79e3528..ffb013c347e59 100644 --- a/x-pack/plugins/siem/public/lib/connectors/types.ts +++ b/x-pack/plugins/siem/public/lib/connectors/types.ts @@ -10,12 +10,9 @@ import { ActionType } from '../../../../triggers_actions_ui/public'; import { ExternalIncidentServiceConfiguration } from '../../../../actions/server/builtin_action_types/case/types'; -import { - ActionType as ThirdPartySupportedActions, - CaseField, -} from '../../../../../../plugins/case/common/api'; +import { ActionType as ThirdPartySupportedActions, CaseField } from '../../../../case/common/api'; -export { ThirdPartyField as AllThirdPartyFields } from '../../../../../../plugins/case/common/api'; +export { ThirdPartyField as AllThirdPartyFields } from '../../../../case/common/api'; export interface ThirdPartyField { label: string; diff --git a/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping.test.tsx b/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping.test.tsx index 9ab752bb589c0..498757a34b78d 100644 --- a/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping.test.tsx +++ b/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping.test.tsx @@ -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); diff --git a/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.test.tsx b/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.test.tsx index 5143df37a3052..6ea0555483e60 100644 --- a/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.test.tsx +++ b/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.test.tsx @@ -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> = [ { @@ -25,6 +25,24 @@ const thirdPartyOptions: Array> = [ }, ]; +const actionTypeOptions: Array> = [ + { + 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(); @@ -34,6 +52,7 @@ describe('FieldMappingRow', () => { disabled: false, siemField: 'title', thirdPartyOptions, + actionTypeOptions, onChangeActionType, onChangeThirdParty, selectedActionType: 'nothing', diff --git a/x-pack/plugins/siem/public/pages/case/components/configure_cases/index.test.tsx b/x-pack/plugins/siem/public/pages/case/components/configure_cases/index.test.tsx index fde179f3d25fc..caac88bbb538d 100644 --- a/x-pack/plugins/siem/public/pages/case/components/configure_cases/index.test.tsx +++ b/x-pack/plugins/siem/public/pages/case/components/configure_cases/index.test.tsx @@ -190,6 +190,26 @@ describe('ConfigureCases', () => { enabledInConfig: true, enabledInLicense: true, minimumLicenseRequired: 'platinum', + fields: { + short_description: { + label: 'Short Description', + validSourceFields: ['title', 'description'], + defaultSourceField: 'title', + defaultActionType: 'overwrite', + }, + description: { + label: 'Description', + validSourceFields: ['title', 'description'], + defaultSourceField: 'description', + defaultActionType: 'overwrite', + }, + comments: { + label: 'Comments', + validSourceFields: ['comments'], + defaultSourceField: 'comments', + defaultActionType: 'append', + }, + }, }, { id: '.jira', @@ -199,6 +219,26 @@ describe('ConfigureCases', () => { enabledInConfig: true, enabledInLicense: true, minimumLicenseRequired: 'platinum', + fields: { + summary: { + label: 'Summary', + validSourceFields: ['title', 'description'], + defaultSourceField: 'title', + defaultActionType: 'overwrite', + }, + description: { + label: 'Description', + validSourceFields: ['title', 'description'], + defaultSourceField: 'description', + defaultActionType: 'overwrite', + }, + comments: { + label: 'Comments', + validSourceFields: ['comments'], + defaultSourceField: 'comments', + defaultActionType: 'append', + }, + }, }, ]); diff --git a/x-pack/plugins/siem/public/pages/case/components/configure_cases/index.tsx b/x-pack/plugins/siem/public/pages/case/components/configure_cases/index.tsx index 7936c895f5af9..441acdf8b446f 100644 --- a/x-pack/plugins/siem/public/pages/case/components/configure_cases/index.tsx +++ b/x-pack/plugins/siem/public/pages/case/components/configure_cases/index.tsx @@ -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 { diff --git a/x-pack/plugins/siem/public/pages/case/components/configure_cases/mapping.test.tsx b/x-pack/plugins/siem/public/pages/case/components/configure_cases/mapping.test.tsx index fefcb2ca8cf6a..5da5b417da5b7 100644 --- a/x-pack/plugins/siem/public/pages/case/components/configure_cases/mapping.test.tsx +++ b/x-pack/plugins/siem/public/pages/case/components/configure_cases/mapping.test.tsx @@ -21,6 +21,7 @@ describe('Mapping', () => { updateConnectorDisabled: false, onChangeMapping, setEditFlyoutVisibility, + connectorActionTypeId: '.servicenow', }; beforeAll(() => { From 2e939df0cb753aa9ac71829a08d66459f1c2f095 Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Tue, 28 Apr 2020 16:36:36 +0300 Subject: [PATCH 06/10] Improve mapping tests --- .../configure_cases/closure_options.test.tsx | 1 - .../configure_cases/field_mapping.tsx | 4 +- .../configure_cases/field_mapping_row.tsx | 6 +- .../configure_cases/mapping.test.tsx | 274 ++++++++++++++++-- 4 files changed, 253 insertions(+), 32 deletions(-) diff --git a/x-pack/plugins/siem/public/pages/case/components/configure_cases/closure_options.test.tsx b/x-pack/plugins/siem/public/pages/case/components/configure_cases/closure_options.test.tsx index 209dce9aedffc..eaef524b13da8 100644 --- a/x-pack/plugins/siem/public/pages/case/components/configure_cases/closure_options.test.tsx +++ b/x-pack/plugins/siem/public/pages/case/components/configure_cases/closure_options.test.tsx @@ -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'); }); }); diff --git a/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx b/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx index 6a6a28a083111..f99751b1495fc 100644 --- a/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx +++ b/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx @@ -54,6 +54,7 @@ const createSuperSelectOptions = ( return options.map(option => ({ value: option.key, inputDisplay: {option.label}, + 'data-test-subj': `dropdown-mapping-${option.key}`, })); }; @@ -120,7 +121,8 @@ const FieldMappingComponent: React.FC = ({ {(mapping ?? defaultMapping).map(item => ( >; @@ -34,6 +35,7 @@ export interface RowProps { } const FieldMappingRowComponent: React.FC = ({ + id, disabled, siemField, thirdPartyOptions, @@ -62,7 +64,7 @@ const FieldMappingRowComponent: React.FC = ({ 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}`} /> @@ -71,7 +73,7 @@ const FieldMappingRowComponent: React.FC = ({ 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}`} /> diff --git a/x-pack/plugins/siem/public/pages/case/components/configure_cases/mapping.test.tsx b/x-pack/plugins/siem/public/pages/case/components/configure_cases/mapping.test.tsx index 5da5b417da5b7..083904d303490 100644 --- a/x-pack/plugins/siem/public/pages/case/components/configure_cases/mapping.test.tsx +++ b/x-pack/plugins/siem/public/pages/case/components/configure_cases/mapping.test.tsx @@ -24,43 +24,261 @@ describe('Mapping', () => { connectorActionTypeId: '.servicenow', }; - beforeAll(() => { + beforeEach(() => { + jest.clearAllMocks(); wrapper = mount(, { 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(); - test('it shows the update button', () => { - expect( + 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('[data-test-subj="case-mapping-update-connector-button"]') - .first() - .exists() - ).toBe(true); - }); + .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 field mapping', () => { - expect( + test('it shows the correct action types', () => { wrapper - .find('[data-test-subj="case-mapping-field"]') - .first() - .exists() - ).toBe(true); + .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(); + }); + }); + + 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(); + + 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(, { + 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(); + }); + }); }); }); From fb000b7e201051ff6266ad73317869cac34d2c4e Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Tue, 28 Apr 2020 17:38:42 +0300 Subject: [PATCH 07/10] Improve tests --- .../public/containers/case/configure/mock.ts | 32 ++- .../configure_cases/connectors.test.tsx | 6 +- .../connectors_dropdown.test.tsx | 12 +- .../field_mapping_row.test.tsx | 5 +- .../components/configure_cases/index.test.tsx | 208 ++++++++---------- 5 files changed, 136 insertions(+), 127 deletions(-) diff --git a/x-pack/plugins/siem/public/containers/case/configure/mock.ts b/x-pack/plugins/siem/public/containers/case/configure/mock.ts index c6824bd50edb5..88e1793aa15c1 100644 --- a/x-pack/plugins/siem/public/containers/case/configure/mock.ts +++ b/x-pack/plugins/siem/public/containers/case/configure/mock.ts @@ -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 = { diff --git a/x-pack/plugins/siem/public/pages/case/components/configure_cases/connectors.test.tsx b/x-pack/plugins/siem/public/pages/case/components/configure_cases/connectors.test.tsx index 5fb52c374b482..125a42b126466 100644 --- a/x-pack/plugins/siem/public/pages/case/components/configure_cases/connectors.test.tsx +++ b/x-pack/plugins/siem/public/pages/case/components/configure_cases/connectors.test.tsx @@ -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(, { + const newWrapper = mount(, { wrappingComponent: TestProviders, }); diff --git a/x-pack/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.test.tsx b/x-pack/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.test.tsx index 044108962efc7..6abe4f1ac00ad 100644 --- a/x-pack/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.test.tsx +++ b/x-pack/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.test.tsx @@ -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(, { + const newWrapper = mount(, { wrappingComponent: TestProviders, }); diff --git a/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.test.tsx b/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.test.tsx index 6ea0555483e60..e30096cc7eb62 100644 --- a/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.test.tsx +++ b/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.test.tsx @@ -49,6 +49,7 @@ describe('FieldMappingRow', () => { const onChangeThirdParty = jest.fn(); const props: RowProps = { + id: 'title', disabled: false, siemField: 'title', thirdPartyOptions, @@ -66,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); diff --git a/x-pack/plugins/siem/public/pages/case/components/configure_cases/index.test.tsx b/x-pack/plugins/siem/public/pages/case/components/configure_cases/index.test.tsx index caac88bbb538d..0359c1dbdba67 100644 --- a/x-pack/plugins/siem/public/pages/case/components/configure_cases/index.test.tsx +++ b/x-pack/plugins/siem/public/pages/case/components/configure_cases/index.test.tsx @@ -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,64 +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', - fields: { - short_description: { - label: 'Short Description', - validSourceFields: ['title', 'description'], - defaultSourceField: 'title', - defaultActionType: 'overwrite', - }, - description: { - label: 'Description', - validSourceFields: ['title', 'description'], - defaultSourceField: 'description', - defaultActionType: 'overwrite', - }, - comments: { - label: 'Comments', - validSourceFields: ['comments'], - defaultSourceField: 'comments', - defaultActionType: 'append', - }, - }, - }, - { + }), + expect.objectContaining({ id: '.jira', - name: 'Jira', - logo: 'test-file-stub', - enabled: true, - enabledInConfig: true, - enabledInLicense: true, - minimumLicenseRequired: 'platinum', - fields: { - summary: { - label: 'Summary', - validSourceFields: ['title', 'description'], - defaultSourceField: 'title', - defaultActionType: 'overwrite', - }, - description: { - label: 'Description', - validSourceFields: ['title', 'description'], - defaultSourceField: 'description', - defaultActionType: 'overwrite', - }, - comments: { - label: 'Comments', - validSourceFields: ['comments'], - defaultSourceField: 'comments', - defaultActionType: 'append', - }, - }, - }, + }), ]); expect(wrapper.find(ConnectorEditFlyout).prop('editFlyoutVisible')).toBe(false); @@ -263,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', () => { @@ -314,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', }, })); @@ -375,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, @@ -409,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, @@ -449,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, @@ -477,7 +423,7 @@ describe('ConfigureCases', () => { expect(persistCaseConfigure).toHaveBeenCalled(); expect(persistCaseConfigure).toHaveBeenCalledWith({ - connectorId: '456', + connectorId: 'servicenow-2', connectorName: 'My Connector 2', closureType: 'close-by-user', }); @@ -491,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, @@ -530,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', }, })); @@ -570,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(, { 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(); @@ -598,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(, { 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(); @@ -635,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(); @@ -643,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(); @@ -657,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(, { wrappingComponent: TestProviders }); // Change closure type @@ -712,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(, { wrappingComponent: TestProviders }); @@ -764,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(, { 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( @@ -797,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(, { wrappingComponent: TestProviders }); wrapper.find('input[id="close-by-pushing"]').simulate('change'); @@ -828,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(, { 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'); }); }); From 793797c3adc24daa94f020a349864bc97569b807 Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Thu, 30 Apr 2020 15:40:01 +0300 Subject: [PATCH 08/10] Fix Jira logo --- .../plugins/siem/public/lib/connectors/jira/logo.svg | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/siem/public/lib/connectors/jira/logo.svg b/x-pack/plugins/siem/public/lib/connectors/jira/logo.svg index dcd022a8dca18..8560cf7e270c8 100644 --- a/x-pack/plugins/siem/public/lib/connectors/jira/logo.svg +++ b/x-pack/plugins/siem/public/lib/connectors/jira/logo.svg @@ -1,5 +1,9 @@ - - - - + + + + + + + + From 0a1a39a2f1351272c6b08040b22d721392218c56 Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Fri, 1 May 2020 12:39:31 +0300 Subject: [PATCH 09/10] Fix i18n --- x-pack/plugins/translations/translations/ja-JP.json | 4 ---- x-pack/plugins/translations/translations/zh-CN.json | 4 ---- 2 files changed, 8 deletions(-) diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 49a749c578caf..9267252d23ef0 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -13231,10 +13231,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": "編集時と更新時", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index adb8ac1817a14..530adb08431b9 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -13235,10 +13235,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": "编辑和更新时", From 8c7fbf1a765e3e8e68785e31a8f067f78d9c1deb Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Mon, 4 May 2020 21:38:48 +0300 Subject: [PATCH 10/10] Improve getThirdPartyOptions --- .../configure_cases/field_mapping.tsx | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx b/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx index f99751b1495fc..41a6fbca3c007 100644 --- a/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx +++ b/x-pack/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx @@ -48,25 +48,33 @@ const actionTypeOptions: Array> = [ }, ]; -const createSuperSelectOptions = ( - options: Array<{ key: T; label: string }> -): Array> => { - return options.map(option => ({ - value: option.key, - inputDisplay: {option.label}, - 'data-test-subj': `dropdown-mapping-${option.key}`, - })); -}; - const getThirdPartyOptions = ( caseField: CaseField, thirdPartyFields: Record -) => - createSuperSelectOptions( - Object.keys(thirdPartyFields) - .filter(key => thirdPartyFields[key].validSourceFields.includes(caseField)) - .map(key => ({ key: key as AllThirdPartyFields, label: thirdPartyFields[key].label })) - .concat([{ key: 'not_mapped', label: i18n.MAPPING_FIELD_NOT_MAPPED }]) +): Array> => + (Object.keys(thirdPartyFields) as AllThirdPartyFields[]).reduce< + Array> + >( + (acc, key) => { + if (thirdPartyFields[key].validSourceFields.includes(caseField)) { + return [ + ...acc, + { + value: key, + inputDisplay: {thirdPartyFields[key].label}, + '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 {