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

[Security Solution][Exceptions] - Make esTypes and subType available to index patterns #72336

Merged
merged 12 commits into from
Jul 20, 2020
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,27 @@ 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';
// we don't have the types for waitFor just yet, so using "as waitFor" until when we do
import { wait as waitFor } from '@testing-library/react';

import { getField } from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks.ts';
import { AutocompleteFieldListsComponent } from './field_value_lists';
import { ListSchema } from '../../../lists_plugin_deps';
import { getFoundListSchemaMock } from '../../../../../lists/common/schemas/response/found_list_schema.mock';
import { getListResponseMock } from '../../../../../lists/common/schemas/response/list_schema.mock';
import { DATE_NOW } from '../../../../../lists/common/constants.mock';

import { AutocompleteFieldListsComponent } from './field_value_lists';

const mockStart = jest.fn();
const mockResult = getFoundListSchemaMock();
jest.mock('../../../common/lib/kibana');
const mockStart = jest.fn();
const mockKeywordList: ListSchema = {
...getListResponseMock(),
id: 'keyword_list',
type: 'keyword',
name: 'keyword list',
};
const mockResult = { ...getFoundListSchemaMock() };
mockResult.data = [...mockResult.data, mockKeywordList];
jest.mock('../../../lists_plugin_deps', () => {
const originalModule = jest.requireActual('../../../lists_plugin_deps');

Expand All @@ -31,7 +44,7 @@ jest.mock('../../../lists_plugin_deps', () => {
});

describe('AutocompleteFieldListsComponent', () => {
test('it renders disabled if "isDisabled" is true', () => {
test('it renders disabled if "isDisabled" is true', async () => {
const wrapper = mount(
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>
<AutocompleteFieldListsComponent
Expand All @@ -46,14 +59,16 @@ describe('AutocompleteFieldListsComponent', () => {
</ThemeProvider>
);

expect(
wrapper
.find(`[data-test-subj="valuesAutocompleteComboBox listsComboxBox"] input`)
.prop('disabled')
).toBeTruthy();
await waitFor(() => {
expect(
wrapper
.find(`[data-test-subj="valuesAutocompleteComboBox listsComboxBox"] input`)
.prop('disabled')
).toBeTruthy();
});
});

test('it renders loading if "isLoading" is true', () => {
test('it renders loading if "isLoading" is true', async () => {
const wrapper = mount(
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>
<AutocompleteFieldListsComponent
Expand All @@ -67,20 +82,23 @@ describe('AutocompleteFieldListsComponent', () => {
/>
</ThemeProvider>
);
wrapper
.find(`[data-test-subj="valuesAutocompleteComboBox listsComboxBox"] button`)
.at(0)
.simulate('click');
expect(

await waitFor(() => {
wrapper
.find(
`EuiComboBoxOptionsList[data-test-subj="valuesAutocompleteComboBox listsComboxBox-optionsList"]`
)
.prop('isLoading')
).toBeTruthy();
.find(`[data-test-subj="valuesAutocompleteComboBox listsComboxBox"] button`)
.at(0)
.simulate('click');
expect(
wrapper
.find(
`EuiComboBoxOptionsList[data-test-subj="valuesAutocompleteComboBox listsComboxBox-optionsList"]`
)
.prop('isLoading')
).toBeTruthy();
});
});

test('it allows user to clear values if "isClearable" is true', () => {
test('it allows user to clear values if "isClearable" is true', async () => {
const wrapper = mount(
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>
<AutocompleteFieldListsComponent
Expand All @@ -94,15 +112,62 @@ describe('AutocompleteFieldListsComponent', () => {
/>
</ThemeProvider>
);

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

test('it correctly displays selected list', () => {
test('it correctly displays lists that match the selected "keyword" field esType', () => {
const wrapper = mount(
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>
<AutocompleteFieldListsComponent
placeholder="Placeholder text"
selectedField={getField('@tags')}
selectedValue=""
isLoading={false}
isClearable={false}
isDisabled={false}
onChange={jest.fn()}
/>
</ThemeProvider>
);

wrapper.find('[data-test-subj="comboBoxToggleListButton"] button').simulate('click');

expect(
wrapper
.find('EuiComboBox[data-test-subj="valuesAutocompleteComboBox listsComboxBox"]')
.prop('options')
).toEqual([{ label: 'keyword list' }]);
});

test('it correctly displays lists that match the selected "ip" field esType', () => {
const wrapper = mount(
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>
<AutocompleteFieldListsComponent
placeholder="Placeholder text"
selectedField={getField('ip')}
selectedValue=""
isLoading={false}
isClearable={false}
isDisabled={false}
onChange={jest.fn()}
/>
</ThemeProvider>
);

wrapper.find('[data-test-subj="comboBoxToggleListButton"] button').simulate('click');

expect(
wrapper
.find('EuiComboBox[data-test-subj="valuesAutocompleteComboBox listsComboxBox"]')
.prop('options')
).toEqual([{ label: 'some name' }]);
});

test('it correctly displays selected list', async () => {
const wrapper = mount(
<ThemeProvider theme={() => ({ eui: euiLightVars, darkMode: false })}>
<AutocompleteFieldListsComponent
Expand Down Expand Up @@ -146,15 +211,15 @@ describe('AutocompleteFieldListsComponent', () => {
}).onChange([{ label: 'some name' }]);

expect(mockOnChange).toHaveBeenCalledWith({
created_at: '2020-04-20T15:25:31.830Z',
created_at: DATE_NOW,
created_by: 'some user',
description: 'some description',
id: 'some-list-id',
meta: {},
name: 'some name',
tie_breaker_id: '6a76b69d-80df-4ab2-8c3e-85f466b06a0e',
type: 'ip',
updated_at: '2020-04-20T15:25:31.830Z',
updated_at: DATE_NOW,
updated_by: 'some user',
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,12 @@ export const AutocompleteFieldListsComponent: React.FC<AutocompleteFieldListsPro
const getLabel = useCallback(({ name }) => name, []);

const optionsMemo = useMemo(() => {
if (selectedField != null) {
return lists.filter(({ type }) => type === selectedField.type);
if (
selectedField != null &&
selectedField.esTypes != null &&
selectedField.esTypes.length > 0
) {
return lists.filter(({ type }) => selectedField.esTypes?.includes(type));
} else {
return [];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ export const AutocompleteFieldMatchComponent: React.FC<AutocompleteFieldMatchPro
});
};

const isValid = useMemo(
(): boolean => validateParams(selectedValue, selectedField ? selectedField.type : ''),
[selectedField, selectedValue]
);
const isValid = useMemo((): boolean => validateParams(selectedValue, selectedField), [
selectedField,
selectedValue,
]);

return (
<EuiComboBox
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export const AutocompleteFieldMatchAnyComponent: React.FC<AutocompleteFieldMatch

const isValid = useMemo((): boolean => {
const areAnyInvalid = selectedComboOptions.filter(
({ label }) => !validateParams(label, selectedField ? selectedField.type : '')
({ label }) => !validateParams(label, selectedField)
);
return areAnyInvalid.length === 0;
}, [selectedComboOptions, selectedField]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,49 +55,25 @@ describe('helpers', () => {

describe('#validateParams', () => {
test('returns true if value is undefined', () => {
const isValid = validateParams(undefined, 'date');
const isValid = validateParams(undefined, getField('@timestamp'));

expect(isValid).toBeTruthy();
});

test('returns true if value is empty string', () => {
const isValid = validateParams('', 'date');
const isValid = validateParams('', getField('@timestamp'));

expect(isValid).toBeTruthy();
});

test('returns true if type is "date" and value is valid', () => {
const isValid = validateParams('1994-11-05T08:15:30-05:00', 'date');
const isValid = validateParams('1994-11-05T08:15:30-05:00', getField('@timestamp'));

expect(isValid).toBeTruthy();
});

test('returns false if type is "date" and value is not valid', () => {
const isValid = validateParams('1593478826', 'date');

expect(isValid).toBeFalsy();
});

test('returns true if type is "ip" and value is valid', () => {
const isValid = validateParams('126.45.211.34', 'ip');

expect(isValid).toBeTruthy();
});

test('returns false if type is "ip" and value is not valid', () => {
const isValid = validateParams('hellooo', 'ip');

expect(isValid).toBeFalsy();
});

test('returns true if type is "number" and value is valid', () => {
const isValid = validateParams('123', 'number');

expect(isValid).toBeTruthy();
});

test('returns false if type is "number" and value is not valid', () => {
const isValid = validateParams('not a number', 'number');
const isValid = validateParams('1593478826', getField('@timestamp'));

expect(isValid).toBeFalsy();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import dateMath from '@elastic/datemath';
import { EuiComboBoxOptionOption } from '@elastic/eui';

import { IFieldType, Ipv4Address } from '../../../../../../../src/plugins/data/common';
import { IFieldType } from '../../../../../../../src/plugins/data/common';

import {
EXCEPTION_OPERATORS,
Expand All @@ -30,29 +30,27 @@ export const getOperators = (field: IFieldType | undefined): OperatorOption[] =>
}
};

export function validateParams(params: string | undefined, type: string) {
export const validateParams = (
params: string | undefined,
field: IFieldType | undefined
): boolean => {
// Box would show error state if empty otherwise
if (params == null || params === '') {
return true;
}

switch (type) {
case 'date':
const moment = dateMath.parse(params);
return Boolean(moment && moment.isValid());
case 'ip':
try {
return Boolean(new Ipv4Address(params));
} catch (e) {
return false;
}
case 'number':
const val = parseFloat(params);
return typeof val === 'number' && !isNaN(val);
default:
return true;
}
}
const types = field != null && field.esTypes != null ? field.esTypes : [];

return types.reduce<boolean>((acc, type) => {
switch (type) {
case 'date':
const moment = dateMath.parse(params);
return Boolean(moment && moment.isValid());
Copy link
Contributor

Choose a reason for hiding this comment

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

nit, can you just do: return moment && moment.isValid() ? Won't that work as well without introducing the capital boolean boxing here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried removing it and the test yelled at me saying expected boolean not string. Leaving for now.

default:
return acc;
}
}, true);
};

export function getGenericComboBoxProps<T>({
options,
Expand Down
Loading