diff --git a/x-pack/plugins/ml/public/settings/filter_lists/components/add_item_popover/__snapshots__/add_item_popover.test.js.snap b/x-pack/plugins/ml/public/settings/filter_lists/components/add_item_popover/__snapshots__/add_item_popover.test.js.snap
new file mode 100644
index 000000000000..fc98b2e69df7
--- /dev/null
+++ b/x-pack/plugins/ml/public/settings/filter_lists/components/add_item_popover/__snapshots__/add_item_popover.test.js.snap
@@ -0,0 +1,232 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`AddItemPopover calls addItems with multiple items on clicking Add button 1`] = `
+
+
+ Add item
+
+ }
+ closePopover={[Function]}
+ id="add_item_popover"
+ isOpen={false}
+ ownFocus={true}
+ panelClassName="ml-add-filter-item-popover"
+ panelPaddingSize="m"
+ >
+
+
+
+
+
+
+ Enter one item per line
+
+
+
+
+
+ Add
+
+
+
+
+
+`;
+
+exports[`AddItemPopover opens the popover onButtonClick 1`] = `
+
+
+ Add item
+
+ }
+ closePopover={[Function]}
+ id="add_item_popover"
+ isOpen={true}
+ ownFocus={true}
+ panelClassName="ml-add-filter-item-popover"
+ panelPaddingSize="m"
+ >
+
+
+
+
+
+
+ Enter one item per line
+
+
+
+
+
+ Add
+
+
+
+
+
+`;
+
+exports[`AddItemPopover renders the popover 1`] = `
+
+
+ Add item
+
+ }
+ closePopover={[Function]}
+ id="add_item_popover"
+ isOpen={false}
+ ownFocus={true}
+ panelClassName="ml-add-filter-item-popover"
+ panelPaddingSize="m"
+ >
+
+
+
+
+
+
+ Enter one item per line
+
+
+
+
+
+ Add
+
+
+
+
+
+`;
diff --git a/x-pack/plugins/ml/public/settings/filter_lists/components/add_item_popover/add_item_popover.test.js b/x-pack/plugins/ml/public/settings/filter_lists/components/add_item_popover/add_item_popover.test.js
new file mode 100644
index 000000000000..e4ce7e7104ae
--- /dev/null
+++ b/x-pack/plugins/ml/public/settings/filter_lists/components/add_item_popover/add_item_popover.test.js
@@ -0,0 +1,64 @@
+/*
+ * 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 { shallow } from 'enzyme';
+import React from 'react';
+
+import { AddItemPopover } from './add_item_popover';
+
+function prepareTest(addItemsFn) {
+
+ const props = {
+ addItems: addItemsFn
+ };
+
+ const wrapper = shallow(
+
+ );
+
+ return wrapper;
+}
+
+describe('AddItemPopover', () => {
+
+ test('renders the popover', () => {
+ const addItems = jest.fn(() => {});
+ const wrapper = prepareTest(addItems);
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ test('opens the popover onButtonClick', () => {
+ const addItems = jest.fn(() => {});
+ const wrapper = prepareTest(addItems);
+ const instance = wrapper.instance();
+ instance.onButtonClick();
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ test('calls addItems with one item on clicking Add button', () => {
+ const addItems = jest.fn(() => {});
+ const wrapper = prepareTest(addItems);
+ wrapper.find('EuiTextArea').simulate('change', { target: { value: 'google.com' } });
+ const instance = wrapper.instance();
+ instance.onAddButtonClick();
+ wrapper.update();
+ expect(addItems).toHaveBeenCalledWith(['google.com']);
+ });
+
+ test('calls addItems with multiple items on clicking Add button', () => {
+ const addItems = jest.fn(() => {});
+ const wrapper = prepareTest(addItems);
+ wrapper.find('EuiTextArea').simulate('change', { target: { value: 'google.com\nelastic.co' } });
+ const instance = wrapper.instance();
+ instance.onAddButtonClick();
+ wrapper.update();
+ expect(addItems).toHaveBeenCalledWith(['google.com', 'elastic.co']);
+ expect(wrapper).toMatchSnapshot();
+ });
+
+});
diff --git a/x-pack/plugins/ml/public/settings/filter_lists/components/delete_filter_list_modal/__snapshots__/delete_filter_list_modal.test.js.snap b/x-pack/plugins/ml/public/settings/filter_lists/components/delete_filter_list_modal/__snapshots__/delete_filter_list_modal.test.js.snap
new file mode 100644
index 000000000000..d2cdca52de78
--- /dev/null
+++ b/x-pack/plugins/ml/public/settings/filter_lists/components/delete_filter_list_modal/__snapshots__/delete_filter_list_modal.test.js.snap
@@ -0,0 +1,104 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`DeleteFilterListModal false canDeleteFilter privilege renders as disabled delete button 1`] = `
+
+
+ Delete
+
+
+`;
+
+exports[`DeleteFilterListModal renders as delete button after opening and closing modal 1`] = `
+
+
+ Delete
+
+
+`;
+
+exports[`DeleteFilterListModal renders as disabled delete button when no lists selected 1`] = `
+
+
+ Delete
+
+
+`;
+
+exports[`DeleteFilterListModal renders as enabled delete button when a list is selected 1`] = `
+
+
+ Delete
+
+
+`;
+
+exports[`DeleteFilterListModal renders modal after clicking delete button 1`] = `
+
+
+ Delete
+
+
+
+
+ Are you sure you want to delete
+ these filter lists
+ ?
+
+
+
+
+`;
diff --git a/x-pack/plugins/ml/public/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.js b/x-pack/plugins/ml/public/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.js
index 43b5be11a4ca..f147b4891283 100644
--- a/x-pack/plugins/ml/public/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.js
+++ b/x-pack/plugins/ml/public/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.js
@@ -16,7 +16,7 @@ import {
EUI_MODAL_CONFIRM_BUTTON,
} from '@elastic/eui';
-import { checkPermission } from 'plugins/ml/privilege/check_privilege';
+import { checkPermission } from '../../../../privilege/check_privilege';
import { deleteFilterLists } from './delete_filter_lists';
/*
@@ -87,7 +87,7 @@ export class DeleteFilterListModal extends Component {
iconType="trash"
color="danger"
onClick={this.showModal}
- isDisabled={(selectedFilterLists.length === 0 || this.canDeleteFilter === false)}
+ isDisabled={(selectedFilterLists === undefined || selectedFilterLists.length === 0 || this.canDeleteFilter === false)}
>
Delete
@@ -98,7 +98,7 @@ export class DeleteFilterListModal extends Component {
}
}
DeleteFilterListModal.propTypes = {
- selectedFilterLists: PropTypes.array.isRequired,
+ selectedFilterLists: PropTypes.array,
};
diff --git a/x-pack/plugins/ml/public/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.test.js b/x-pack/plugins/ml/public/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.test.js
new file mode 100644
index 000000000000..87078e1592e5
--- /dev/null
+++ b/x-pack/plugins/ml/public/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.test.js
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ */
+
+
+// Create a mock for the canDeleteFilter privilege check.
+// The mock is hoisted to the top, so need to prefix the mock function
+// with 'mock' so it can be used lazily.
+const mockCheckPermission = jest.fn(() => true);
+jest.mock('../../../../privilege/check_privilege', () => ({
+ checkPermission: (privilege) => mockCheckPermission(privilege)
+}));
+jest.mock('../../../../services/ml_api_service', () => 'ml');
+
+import { shallow } from 'enzyme';
+import React from 'react';
+
+import { DeleteFilterListModal } from './delete_filter_list_modal';
+
+const testSelectedLists = [
+ { filter_id: 'web_domains' },
+ { filter_id: 'test_instances' }
+];
+
+const testProps = {
+ selectedFilterLists: testSelectedLists
+};
+
+describe('DeleteFilterListModal', () => {
+
+ test('renders as disabled delete button when no lists selected', () => {
+ const component = shallow(
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+
+ test('renders as enabled delete button when a list is selected', () => {
+ const component = shallow(
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+
+ test('renders modal after clicking delete button', () => {
+ const wrapper = shallow(
+
+ );
+ wrapper.find('EuiButton').simulate('click');
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+ });
+
+
+ test('renders as delete button after opening and closing modal', () => {
+ const wrapper = shallow(
+
+ );
+ wrapper.find('EuiButton').simulate('click');
+ const instance = wrapper.instance();
+ instance.closeModal();
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+ });
+
+});
+
+describe('DeleteFilterListModal false canDeleteFilter privilege', () => {
+
+ beforeEach(() => {
+ jest.resetModules();
+ });
+
+
+ test('renders as disabled delete button', () => {
+
+ mockCheckPermission.mockImplementationOnce(() => {
+ return false;
+ });
+
+ const component = shallow(
+
+ );
+
+ expect(component).toMatchSnapshot();
+ });
+
+});
diff --git a/x-pack/plugins/ml/public/settings/filter_lists/components/delete_filter_list_modal/delete_filter_lists.js b/x-pack/plugins/ml/public/settings/filter_lists/components/delete_filter_list_modal/delete_filter_lists.js
index b07bf002328e..42f65247483c 100644
--- a/x-pack/plugins/ml/public/settings/filter_lists/components/delete_filter_list_modal/delete_filter_lists.js
+++ b/x-pack/plugins/ml/public/settings/filter_lists/components/delete_filter_list_modal/delete_filter_lists.js
@@ -5,7 +5,7 @@
*/
import { toastNotifications } from 'ui/notify';
-import { ml } from 'plugins/ml/services/ml_api_service';
+import { ml } from '../../../../services/ml_api_service';
export async function deleteFilterLists(filterListsToDelete) {
diff --git a/x-pack/plugins/ml/public/settings/filter_lists/components/edit_description_popover/__snapshots__/edit_description_popover.test.js.snap b/x-pack/plugins/ml/public/settings/filter_lists/components/edit_description_popover/__snapshots__/edit_description_popover.test.js.snap
new file mode 100644
index 000000000000..e59216401033
--- /dev/null
+++ b/x-pack/plugins/ml/public/settings/filter_lists/components/edit_description_popover/__snapshots__/edit_description_popover.test.js.snap
@@ -0,0 +1,147 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`FilterListUsagePopover opens the popover onButtonClick 1`] = `
+
+
+ }
+ closePopover={[Function]}
+ id="filter_list_description_popover"
+ isOpen={true}
+ ownFocus={true}
+ panelPaddingSize="m"
+ >
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`FilterListUsagePopover renders the popover with a description 1`] = `
+
+
+ }
+ closePopover={[Function]}
+ id="filter_list_description_popover"
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="m"
+ >
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`FilterListUsagePopover renders the popover with no description 1`] = `
+
+
+ }
+ closePopover={[Function]}
+ id="filter_list_description_popover"
+ isOpen={false}
+ ownFocus={true}
+ panelPaddingSize="m"
+ >
+
+
+
+
+
+
+
+
+
+`;
diff --git a/x-pack/plugins/ml/public/settings/filter_lists/components/edit_description_popover/edit_description_popover.test.js b/x-pack/plugins/ml/public/settings/filter_lists/components/edit_description_popover/edit_description_popover.test.js
new file mode 100644
index 000000000000..2d6a4372771b
--- /dev/null
+++ b/x-pack/plugins/ml/public/settings/filter_lists/components/edit_description_popover/edit_description_popover.test.js
@@ -0,0 +1,69 @@
+/*
+ * 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 { shallow } from 'enzyme';
+import React from 'react';
+
+import { EditDescriptionPopover } from './edit_description_popover';
+
+function prepareTest(updateDescriptionFn) {
+
+ const props = {
+ description: 'A list of known safe domains',
+ updateDescription: updateDescriptionFn
+ };
+
+ const wrapper = shallow(
+
+ );
+
+ return wrapper;
+}
+
+describe('FilterListUsagePopover', () => {
+
+ test('renders the popover with no description', () => {
+ const updateDescription = jest.fn(() => {});
+
+ const props = {
+ updateDescription
+ };
+
+ const component = shallow(
+
+ );
+
+ expect(component).toMatchSnapshot();
+
+ });
+
+ test('renders the popover with a description', () => {
+ const updateDescription = jest.fn(() => {});
+ const wrapper = prepareTest(updateDescription);
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ test('opens the popover onButtonClick', () => {
+ const updateDescription = jest.fn(() => {});
+ const wrapper = prepareTest(updateDescription);
+ const instance = wrapper.instance();
+ instance.onButtonClick();
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ test('calls updateDescription on closing', () => {
+ const updateDescription = jest.fn(() => {});
+ const wrapper = prepareTest(updateDescription);
+ const instance = wrapper.instance();
+ instance.onButtonClick();
+ instance.closePopover();
+ wrapper.update();
+ expect(updateDescription).toHaveBeenCalled();
+ });
+
+});
diff --git a/x-pack/plugins/ml/public/settings/filter_lists/components/filter_list_usage_popover/__snapshots__/filter_list_usage_popover.test.js.snap b/x-pack/plugins/ml/public/settings/filter_lists/components/filter_list_usage_popover/__snapshots__/filter_list_usage_popover.test.js.snap
new file mode 100644
index 000000000000..d66c33bf095e
--- /dev/null
+++ b/x-pack/plugins/ml/public/settings/filter_lists/components/filter_list_usage_popover/__snapshots__/filter_list_usage_popover.test.js.snap
@@ -0,0 +1,120 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`FilterListUsagePopover opens the popover onButtonClick 1`] = `
+
+
+ 3 detectors
+
+ }
+ closePopover={[Function]}
+ id="detector_filter_list_usage"
+ isOpen={true}
+ ownFocus={true}
+ panelClassName="ml-filter-list-usage-popover"
+ panelPaddingSize="m"
+ >
+
+ -
+ mean responsetime
+
+ -
+ max responsetime
+
+ -
+ count
+
+
+
+
+`;
+
+exports[`FilterListUsagePopover renders the popover for 1 job 1`] = `
+
+
+ 1 job
+
+ }
+ closePopover={[Function]}
+ id="job_filter_list_usage"
+ isOpen={false}
+ ownFocus={true}
+ panelClassName="ml-filter-list-usage-popover"
+ panelPaddingSize="m"
+ >
+
+
+
+`;
+
+exports[`FilterListUsagePopover renders the popover for 2 detectors 1`] = `
+
+
+ 3 detectors
+
+ }
+ closePopover={[Function]}
+ id="detector_filter_list_usage"
+ isOpen={false}
+ ownFocus={true}
+ panelClassName="ml-filter-list-usage-popover"
+ panelPaddingSize="m"
+ >
+
+ -
+ mean responsetime
+
+ -
+ max responsetime
+
+ -
+ count
+
+
+
+
+`;
diff --git a/x-pack/plugins/ml/public/settings/filter_lists/components/filter_list_usage_popover/filter_list_usage_popover.test.js b/x-pack/plugins/ml/public/settings/filter_lists/components/filter_list_usage_popover/filter_list_usage_popover.test.js
new file mode 100644
index 000000000000..83f219b001b0
--- /dev/null
+++ b/x-pack/plugins/ml/public/settings/filter_lists/components/filter_list_usage_popover/filter_list_usage_popover.test.js
@@ -0,0 +1,56 @@
+/*
+ * 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 { shallow } from 'enzyme';
+import React from 'react';
+
+import { FilterListUsagePopover } from './filter_list_usage_popover';
+
+
+function prepareDetectorsTest() {
+ const props = {
+ entityType: 'detector',
+ entityValues: ['mean responsetime', 'max responsetime', 'count']
+ };
+
+ const wrapper = shallow(
+
+ );
+
+ return { wrapper };
+}
+
+describe('FilterListUsagePopover', () => {
+
+ test('renders the popover for 1 job', () => {
+ const props = {
+ entityType: 'job',
+ entityValues: ['farequote']
+ };
+
+ const component = shallow(
+
+ );
+
+ expect(component).toMatchSnapshot();
+
+ });
+
+ test('renders the popover for 2 detectors', () => {
+ const test = prepareDetectorsTest();
+ expect(test.wrapper).toMatchSnapshot();
+ });
+
+ test('opens the popover onButtonClick', () => {
+ const test = prepareDetectorsTest();
+ const instance = test.wrapper.instance();
+ instance.onButtonClick();
+ test.wrapper.update();
+ expect(test.wrapper).toMatchSnapshot();
+ });
+
+});
diff --git a/x-pack/plugins/ml/public/settings/filter_lists/edit/__snapshots__/edit_filter_list.test.js.snap b/x-pack/plugins/ml/public/settings/filter_lists/edit/__snapshots__/edit_filter_list.test.js.snap
new file mode 100644
index 000000000000..44dc4ddddbd9
--- /dev/null
+++ b/x-pack/plugins/ml/public/settings/filter_lists/edit/__snapshots__/edit_filter_list.test.js.snap
@@ -0,0 +1,820 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`EditFilterList adds new items to filter list 1`] = `
+
+
+
+
+
+
+
+
+
+ Cancel
+
+
+
+
+ Save
+
+
+
+
+
+`;
+
+exports[`EditFilterList renders after selecting an item and deleting it 1`] = `
+
+
+
+
+
+
+
+
+
+ Cancel
+
+
+
+
+ Save
+
+
+
+
+
+`;
+
+exports[`EditFilterList renders after selecting an item and deleting it 2`] = `
+
+
+
+
+
+
+
+
+
+ Cancel
+
+
+
+
+ Save
+
+
+
+
+
+`;
+
+exports[`EditFilterList renders the edit page for a new filter list and updates ID 1`] = `
+
+
+
+
+
+
+
+
+
+ Cancel
+
+
+
+
+ Save
+
+
+
+
+
+`;
+
+exports[`EditFilterList renders the edit page for a new filter list and updates ID 2`] = `
+
+
+
+
+
+
+
+
+
+ Cancel
+
+
+
+
+ Save
+
+
+
+
+
+`;
+
+exports[`EditFilterList renders the edit page for an existing filter list and updates description 1`] = `
+
+
+
+
+
+
+
+
+
+ Cancel
+
+
+
+
+ Save
+
+
+
+
+
+`;
+
+exports[`EditFilterList renders the edit page for an existing filter list and updates description 2`] = `
+
+
+
+
+
+
+
+
+
+ Cancel
+
+
+
+
+ Save
+
+
+
+
+
+`;
+
+exports[`EditFilterList updates the items per page 1`] = `
+
+
+
+
+
+
+
+
+
+ Cancel
+
+
+
+
+ Save
+
+
+
+
+
+`;
diff --git a/x-pack/plugins/ml/public/settings/filter_lists/edit/__snapshots__/header.test.js.snap b/x-pack/plugins/ml/public/settings/filter_lists/edit/__snapshots__/header.test.js.snap
new file mode 100644
index 000000000000..9ca5b210256b
--- /dev/null
+++ b/x-pack/plugins/ml/public/settings/filter_lists/edit/__snapshots__/header.test.js.snap
@@ -0,0 +1,472 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`EditFilterListHeader renders the header when creating a new filter list with ID, description and items set 1`] = `
+
+
+
+
+
+
+
+ Create new filter list
+
+
+
+
+
+
+ 15
+
+ items
+ in total
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A test filter list
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`EditFilterListHeader renders the header when creating a new filter list with the ID not set 1`] = `
+
+
+
+
+
+
+
+ Create new filter list
+
+
+
+
+
+
+ 0
+
+ items
+ in total
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Add a description
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`EditFilterListHeader renders the header when editing an existing unused filter list with no description or items 1`] = `
+
+
+
+
+
+
+
+ Filter list test_filter_list
+
+
+
+
+
+
+ 0
+
+ items
+ in total
+
+
+
+
+
+
+
+
+
+
+
+ Add a description
+
+
+
+
+
+
+
+
+
+
+
+ This filter list is not used by any jobs.
+
+
+
+
+
+`;
+
+exports[`EditFilterListHeader renders the header when editing an existing used filter list with description and items set 1`] = `
+
+
+
+
+
+
+
+ Filter list test_filter_list
+
+
+
+
+
+
+ 15
+
+ items
+ in total
+
+
+
+
+
+
+
+
+
+
+
+ A test filter list
+
+
+
+
+
+
+
+
+
+
+
+ This filter list is used in
+
+
+
+ across
+
+
+
+
+
+
+`;
diff --git a/x-pack/plugins/ml/public/settings/filter_lists/edit/__snapshots__/toolbar.test.js.snap b/x-pack/plugins/ml/public/settings/filter_lists/edit/__snapshots__/toolbar.test.js.snap
new file mode 100644
index 000000000000..80b743fe73eb
--- /dev/null
+++ b/x-pack/plugins/ml/public/settings/filter_lists/edit/__snapshots__/toolbar.test.js.snap
@@ -0,0 +1,113 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`EditFilterListToolbar renders the toolbar with no items selected 1`] = `
+
+
+
+
+
+
+
+
+
+ Delete item
+
+
+
+
+
+
+
+`;
+
+exports[`EditFilterListToolbar renders the toolbar with one item selected 1`] = `
+
+
+
+
+
+
+
+
+
+ Delete item
+
+
+
+
+
+
+
+`;
diff --git a/x-pack/plugins/ml/public/settings/filter_lists/edit/edit_filter_list.js b/x-pack/plugins/ml/public/settings/filter_lists/edit/edit_filter_list.js
index 8a7bd87f003c..f63556160400 100644
--- a/x-pack/plugins/ml/public/settings/filter_lists/edit/edit_filter_list.js
+++ b/x-pack/plugins/ml/public/settings/filter_lists/edit/edit_filter_list.js
@@ -30,17 +30,21 @@ import { toastNotifications } from 'ui/notify';
import { EditFilterListHeader } from './header';
import { EditFilterListToolbar } from './toolbar';
-import { ItemsGrid } from 'plugins/ml/components/items_grid';
+import { ItemsGrid } from '../../../components/items_grid';
import {
isValidFilterListId,
saveFilterList
} from './utils';
-import { ml } from 'plugins/ml/services/ml_api_service';
+import { ml } from '../../../services/ml_api_service';
const DEFAULT_ITEMS_PER_PAGE = 50;
// Returns the list of items that match the query entered in the EuiSearchBar.
function getMatchingFilterItems(searchBarQuery, items) {
+ if (items === undefined) {
+ return [];
+ }
+
if (searchBarQuery === undefined) {
return [...items];
}
@@ -116,7 +120,7 @@ export class EditFilterList extends Component {
return {
description: loadedFilter.description,
- items: [...loadedFilter.items],
+ items: (loadedFilter.items !== undefined) ? [...loadedFilter.items] : [],
matchingItems,
selectedItems: [],
loadedFilter,
diff --git a/x-pack/plugins/ml/public/settings/filter_lists/edit/edit_filter_list.test.js b/x-pack/plugins/ml/public/settings/filter_lists/edit/edit_filter_list.test.js
new file mode 100644
index 000000000000..1386f8a4b1da
--- /dev/null
+++ b/x-pack/plugins/ml/public/settings/filter_lists/edit/edit_filter_list.test.js
@@ -0,0 +1,113 @@
+/*
+ * 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.
+ */
+
+// Define the required mocks used for loading, saving and validating the filter list.
+jest.mock('./utils', () => ({
+ isValidFilterListId: () => true,
+ saveFilterList: jest.fn(),
+}));
+
+// Mock the call for loading the list of filters.
+// The mock is hoisted to the top, so need to prefix the filter variable
+// with 'mock' so it can be used lazily.
+const mockTestFilter = {
+ filter_id: 'safe_domains',
+ description: 'List of known safe domains',
+ items: ['google.com', 'google.co.uk', 'elastic.co', 'youtube.com'],
+ used_by: {
+ detectors: ['high info content'],
+ jobs: ['dns_exfiltration']
+ },
+};
+jest.mock('../../../services/ml_api_service', () => ({
+ ml: {
+ filters: {
+ filters: () => {
+ return Promise.resolve(mockTestFilter);
+ }
+ }
+ }
+}));
+
+
+import { shallow } from 'enzyme';
+import React from 'react';
+
+import { EditFilterList } from './edit_filter_list';
+
+function prepareEditTest() {
+
+ const wrapper = shallow(
+
+ );
+
+ // Cannot find a way to generate the snapshot after the Promise in the mock ml.filters
+ // has resolved.
+ // So set the loaded filter state directly to ensure the snapshot is generated against
+ // the test filter and not the default empty state.
+ const instance = wrapper.instance();
+ instance.setLoadedFilterState(mockTestFilter);
+ wrapper.update();
+
+ return wrapper;
+}
+
+describe('EditFilterList', () => {
+
+ test('renders the edit page for a new filter list and updates ID', () => {
+ const wrapper = shallow(
+
+ );
+ expect(wrapper).toMatchSnapshot();
+
+ const instance = wrapper.instance();
+ instance.updateNewFilterId('new_filter_list');
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ test('renders the edit page for an existing filter list and updates description', () => {
+ const wrapper = prepareEditTest();
+ expect(wrapper).toMatchSnapshot();
+
+ const instance = wrapper.instance();
+ instance.updateDescription('Known safe web domains');
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ test('updates the items per page', () => {
+ const wrapper = prepareEditTest();
+ const instance = wrapper.instance();
+
+ instance.setItemsPerPage(500);
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ test('renders after selecting an item and deleting it', () => {
+ const wrapper = prepareEditTest();
+ const instance = wrapper.instance();
+
+ instance.setItemSelected(mockTestFilter.items[1], true);
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+
+ instance.deleteSelectedItems();
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ test('adds new items to filter list', () => {
+ const wrapper = prepareEditTest();
+ const instance = wrapper.instance();
+
+ instance.addItems(['amazon.com', 'spotify.com']);
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+ });
+
+});
diff --git a/x-pack/plugins/ml/public/settings/filter_lists/edit/header.test.js b/x-pack/plugins/ml/public/settings/filter_lists/edit/header.test.js
new file mode 100644
index 000000000000..f3313d6f8d06
--- /dev/null
+++ b/x-pack/plugins/ml/public/settings/filter_lists/edit/header.test.js
@@ -0,0 +1,92 @@
+/*
+ * 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 { shallow } from 'enzyme';
+import React from 'react';
+
+import { EditFilterListHeader } from './header';
+
+describe('EditFilterListHeader', () => {
+
+ const updateNewFilterId = jest.fn(() => {});
+ const updateDescription = jest.fn(() => {});
+
+ const requiredProps = {
+ updateNewFilterId,
+ updateDescription,
+ };
+
+ test('renders the header when creating a new filter list with the ID not set', () => {
+ const props = {
+ ...requiredProps,
+ newFilterId: '',
+ isNewFilterIdInvalid: true,
+ totalItemCount: 0,
+ };
+
+ const component = shallow(
+
+ );
+
+ expect(component).toMatchSnapshot();
+
+ });
+
+ test('renders the header when creating a new filter list with ID, description and items set', () => {
+ const props = {
+ ...requiredProps,
+ newFilterId: 'test_filter_list',
+ isNewFilterIdInvalid: false,
+ description: 'A test filter list',
+ totalItemCount: 15,
+ };
+
+ const component = shallow(
+
+ );
+
+ expect(component).toMatchSnapshot();
+
+ });
+
+ test('renders the header when editing an existing unused filter list with no description or items', () => {
+ const props = {
+ ...requiredProps,
+ filterId: 'test_filter_list',
+ totalItemCount: 0,
+ };
+
+ const component = shallow(
+
+ );
+
+ expect(component).toMatchSnapshot();
+
+ });
+
+ test('renders the header when editing an existing used filter list with description and items set', () => {
+ const props = {
+ ...requiredProps,
+ filterId: 'test_filter_list',
+ description: 'A test filter list',
+ totalItemCount: 15,
+ usedBy: {
+ jobs: ['cloudwatch'],
+ detectors: ['mean CPUUtilization']
+ }
+ };
+
+ const component = shallow(
+
+ );
+
+ expect(component).toMatchSnapshot();
+
+ });
+
+
+});
diff --git a/x-pack/plugins/ml/public/settings/filter_lists/edit/toolbar.js b/x-pack/plugins/ml/public/settings/filter_lists/edit/toolbar.js
index 16cdd2654a7b..80b9a269f574 100644
--- a/x-pack/plugins/ml/public/settings/filter_lists/edit/toolbar.js
+++ b/x-pack/plugins/ml/public/settings/filter_lists/edit/toolbar.js
@@ -7,7 +7,7 @@
/*
* React component for the toolbar section of the edit filter list page,
- * holding a search bar,, and buttons for adding and deleting items from the list.
+ * holding a search bar, and buttons for adding and deleting items from the list.
*/
import PropTypes from 'prop-types';
diff --git a/x-pack/plugins/ml/public/settings/filter_lists/edit/toolbar.test.js b/x-pack/plugins/ml/public/settings/filter_lists/edit/toolbar.test.js
new file mode 100644
index 000000000000..50e4faa45069
--- /dev/null
+++ b/x-pack/plugins/ml/public/settings/filter_lists/edit/toolbar.test.js
@@ -0,0 +1,53 @@
+/*
+ * 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 { shallow } from 'enzyme';
+import React from 'react';
+
+import { EditFilterListToolbar } from './toolbar';
+
+describe('EditFilterListToolbar', () => {
+
+ const onSearchChange = jest.fn(() => {});
+ const addItems = jest.fn(() => {});
+ const deleteSelectedItems = jest.fn(() => {});
+
+ const requiredProps = {
+ onSearchChange,
+ addItems,
+ deleteSelectedItems,
+ };
+
+ test('renders the toolbar with no items selected', () => {
+ const props = {
+ ...requiredProps,
+ selectedItemCount: 0,
+ };
+
+ const component = shallow(
+
+ );
+
+ expect(component).toMatchSnapshot();
+
+ });
+
+ test('renders the toolbar with one item selected', () => {
+ const props = {
+ ...requiredProps,
+ selectedItemCount: 1,
+ };
+
+ const component = shallow(
+
+ );
+
+ expect(component).toMatchSnapshot();
+
+ });
+
+});
diff --git a/x-pack/plugins/ml/public/settings/filter_lists/list/__snapshots__/filter_lists.test.js.snap b/x-pack/plugins/ml/public/settings/filter_lists/list/__snapshots__/filter_lists.test.js.snap
new file mode 100644
index 000000000000..18e5fe8b1a2b
--- /dev/null
+++ b/x-pack/plugins/ml/public/settings/filter_lists/list/__snapshots__/filter_lists.test.js.snap
@@ -0,0 +1,39 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Filter Lists renders a list of filters 1`] = `
+
+
+
+
+
+
+`;
diff --git a/x-pack/plugins/ml/public/settings/filter_lists/list/__snapshots__/header.test.js.snap b/x-pack/plugins/ml/public/settings/filter_lists/list/__snapshots__/header.test.js.snap
new file mode 100644
index 000000000000..34da004d8013
--- /dev/null
+++ b/x-pack/plugins/ml/public/settings/filter_lists/list/__snapshots__/header.test.js.snap
@@ -0,0 +1,114 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Filter Lists Header renders header 1`] = `
+
+
+
+
+
+
+
+ Filter Lists
+
+
+
+
+
+
+ 3
+ in total
+
+
+
+
+
+
+
+
+
+ Refresh
+
+
+
+
+
+
+
+
+
+ Filter lists contain values that you can use to include or exclude events from the machine learning analysis. You can use the same filter list in multiple jobs.
+
+
+ Learn more
+
+
+
+
+
+
+`;
diff --git a/x-pack/plugins/ml/public/settings/filter_lists/list/__snapshots__/table.test.js.snap b/x-pack/plugins/ml/public/settings/filter_lists/list/__snapshots__/table.test.js.snap
new file mode 100644
index 000000000000..cc3079080ecd
--- /dev/null
+++ b/x-pack/plugins/ml/public/settings/filter_lists/list/__snapshots__/table.test.js.snap
@@ -0,0 +1,198 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Filter Lists Table renders with filter lists and selection supplied 1`] = `
+
+
+ ,
+ ,
+ ],
+ }
+ }
+ selection={
+ Object {
+ "onSelectionChange": [Function],
+ "selectable": [Function],
+ "selectableMessage": [Function],
+ }
+ }
+ sorting={
+ Object {
+ "sort": Object {
+ "direction": "asc",
+ "field": "filter_id",
+ },
+ }
+ }
+ />
+
+
+`;
+
+exports[`Filter Lists Table renders with filter lists supplied 1`] = `
+
+
+ ,
+ ,
+ ],
+ }
+ }
+ selection={
+ Object {
+ "onSelectionChange": [Function],
+ "selectable": [Function],
+ "selectableMessage": [Function],
+ }
+ }
+ sorting={
+ Object {
+ "sort": Object {
+ "direction": "asc",
+ "field": "filter_id",
+ },
+ }
+ }
+ />
+
+
+`;
diff --git a/x-pack/plugins/ml/public/settings/filter_lists/list/filter_lists.js b/x-pack/plugins/ml/public/settings/filter_lists/list/filter_lists.js
index 2c2047da36ab..e469ba58eb52 100644
--- a/x-pack/plugins/ml/public/settings/filter_lists/list/filter_lists.js
+++ b/x-pack/plugins/ml/public/settings/filter_lists/list/filter_lists.js
@@ -22,7 +22,7 @@ import { toastNotifications } from 'ui/notify';
import { FilterListsHeader } from './header';
import { FilterListsTable } from './table';
-import { ml } from 'plugins/ml/services/ml_api_service';
+import { ml } from '../../../services/ml_api_service';
export class FilterLists extends Component {
@@ -39,6 +39,21 @@ export class FilterLists extends Component {
this.refreshFilterLists();
}
+ setFilterLists = (filterLists) => {
+ // Check selected filter lists still exist.
+ this.setState((prevState) => {
+ const loadedFilterIds = filterLists.map(filterList => filterList.filter_id);
+ const selectedFilterLists = prevState.selectedFilterLists.filter((filterList) => {
+ return (loadedFilterIds.indexOf(filterList.filter_id) !== -1);
+ });
+
+ return {
+ filterLists,
+ selectedFilterLists
+ };
+ });
+ }
+
setSelectedFilterLists = (selectedFilterLists) => {
this.setState({ selectedFilterLists });
}
@@ -47,18 +62,7 @@ export class FilterLists extends Component {
// Load the list of filters.
ml.filters.filtersStats()
.then((filterLists) => {
- // Check selected filter lists still exist.
- this.setState((prevState) => {
- const loadedFilterIds = filterLists.map(filterList => filterList.filter_id);
- const selectedFilterLists = prevState.selectedFilterLists.filter((filterList) => {
- return (loadedFilterIds.indexOf(filterList.filter_id) !== -1);
- });
-
- return {
- filterLists,
- selectedFilterLists
- };
- });
+ this.setFilterLists(filterLists);
})
.catch((resp) => {
console.log('Error loading list of filters:', resp);
diff --git a/x-pack/plugins/ml/public/settings/filter_lists/list/filter_lists.test.js b/x-pack/plugins/ml/public/settings/filter_lists/list/filter_lists.test.js
new file mode 100644
index 000000000000..7299525eb5b8
--- /dev/null
+++ b/x-pack/plugins/ml/public/settings/filter_lists/list/filter_lists.test.js
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+
+jest.mock('../../../privilege/check_privilege', () => ({
+ checkPermission: () => true
+}));
+
+// Mock the call for loading the list of filters.
+// The mock is hoisted to the top, so need to prefix the filter variable
+// with 'mock' so it can be used lazily.
+const mockTestFilter = {
+ filter_id: 'safe_domains',
+ description: 'List of known safe domains',
+ item_count: 500,
+ used_by: { jobs: ['dns_exfiltration'] },
+};
+jest.mock('../../../services/ml_api_service', () => ({
+ ml: {
+ filters: {
+ filtersStats: () => {
+ return Promise.resolve([mockTestFilter]);
+ }
+ }
+ }
+}));
+
+import { shallow } from 'enzyme';
+import React from 'react';
+
+import { FilterLists } from './filter_lists';
+
+describe('Filter Lists', () => {
+
+ test('renders a list of filters', () => {
+
+ const wrapper = shallow(
+
+ );
+
+ // Cannot find a way to generate the snapshot after the Promise in the mock ml.filters
+ // has resolved.
+ // So set the filter lists directly to ensure the snapshot is generated against
+ // the test list and not the default empty state.
+ wrapper.instance().setFilterLists([mockTestFilter]);
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+ });
+
+});
diff --git a/x-pack/plugins/ml/public/settings/filter_lists/list/header.test.js b/x-pack/plugins/ml/public/settings/filter_lists/list/header.test.js
new file mode 100644
index 000000000000..45e7c6c0eebc
--- /dev/null
+++ b/x-pack/plugins/ml/public/settings/filter_lists/list/header.test.js
@@ -0,0 +1,35 @@
+/*
+ * 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 { shallow } from 'enzyme';
+import React from 'react';
+
+import { FilterListsHeader } from './header';
+
+describe('Filter Lists Header', () => {
+
+ const refreshFilterLists = jest.fn(() => {});
+
+ const requiredProps = {
+ totalCount: 3,
+ refreshFilterLists,
+ };
+
+ test('renders header', () => {
+ const props = {
+ ...requiredProps,
+ };
+
+ const component = shallow(
+
+ );
+
+ expect(component).toMatchSnapshot();
+
+ });
+
+});
diff --git a/x-pack/plugins/ml/public/settings/filter_lists/list/table.js b/x-pack/plugins/ml/public/settings/filter_lists/list/table.js
index d3c170883213..c348ffe4d0e8 100644
--- a/x-pack/plugins/ml/public/settings/filter_lists/list/table.js
+++ b/x-pack/plugins/ml/public/settings/filter_lists/list/table.js
@@ -24,7 +24,7 @@ import {
} from '@elastic/eui';
import chrome from 'ui/chrome';
-import { checkPermission } from 'plugins/ml/privilege/check_privilege';
+import { checkPermission } from '../../../privilege/check_privilege';
import { DeleteFilterListModal } from '../components/delete_filter_list_modal';
diff --git a/x-pack/plugins/ml/public/settings/filter_lists/list/table.test.js b/x-pack/plugins/ml/public/settings/filter_lists/list/table.test.js
new file mode 100644
index 000000000000..e1f31be9d70e
--- /dev/null
+++ b/x-pack/plugins/ml/public/settings/filter_lists/list/table.test.js
@@ -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.
+ */
+
+
+// Create a mock for the privilege check used within the table to
+// enable/disable the 'New Filter' button.
+jest.mock('../../../privilege/check_privilege', () => ({
+ checkPermission: () => true
+}));
+jest.mock('../../../services/ml_api_service', () => 'ml');
+
+import { shallow } from 'enzyme';
+import React from 'react';
+
+import { FilterListsTable } from './table';
+
+describe('Filter Lists Table', () => {
+
+ const setSelectedFilterLists = jest.fn(() => {});
+ const refreshFilterLists = jest.fn(() => {});
+
+ const requiredProps = {
+ setSelectedFilterLists,
+ refreshFilterLists,
+ };
+
+ const testFilterLists = [
+ {
+ filter_id: 'safe_domains',
+ description: 'List of known safe domains',
+ item_count: 500,
+ used_by: { jobs: ['dns_exfiltration'] },
+ },
+ {
+ filter_id: 'us_east_instances',
+ description: 'US East AWS instances',
+ item_count: 20,
+ used_by: { jobs: [] },
+ },
+ ];
+
+ test('renders with filter lists supplied', () => {
+ const props = {
+ ...requiredProps,
+ filterLists: testFilterLists
+ };
+
+ const component = shallow(
+
+ );
+
+ expect(component).toMatchSnapshot();
+
+ });
+
+ test('renders with filter lists and selection supplied', () => {
+ const props = {
+ ...requiredProps,
+ filterLists: testFilterLists,
+ selectedFilterLists: [testFilterLists[0]],
+ };
+
+ const component = shallow(
+
+ );
+
+ expect(component).toMatchSnapshot();
+
+ });
+
+});