Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SIEM][Exceptions] - Exception builder component #67013

Merged
merged 59 commits into from
Jul 2, 2020
Merged
Show file tree
Hide file tree
Changes from 58 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
c2702ed
added modified and or badge, should move to common folder
yctercero May 18, 2020
1a0b722
added and or buttons with tests
yctercero May 18, 2020
4436fed
added exception item entry component and helpers with tests
yctercero May 18, 2020
ad80d70
added exception item, needs tests, and some additional utils
yctercero May 18, 2020
3008b0d
added exception item tests and made some modifications
yctercero May 18, 2020
6b47020
added tests, index, cleaned up
yctercero May 18, 2020
e1c02e4
moved exception builder into common components
yctercero May 18, 2020
cf861dc
Merge branch 'master' of github.com:yctercero/kibana into exception-b…
yctercero May 18, 2020
8b93681
cleaned up a few things, wip
yctercero May 19, 2020
a7dc8e6
Merge branch 'master' of github.com:yctercero/kibana into exception-b…
yctercero May 21, 2020
3ded2df
Merge branch 'master' of github.com:yctercero/kibana into exception-b…
yctercero May 27, 2020
0934ca8
Merge branch 'master' of github.com:yctercero/kibana into exception-b…
yctercero Jun 4, 2020
0f84083
Merge branch 'master' of github.com:yctercero/kibana into exception-b…
yctercero Jun 9, 2020
fe16cdf
Merge branch 'master' of github.com:yctercero/kibana into exception-b…
yctercero Jun 18, 2020
c35bb4b
Add pure API functions and react hooks for value list APIs
rylnd Jun 18, 2020
c83dabc
Fix type errors in hook tests
rylnd Jun 19, 2020
3572c6e
Merge branch 'master' of github.com:yctercero/kibana into exception-b…
yctercero Jun 23, 2020
fa512d7
Merge branch 'master' into value_lists_api
rylnd Jun 24, 2020
247f8e4
Document current limitations of useAsyncTask
rylnd Jun 24, 2020
b657229
Defines a new validation function that returns an Either instead of a…
rylnd Jun 24, 2020
d627a64
Remove duplicated copyright comment
rylnd Jun 24, 2020
8d2eb3a
WIP: Perform request/response validations in the FP style
rylnd Jun 25, 2020
d99ba54
Adds helper function to convert a TaskEither back to a Promise
rylnd Jun 25, 2020
ef0a29e
Merge branch 'master' of github.com:yctercero/kibana into exception-b…
yctercero Jun 25, 2020
6920459
Adds request/response validations to findLists
rylnd Jun 25, 2020
6d6b5cc
Refactor our API types
rylnd Jun 25, 2020
0f223aa
Add request/response validation to import/export functions
rylnd Jun 25, 2020
e7f5b86
Fix type errors
rylnd Jun 25, 2020
2fe8948
Merge branch 'master' of github.com:yctercero/kibana into exception-b…
yctercero Jun 26, 2020
f433c51
Attempting to reduce plugin bundle size
rylnd Jun 26, 2020
15da0f7
Merge branch 'master' into value_lists_api
elasticmachine Jun 26, 2020
e8a0483
Merge branch 'master' of github.com:yctercero/kibana into exception-b…
yctercero Jun 26, 2020
9364770
Merge branch 'master' into value_lists_api
rylnd Jun 26, 2020
02101c7
useAsyncFn's initiator does not return a promise
rylnd Jun 26, 2020
4f43637
Fix failing test
rylnd Jun 28, 2020
e2fb4d0
reworking builder
yctercero Jun 28, 2020
3b8653a
wip - adding exception builder input blocks
yctercero Jun 29, 2020
dee8857
Merge branch 'value_lists_api' of https://github.com/rylnd/kibana int…
yctercero Jun 29, 2020
708cfb3
added reusable field, operator and autocomplete components with tests…
yctercero Jun 30, 2020
533f317
added exception builder components
yctercero Jun 30, 2020
bfa4f39
superficial change, updated lists exceptions public files to be exten…
yctercero Jun 30, 2020
824246b
exported operator enum for use in front end and added tests
yctercero Jun 30, 2020
15226b1
updated types in exceptions components
yctercero Jun 30, 2020
575e2d2
fixed up lint issues
yctercero Jun 30, 2020
b5d034c
Merge branch 'master' of github.com:yctercero/kibana into exceptions_…
yctercero Jun 30, 2020
b0d2286
Merge branch 'master' of github.com:yctercero/kibana into exception-b…
yctercero Jun 30, 2020
2c52325
reverting changes in prep for new changes from other branch
yctercero Jun 30, 2020
004e312
reverting changes in prep for new changes from other branch
yctercero Jun 30, 2020
2795bd1
reverting changes in prep for new changes from other branch
yctercero Jun 30, 2020
32212bb
Worked on exception builder in a separate branch than original, mergi…
yctercero Jun 30, 2020
c30cf95
some cleanup
yctercero Jun 30, 2020
91e02d0
Merge branch 'master' of github.com:yctercero/kibana into exception-b…
yctercero Jul 1, 2020
1bc48cd
clean up
yctercero Jul 1, 2020
a9b3e95
continued cleanup, added some more validation
yctercero Jul 1, 2020
58b2ceb
updated operator field label
yctercero Jul 1, 2020
3026957
fixed react warning, updated validation and tests
yctercero Jul 1, 2020
9341cba
few changes didnt get in, updated tests
yctercero Jul 1, 2020
ac3074e
updated creation of new exception item
yctercero Jul 1, 2020
7749035
Merge branch 'master' of github.com:yctercero/kibana into exception-b…
yctercero Jul 1, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion x-pack/plugins/lists/common/schemas/common/schemas.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { left } from 'fp-ts/lib/Either';

import { foldLeftRight, getPaths } from '../../siem_common_deps';

import { operator_type as operatorType } from './schemas';
import { operator, operator_type as operatorType } from './schemas';

describe('Common schemas', () => {
describe('operatorType', () => {
Expand Down Expand Up @@ -60,4 +60,35 @@ describe('Common schemas', () => {
expect(keys.length).toEqual(4);
});
});

describe('operator', () => {
test('it should validate for "included"', () => {
const payload = 'included';
const decoded = operator.decode(payload);
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
});

test('it should validate for "excluded"', () => {
const payload = 'excluded';
const decoded = operator.decode(payload);
const message = pipe(decoded, foldLeftRight);

expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
});

test('it should contain 2 keys', () => {
// Might seem like a weird test, but its meant to
// ensure that if operator is updated, you
// also update the operatorEnum, a workaround
// for io-ts not yet supporting enums
// https://github.com/gcanti/io-ts/issues/67
const keys = Object.keys(operator.keys);

expect(keys.length).toEqual(2);
});
});
});
4 changes: 4 additions & 0 deletions x-pack/plugins/lists/common/schemas/common/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ export type NamespaceType = t.TypeOf<typeof namespace_type>;

export const operator = t.keyof({ excluded: null, included: null });
export type Operator = t.TypeOf<typeof operator>;
export enum OperatorEnum {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Icky, I know. It's a workaround to enums not being a thing in io-ts yet - gcanti/io-ts#67 . I've added unit tests so that if the io-ts type operator is updated, tests will fail and user will see note about needing to update OperatorEnum as well.

INCLUDED = 'included',
EXCLUDED = 'excluded',
}

export const operator_type = t.keyof({
exists: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ const antennaStyles = css`
background: ${({ theme }) => theme.eui.euiColorLightShade};
position: relative;
width: 2px;
margin: 0 12px 0 0;
&:after {
background: ${({ theme }) => theme.eui.euiColorLightShade};
content: '';
Expand All @@ -40,10 +39,6 @@ const BottomAntenna = styled(EuiFlexItem)`
}
`;

const EuiFlexItemWrapper = styled(EuiFlexItem)`
margin: 0 12px 0 0;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed, ended up being unnecessary. This antenna version of the badge is only being used in exceptions UI, so would not have ramifications elsewhere.

`;

export const RoundedBadgeAntenna: React.FC<{ type: AndOr }> = ({ type }) => (
<EuiFlexGroup
className="andBadgeContainer"
Expand All @@ -52,9 +47,9 @@ export const RoundedBadgeAntenna: React.FC<{ type: AndOr }> = ({ type }) => (
alignItems="center"
>
<TopAntenna data-test-subj="andOrBadgeBarTop" grow={1} />
<EuiFlexItemWrapper grow={false}>
<EuiFlexItem grow={false}>
<RoundedBadge type={type} />
</EuiFlexItemWrapper>
</EuiFlexItem>
<BottomAntenna data-test-subj="andOrBadgeBarBottom" grow={1} />
</EuiFlexGroup>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
* 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 { ThemeProvider } from 'styled-components';
import { mount } from 'enzyme';
import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';
import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui';

import {
fields,
getField,
} from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks.ts';
import { FieldComponent } from './field';

describe('FieldComponent', () => {
test('it renders disabled if "isDisabled" is true', () => {
const wrapper = mount(
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>
<FieldComponent
placeholder="Placeholder text"
indexPattern={{
id: '1234',
title: 'logstash-*',
fields,
}}
selectedField={getField('machine.os.raw')}
isLoading={false}
isClearable={false}
isDisabled={true}
onChange={jest.fn()}
/>
</ThemeProvider>
);

expect(
wrapper.find(`[data-test-subj="fieldAutocompleteComboBox"] input`).prop('disabled')
).toBeTruthy();
});

test('it renders loading if "isLoading" is true', () => {
const wrapper = mount(
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>
<FieldComponent
placeholder="Placeholder text"
indexPattern={{
id: '1234',
title: 'logstash-*',
fields,
}}
selectedField={getField('machine.os.raw')}
isLoading={true}
isClearable={false}
isDisabled={false}
onChange={jest.fn()}
/>
</ThemeProvider>
);
wrapper.find(`[data-test-subj="fieldAutocompleteComboBox"] button`).at(0).simulate('click');
expect(
wrapper
.find(`EuiComboBoxOptionsList[data-test-subj="fieldAutocompleteComboBox-optionsList"]`)
.prop('isLoading')
).toBeTruthy();
});

test('it allows user to clear values if "isClearable" is true', () => {
const wrapper = mount(
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>
<FieldComponent
placeholder="Placeholder text"
indexPattern={{
id: '1234',
title: 'logstash-*',
fields,
}}
selectedField={getField('machine.os.raw')}
isLoading={false}
isClearable={true}
isDisabled={false}
onChange={jest.fn()}
/>
</ThemeProvider>
);

expect(
wrapper
.find(`[data-test-subj="comboBoxInput"]`)
.hasClass('euiComboBox__inputWrap-isClearable')
).toBeTruthy();
});

test('it correctly displays selected field', () => {
const wrapper = mount(
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>
<FieldComponent
placeholder="Placeholder text"
indexPattern={{
id: '1234',
title: 'logstash-*',
fields,
}}
selectedField={getField('machine.os.raw')}
isLoading={false}
isClearable={false}
isDisabled={false}
onChange={jest.fn()}
/>
</ThemeProvider>
);

expect(
wrapper.find(`[data-test-subj="fieldAutocompleteComboBox"] EuiComboBoxPill`).at(0).text()
).toEqual('machine.os.raw');
});

test('it invokes "onChange" when option selected', () => {
const mockOnChange = jest.fn();
const wrapper = mount(
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>
<FieldComponent
placeholder="Placeholder text"
indexPattern={{
id: '1234',
title: 'logstash-*',
fields,
}}
selectedField={getField('machine.os.raw')}
isLoading={false}
isClearable={false}
isDisabled={false}
onChange={mockOnChange}
/>
</ThemeProvider>
);

((wrapper.find(EuiComboBox).props() as unknown) as {
onChange: (a: EuiComboBoxOptionOption[]) => void;
}).onChange([{ label: 'machine.os' }]);

expect(mockOnChange).toHaveBeenCalledWith([
{
aggregatable: true,
count: 0,
esTypes: ['text'],
name: 'machine.os',
readFromDocValues: false,
scripted: false,
searchable: true,
type: 'string',
},
]);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* 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, { useMemo, useCallback } from 'react';
import { EuiComboBoxOptionOption, EuiComboBox } from '@elastic/eui';

import { IFieldType, IIndexPattern } from '../../../../../../../src/plugins/data/common';
import { getGenericComboBoxProps } from './helpers';
import { GetGenericComboBoxPropsReturn } from './types';

interface OperatorProps {
placeholder: string;
selectedField: IFieldType | undefined;
indexPattern: IIndexPattern | undefined;
isLoading: boolean;
isDisabled: boolean;
isClearable: boolean;
fieldInputWidth?: number;
onChange: (a: IFieldType[]) => void;
}

export const FieldComponent: React.FC<OperatorProps> = ({
placeholder,
selectedField,
indexPattern,
isLoading = false,
isDisabled = false,
isClearable = false,
fieldInputWidth = 190,
onChange,
}): JSX.Element => {
const getLabel = useCallback((field): string => field.name, []);
const optionsMemo = useMemo((): IFieldType[] => (indexPattern ? indexPattern.fields : []), [
indexPattern,
]);
const selectedOptionsMemo = useMemo((): IFieldType[] => (selectedField ? [selectedField] : []), [
selectedField,
]);
const { comboOptions, labels, selectedComboOptions } = useMemo(
(): GetGenericComboBoxPropsReturn =>
getGenericComboBoxProps<IFieldType>({
options: optionsMemo,
selectedOptions: selectedOptionsMemo,
getLabel,
}),
[optionsMemo, selectedOptionsMemo, getLabel]
);

const handleValuesChange = (newOptions: EuiComboBoxOptionOption[]): void => {
const newValues: IFieldType[] = newOptions.map(
({ label }) => optionsMemo[labels.indexOf(label)]
);
onChange(newValues);
};

return (
<EuiComboBox
placeholder={placeholder}
options={comboOptions}
selectedOptions={selectedComboOptions}
onChange={handleValuesChange}
isLoading={isLoading}
isDisabled={isDisabled}
isClearable={isClearable}
singleSelection={{ asPlainText: true }}
data-test-subj="fieldAutocompleteComboBox"
style={{ width: `${fieldInputWidth}px` }}
/>
);
};

FieldComponent.displayName = 'Field';
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 { ThemeProvider } from 'styled-components';
import { mount } from 'enzyme';
import euiLightVars from '@elastic/eui/dist/eui_theme_light.json';

import { AutocompleteFieldExistsComponent } from './field_value_exists';

describe('AutocompleteFieldExistsComponent', () => {
test('it renders field disabled', () => {
const wrapper = mount(
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>
<AutocompleteFieldExistsComponent placeholder="Placeholder text" />
</ThemeProvider>
);

expect(
wrapper
.find(`[data-test-subj="valuesAutocompleteComboBox existsComboxBox"] input`)
.prop('disabled')
).toBeTruthy();
});
});
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 { EuiComboBox } from '@elastic/eui';

interface AutocompleteFieldExistsProps {
placeholder: string;
}

export const AutocompleteFieldExistsComponent: React.FC<AutocompleteFieldExistsProps> = ({
placeholder,
}): JSX.Element => (
<EuiComboBox
placeholder={placeholder}
options={[]}
selectedOptions={[]}
onChange={undefined}
isDisabled
data-test-subj="valuesAutocompleteComboBox existsComboxBox"
fullWidth
/>
);

AutocompleteFieldExistsComponent.displayName = 'AutocompleteFieldExists';
Loading