Skip to content

Commit

Permalink
[SIEM][CASE] Configure cases: Closure Options & Field Mappings UI (#5…
Browse files Browse the repository at this point in the history
…9062)

* Create closure options radio group

* Create closure options component

* Refactor structure

* Create field mapping row

* Create field component

* Show closure options

* Show field mapping

* Translate editUpdate options

* Add more siem fields

* Remove unnecessary export

* Leave spaces between sections

* Fix callbacks

* Better return

* Fix callback

* Move thirdPartyFields to parent component

* Rename constant

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
cnasikas and elasticmachine authored Mar 4, 2020
1 parent 2361fe6 commit aea4811
Show file tree
Hide file tree
Showing 7 changed files with 326 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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 React from 'react';
import { EuiDescribedFormGroup, EuiFormRow } from '@elastic/eui';

import * as i18n from './translations';
import { ClosureOptionsRadio } from './closure_options_radio';

const ClosureOptionsComponent: React.FC = () => {
return (
<EuiDescribedFormGroup
fullWidth
title={<h3>{i18n.CASE_CLOSURE_OPTIONS_TITLE}</h3>}
description={i18n.CASE_CLOSURE_OPTIONS_DESC}
>
<EuiFormRow fullWidth label={i18n.CASE_CLOSURE_OPTIONS_LABEL}>
<ClosureOptionsRadio />
</EuiFormRow>
</EuiDescribedFormGroup>
);
};

export const ClosureOptions = React.memo(ClosureOptionsComponent);
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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 React, { useState } from 'react';
import { EuiRadioGroup } from '@elastic/eui';

import * as i18n from './translations';

const ID_PREFIX = 'closure_options';
const DEFAULT_RADIO = `${ID_PREFIX}_manual`;

const radios = [
{
id: DEFAULT_RADIO,
label: i18n.CASE_CLOSURE_OPTIONS_MANUAL,
},
{
id: `${ID_PREFIX}_new_incident`,
label: i18n.CASE_CLOSURE_OPTIONS_NEW_INCIDENT,
},
{
id: `${ID_PREFIX}_closed_incident`,
label: i18n.CASE_CLOSURE_OPTIONS_CLOSED_INCIDENT,
},
];

const ClosureOptionsRadioComponent: React.FC = () => {
const [selectedClosure, setSelectedClosure] = useState(DEFAULT_RADIO);

return (
<EuiRadioGroup
options={radios}
idSelected={selectedClosure}
onChange={setSelectedClosure}
name="closure_options"
/>
);
};

export const ClosureOptionsRadio = React.memo(ClosureOptionsRadioComponent);
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React, { useState, useCallback } from 'react';
import React, { useState } from 'react';
import { EuiSuperSelect, EuiIcon, EuiSuperSelectOption } from '@elastic/eui';
import styled from 'styled-components';

import * as i18n from '../translations';
import * as i18n from './translations';

const ICON_SIZE = 'm';

Expand Down Expand Up @@ -40,15 +40,14 @@ const connectors: Array<EuiSuperSelectOption<string>> = [
];

const ConnectorsDropdownComponent: React.FC = () => {
const [selectedConnector, selectConnector] = useState(connectors[0].value);
const onChange = useCallback(connector => selectConnector(connector), [selectedConnector]);
const [selectedConnector, setSelectedConnector] = useState(connectors[0].value);

return (
<EuiSuperSelect
options={connectors}
valueOfSelected={selectedConnector}
fullWidth
onChange={onChange}
onChange={setSelectedConnector}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* 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 React from 'react';
import { EuiDescribedFormGroup, EuiFormRow, EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
import styled from 'styled-components';

import * as i18n from './translations';
import { FieldMappingRow } from './field_mapping_row';

const FieldRowWrapper = styled.div`
margin-top: 8px;
font-size: 14px;
`;

const supportedThirdPartyFields = [
{
value: 'short_description',
inputDisplay: <span>{'Short Description'}</span>,
},
{
value: 'comment',
inputDisplay: <span>{'Comment'}</span>,
},
{
value: 'tags',
inputDisplay: <span>{'Tags'}</span>,
},
{
value: 'description',
inputDisplay: <span>{'Description'}</span>,
},
];

const FieldMappingComponent: React.FC = () => (
<EuiDescribedFormGroup
fullWidth
title={<h3>{i18n.FIELD_MAPPING_TITLE}</h3>}
description={i18n.FIELD_MAPPING_DESC}
>
<EuiFormRow fullWidth>
<EuiFlexGroup>
<EuiFlexItem>
<span className="euiFormLabel">{i18n.FIELD_MAPPING_FIRST_COL}</span>
</EuiFlexItem>
<EuiFlexItem>
<span className="euiFormLabel">{i18n.FIELD_MAPPING_SECOND_COL}</span>
</EuiFlexItem>
<EuiFlexItem>
<span className="euiFormLabel">{i18n.FIELD_MAPPING_THIRD_COL}</span>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFormRow>
<FieldRowWrapper>
<FieldMappingRow siemField="Name" thirdPartyOptions={supportedThirdPartyFields} />
<FieldMappingRow siemField="Tags" thirdPartyOptions={supportedThirdPartyFields} />
<FieldMappingRow siemField="Description" thirdPartyOptions={supportedThirdPartyFields} />
<FieldMappingRow siemField="Comment" thirdPartyOptions={supportedThirdPartyFields} />
</FieldRowWrapper>
</EuiDescribedFormGroup>
);

export const FieldMapping = React.memo(FieldMappingComponent);
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* 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 React, { useState } from 'react';
import { EuiFlexItem, EuiFlexGroup, EuiSuperSelect, EuiIcon } from '@elastic/eui';

import * as i18n from './translations';

interface ThirdPartyField {
value: string;
inputDisplay: JSX.Element;
}
interface RowProps {
siemField: string;
thirdPartyOptions: ThirdPartyField[];
}

const editUpdateOptions = [
{
value: 'nothing',
inputDisplay: <span>{i18n.FIELD_MAPPING_EDIT_NOTHING}</span>,
'data-test-subj': 'edit-update-option-nothing',
},
{
value: 'overwrite',
inputDisplay: <span>{i18n.FIELD_MAPPING_EDIT_OVERWRITE}</span>,
'data-test-subj': 'edit-update-option-overwrite',
},
{
value: 'append',
inputDisplay: <span>{i18n.FIELD_MAPPING_EDIT_APPEND}</span>,
'data-test-subj': 'edit-update-option-append',
},
];

const FieldMappingRowComponent: React.FC<RowProps> = ({ siemField, thirdPartyOptions }) => {
const [selectedEditUpdate, setSelectedEditUpdate] = useState(editUpdateOptions[0].value);
const [selectedThirdParty, setSelectedThirdParty] = useState(thirdPartyOptions[0].value);

return (
<EuiFlexGroup alignItems="center">
<EuiFlexItem>
<EuiFlexGroup component="span" justifyContent="spaceBetween">
<EuiFlexItem component="span" grow={false}>
{siemField}
</EuiFlexItem>
<EuiFlexItem component="span" grow={false}>
<EuiIcon type="sortRight" />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<EuiSuperSelect
options={thirdPartyOptions}
valueOfSelected={selectedThirdParty}
onChange={setSelectedThirdParty}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiSuperSelect
options={editUpdateOptions}
valueOfSelected={selectedEditUpdate}
onChange={setSelectedEditUpdate}
/>
</EuiFlexItem>
</EuiFlexGroup>
);
};

export const FieldMappingRow = React.memo(FieldMappingRowComponent);
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,103 @@ export const NO_CONNECTOR = i18n.translate('xpack.siem.case.configureCases.noCon
export const ADD_NEW_CONNECTOR = i18n.translate('xpack.siem.case.configureCases.addNewConnector', {
defaultMessage: 'Add new connector option',
});

export const CASE_CLOSURE_OPTIONS_TITLE = i18n.translate(
'xpack.siem.case.configureCases.caseClosureOptionsTitle',
{
defaultMessage: 'Cases Closures',
}
);

export const CASE_CLOSURE_OPTIONS_DESC = i18n.translate(
'xpack.siem.case.configureCases.caseClosureOptionsDesc',
{
defaultMessage:
'Define how you wish SIEM cases to be closed. Automated case closures require an established connection to a third-party incident management system.',
}
);

export const CASE_CLOSURE_OPTIONS_LABEL = i18n.translate(
'xpack.siem.case.configureCases.caseClosureOptionsLabel',
{
defaultMessage: 'Case closure options',
}
);

export const CASE_CLOSURE_OPTIONS_MANUAL = i18n.translate(
'xpack.siem.case.configureCases.caseClosureOptionsManual',
{
defaultMessage: 'Manually close SIEM cases',
}
);

export const CASE_CLOSURE_OPTIONS_NEW_INCIDENT = i18n.translate(
'xpack.siem.case.configureCases.caseClosureOptionsNewIncident',
{
defaultMessage: 'Automatically close SIEM cases when pushing new incident to third-party',
}
);

export const CASE_CLOSURE_OPTIONS_CLOSED_INCIDENT = i18n.translate(
'xpack.siem.case.configureCases.caseClosureOptionsClosedIncident',
{
defaultMessage: 'Automatically close SIEM cases when incident is closed in third-party',
}
);

export const FIELD_MAPPING_TITLE = i18n.translate(
'xpack.siem.case.configureCases.fieldMappingTitle',
{
defaultMessage: 'Field mappings',
}
);

export const FIELD_MAPPING_DESC = i18n.translate(
'xpack.siem.case.configureCases.fieldMappingDesc',
{
defaultMessage:
'Map SIEM case fields when pushing data to a third-party. Field mappings require an established connection to a third-party incident management system.',
}
);

export const FIELD_MAPPING_FIRST_COL = i18n.translate(
'xpack.siem.case.configureCases.fieldMappingFirstCol',
{
defaultMessage: 'SIEM case field',
}
);

export const FIELD_MAPPING_SECOND_COL = i18n.translate(
'xpack.siem.case.configureCases.fieldMappingSecondCol',
{
defaultMessage: 'Third-party incident field',
}
);

export const FIELD_MAPPING_THIRD_COL = i18n.translate(
'xpack.siem.case.configureCases.fieldMappingThirdCol',
{
defaultMessage: 'On edit and update',
}
);

export const FIELD_MAPPING_EDIT_NOTHING = i18n.translate(
'xpack.siem.case.configureCases.fieldMappingEditNothing',
{
defaultMessage: 'Nothing',
}
);

export const FIELD_MAPPING_EDIT_OVERWRITE = i18n.translate(
'xpack.siem.case.configureCases.fieldMappingEditOverwrite',
{
defaultMessage: 'Overwrite',
}
);

export const FIELD_MAPPING_EDIT_APPEND = i18n.translate(
'xpack.siem.case.configureCases.fieldMappingEditAppend',
{
defaultMessage: 'Append',
}
);
14 changes: 13 additions & 1 deletion x-pack/legacy/plugins/siem/public/pages/case/configure_cases.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { getCaseUrl } from '../../components/link_to';
import { WhitePageWrapper, SectionWrapper } from './components/wrappers';
import { Connectors } from './components/configure_cases/connectors';
import * as i18n from './translations';
import { ClosureOptions } from './components/configure_cases/closure_options';
import { FieldMapping } from './components/configure_cases/field_mapping';

const backOptions = {
href: getCaseUrl(),
Expand All @@ -26,8 +28,12 @@ const wrapperPageStyle: Record<string, string> = {
paddingBottom: '0',
};

export const FormWrapper = styled.div`
const FormWrapper = styled.div`
${({ theme }) => css`
& > * {
margin-top 40px;
}
padding-top: ${theme.eui.paddingSizes.l};
padding-bottom: ${theme.eui.paddingSizes.l};
`}
Expand All @@ -44,6 +50,12 @@ const ConfigureCasesPageComponent: React.FC = () => (
<SectionWrapper>
<Connectors />
</SectionWrapper>
<SectionWrapper>
<ClosureOptions />
</SectionWrapper>
<SectionWrapper>
<FieldMapping />
</SectionWrapper>
</FormWrapper>
</WhitePageWrapper>
</WrapperPage>
Expand Down

0 comments on commit aea4811

Please sign in to comment.