Skip to content

Commit 7c32bac

Browse files
committed
Create Resilient flyout
1 parent c245ed9 commit 7c32bac

File tree

21 files changed

+1195
-3
lines changed

21 files changed

+1195
-3
lines changed

x-pack/plugins/actions/server/builtin_action_types/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { getActionType as getSlackActionType } from './slack';
1616
import { getActionType as getWebhookActionType } from './webhook';
1717
import { getActionType as getServiceNowActionType } from './servicenow';
1818
import { getActionType as getJiraActionType } from './jira';
19+
import { getActionType as getResilientActionType } from './resilient';
1920

2021
export function registerBuiltInActionTypes({
2122
actionsConfigUtils: configurationUtilities,
@@ -34,4 +35,5 @@ export function registerBuiltInActionTypes({
3435
actionTypeRegistry.register(getWebhookActionType({ logger, configurationUtilities }));
3536
actionTypeRegistry.register(getServiceNowActionType({ configurationUtilities }));
3637
actionTypeRegistry.register(getJiraActionType({ configurationUtilities }));
38+
actionTypeRegistry.register(getResilientActionType({ logger, configurationUtilities }));
3739
}

x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ interface GetActionTypeParams {
3434
export function getActionType(params: GetActionTypeParams): ActionType {
3535
const { logger, configurationUtilities } = params;
3636
return {
37-
id: '.servicenow',
37+
id: '.resilient',
3838
minimumLicenseRequired: 'platinum',
3939
name: i18n.NAME,
4040
validate: {

x-pack/plugins/case/common/api/cases/configure.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { ActionResult } from '../../../../actions/common';
1010
import { UserRT } from '../user';
1111
import { JiraFieldsRT } from '../connectors/jira';
1212
import { ServiceNowFieldsRT } from '../connectors/servicenow';
13+
import { ResilientFieldsRT } from '../connectors/resilient';
1314

1415
/*
1516
* This types below are related to the service now configuration
@@ -29,7 +30,12 @@ const CaseFieldRT = rt.union([
2930
rt.literal('comments'),
3031
]);
3132

32-
const ThirdPartyFieldRT = rt.union([JiraFieldsRT, ServiceNowFieldsRT, rt.literal('not_mapped')]);
33+
const ThirdPartyFieldRT = rt.union([
34+
JiraFieldsRT,
35+
ServiceNowFieldsRT,
36+
ResilientFieldsRT,
37+
rt.literal('not_mapped'),
38+
]);
3339

3440
export const CasesConfigurationMapsRT = rt.type({
3541
source: CaseFieldRT,

x-pack/plugins/case/common/api/connectors/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66

77
export * from './jira';
88
export * from './servicenow';
9+
export * from './resilient';
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import * as rt from 'io-ts';
8+
9+
export const ResilientFieldsRT = rt.union([
10+
rt.literal('name'),
11+
rt.literal('description'),
12+
rt.literal('comments'),
13+
]);
14+
15+
export type ResilientFieldsType = rt.TypeOf<typeof ResilientFieldsRT>;

x-pack/plugins/case/common/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,4 @@ export const CASE_USER_ACTIONS_URL = `${CASE_DETAILS_URL}/user_actions`;
2828
export const ACTION_URL = '/api/actions';
2929
export const ACTION_TYPES_URL = '/api/actions/list_action_types';
3030

31-
export const SUPPORTED_CONNECTORS = ['.servicenow', '.jira'];
31+
export const SUPPORTED_CONNECTORS = ['.servicenow', '.jira', '.resilient'];

x-pack/plugins/security_solution/public/common/lib/connectors/config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7+
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
8+
import { connectorConfiguration as resilientConnectorConfig } from '../../../../../triggers_actions_ui/public/application/components/builtin_action_types/resilient/config';
79
import { connector as serviceNowConnectorConfig } from './servicenow/config';
810
import { connector as jiraConnectorConfig } from './jira/config';
911
import { ConnectorConfiguration } from './types';
1012

1113
export const connectorsConfiguration: Record<string, ConnectorConfiguration> = {
1214
'.servicenow': serviceNowConnectorConfig,
1315
'.jira': jiraConnectorConfig,
16+
'.resilient': resilientConnectorConfig as ConnectorConfiguration,
1417
};

x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { getEmailActionType } from './email';
1010
import { getIndexActionType } from './es_index';
1111
import { getPagerDutyActionType } from './pagerduty';
1212
import { getWebhookActionType } from './webhook';
13+
import { getResilientActionType } from './resilient';
1314
import { TypeRegistry } from '../../type_registry';
1415
import { ActionTypeModel } from '../../../types';
1516

@@ -24,4 +25,5 @@ export function registerBuiltInActionTypes({
2425
actionTypeRegistry.register(getIndexActionType());
2526
actionTypeRegistry.register(getPagerDutyActionType());
2627
actionTypeRegistry.register(getWebhookActionType());
28+
actionTypeRegistry.register(getResilientActionType());
2729
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import React, { useCallback, useMemo } from 'react';
8+
import { EuiFormRow, EuiFlexItem, EuiFlexGroup, EuiSuperSelectOption } from '@elastic/eui';
9+
import styled from 'styled-components';
10+
11+
import { CaseField, ActionType, ThirdPartyField } from '../../../../../../../case/common/api';
12+
import { FieldMappingRow } from './field_mapping_row';
13+
import * as i18n from './translations';
14+
15+
import { setActionTypeToMapping, setThirdPartyToMapping } from './utils';
16+
import {
17+
ThirdPartyField as ConnectorConfigurationThirdPartyField,
18+
AllThirdPartyFields,
19+
} from './types';
20+
import { CasesConfigurationMapping } from '../types';
21+
import { connectorConfiguration } from '../config';
22+
import { createDefaultMapping } from '../resilient_connectors';
23+
24+
const FieldRowWrapper = styled.div`
25+
margin-top: 8px;
26+
font-size: 14px;
27+
`;
28+
29+
const actionTypeOptions: Array<EuiSuperSelectOption<ActionType>> = [
30+
{
31+
value: 'nothing',
32+
inputDisplay: <>{i18n.FIELD_MAPPING_EDIT_NOTHING}</>,
33+
'data-test-subj': 'edit-update-option-nothing',
34+
},
35+
{
36+
value: 'overwrite',
37+
inputDisplay: <>{i18n.FIELD_MAPPING_EDIT_OVERWRITE}</>,
38+
'data-test-subj': 'edit-update-option-overwrite',
39+
},
40+
{
41+
value: 'append',
42+
inputDisplay: <>{i18n.FIELD_MAPPING_EDIT_APPEND}</>,
43+
'data-test-subj': 'edit-update-option-append',
44+
},
45+
];
46+
47+
const getThirdPartyOptions = (
48+
caseField: CaseField,
49+
thirdPartyFields: Record<string, ConnectorConfigurationThirdPartyField>
50+
): Array<EuiSuperSelectOption<AllThirdPartyFields>> =>
51+
(Object.keys(thirdPartyFields) as AllThirdPartyFields[]).reduce<
52+
Array<EuiSuperSelectOption<AllThirdPartyFields>>
53+
>(
54+
(acc, key) => {
55+
if (thirdPartyFields[key].validSourceFields.includes(caseField)) {
56+
return [
57+
...acc,
58+
{
59+
value: key,
60+
inputDisplay: <span>{thirdPartyFields[key].label}</span>,
61+
'data-test-subj': `dropdown-mapping-${key}`,
62+
},
63+
];
64+
}
65+
return acc;
66+
},
67+
[
68+
{
69+
value: 'not_mapped',
70+
inputDisplay: i18n.MAPPING_FIELD_NOT_MAPPED,
71+
'data-test-subj': 'dropdown-mapping-not_mapped',
72+
},
73+
]
74+
);
75+
76+
export interface FieldMappingProps {
77+
disabled: boolean;
78+
mapping: CasesConfigurationMapping[] | null;
79+
connectorActionTypeId: string;
80+
onChangeMapping: (newMapping: CasesConfigurationMapping[]) => void;
81+
}
82+
83+
const FieldMappingComponent: React.FC<FieldMappingProps> = ({
84+
disabled,
85+
mapping,
86+
onChangeMapping,
87+
connectorActionTypeId,
88+
}) => {
89+
const onChangeActionType = useCallback(
90+
(caseField: CaseField, newActionType: ActionType) => {
91+
const myMapping = mapping ?? defaultMapping;
92+
onChangeMapping(setActionTypeToMapping(caseField, newActionType, myMapping));
93+
},
94+
// eslint-disable-next-line react-hooks/exhaustive-deps
95+
[mapping]
96+
);
97+
98+
const onChangeThirdParty = useCallback(
99+
(caseField: CaseField, newThirdPartyField: ThirdPartyField) => {
100+
const myMapping = mapping ?? defaultMapping;
101+
onChangeMapping(setThirdPartyToMapping(caseField, newThirdPartyField, myMapping));
102+
},
103+
// eslint-disable-next-line react-hooks/exhaustive-deps
104+
[mapping]
105+
);
106+
107+
const selectedConnector = connectorConfiguration ?? { fields: {} };
108+
const defaultMapping = useMemo(() => createDefaultMapping(selectedConnector.fields), [
109+
selectedConnector.fields,
110+
]);
111+
112+
return (
113+
<>
114+
<EuiFormRow fullWidth data-test-subj="case-configure-field-mapping-cols">
115+
<EuiFlexGroup>
116+
<EuiFlexItem>
117+
<span className="euiFormLabel">{i18n.FIELD_MAPPING_FIRST_COL}</span>
118+
</EuiFlexItem>
119+
<EuiFlexItem>
120+
<span className="euiFormLabel">{i18n.FIELD_MAPPING_SECOND_COL}</span>
121+
</EuiFlexItem>
122+
<EuiFlexItem>
123+
<span className="euiFormLabel">{i18n.FIELD_MAPPING_THIRD_COL}</span>
124+
</EuiFlexItem>
125+
</EuiFlexGroup>
126+
</EuiFormRow>
127+
<FieldRowWrapper data-test-subj="case-configure-field-mapping-row-wrapper">
128+
{(mapping ?? defaultMapping).map((item) => (
129+
<FieldMappingRow
130+
key={`${item.source}`}
131+
id={`${item.source}`}
132+
disabled={disabled}
133+
securitySolutionField={item.source}
134+
thirdPartyOptions={getThirdPartyOptions(item.source, selectedConnector.fields as any)}
135+
actionTypeOptions={actionTypeOptions}
136+
onChangeActionType={onChangeActionType}
137+
onChangeThirdParty={onChangeThirdParty}
138+
selectedActionType={item.actionType}
139+
selectedThirdParty={item.target ?? 'not_mapped'}
140+
/>
141+
))}
142+
</FieldRowWrapper>
143+
</>
144+
);
145+
};
146+
147+
export const FieldMapping = React.memo(FieldMappingComponent);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import React, { useMemo } from 'react';
8+
import {
9+
EuiFlexItem,
10+
EuiFlexGroup,
11+
EuiSuperSelect,
12+
EuiIcon,
13+
EuiSuperSelectOption,
14+
} from '@elastic/eui';
15+
16+
import { capitalize } from 'lodash';
17+
import { CaseField, ActionType, ThirdPartyField } from '../../../../../../../case/common/api/cases';
18+
import { AllThirdPartyFields } from './types';
19+
20+
export interface RowProps {
21+
id: string;
22+
disabled: boolean;
23+
securitySolutionField: CaseField;
24+
thirdPartyOptions: Array<EuiSuperSelectOption<AllThirdPartyFields>>;
25+
actionTypeOptions: Array<EuiSuperSelectOption<ActionType>>;
26+
onChangeActionType: (caseField: CaseField, newActionType: ActionType) => void;
27+
onChangeThirdParty: (caseField: CaseField, newThirdPartyField: ThirdPartyField) => void;
28+
selectedActionType: ActionType;
29+
selectedThirdParty: ThirdPartyField;
30+
}
31+
32+
const FieldMappingRowComponent: React.FC<RowProps> = ({
33+
id,
34+
disabled,
35+
securitySolutionField,
36+
thirdPartyOptions,
37+
actionTypeOptions,
38+
onChangeActionType,
39+
onChangeThirdParty,
40+
selectedActionType,
41+
selectedThirdParty,
42+
}) => {
43+
const securitySolutionFieldCapitalized = useMemo(() => capitalize(securitySolutionField), [
44+
securitySolutionField,
45+
]);
46+
return (
47+
<EuiFlexGroup alignItems="center">
48+
<EuiFlexItem>
49+
<EuiFlexGroup component="span" justifyContent="spaceBetween">
50+
<EuiFlexItem component="span" grow={false}>
51+
{securitySolutionFieldCapitalized}
52+
</EuiFlexItem>
53+
<EuiFlexItem component="span" grow={false}>
54+
<EuiIcon type="sortRight" />
55+
</EuiFlexItem>
56+
</EuiFlexGroup>
57+
</EuiFlexItem>
58+
<EuiFlexItem>
59+
<EuiSuperSelect
60+
disabled={disabled}
61+
options={thirdPartyOptions}
62+
valueOfSelected={selectedThirdParty}
63+
onChange={onChangeThirdParty.bind(null, securitySolutionField)}
64+
data-test-subj={`case-configure-third-party-select-${id}`}
65+
/>
66+
</EuiFlexItem>
67+
<EuiFlexItem>
68+
<EuiSuperSelect
69+
disabled={disabled}
70+
options={actionTypeOptions}
71+
valueOfSelected={selectedActionType}
72+
onChange={onChangeActionType.bind(null, securitySolutionField)}
73+
data-test-subj={`case-configure-action-type-select-${id}`}
74+
/>
75+
</EuiFlexItem>
76+
</EuiFlexGroup>
77+
);
78+
};
79+
80+
export const FieldMappingRow = React.memo(FieldMappingRowComponent);

0 commit comments

Comments
 (0)