diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.test.tsx
index 1c2e64735ca16..a204c1ec590a4 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.test.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.test.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React, { MouseEventHandler } from 'react';
+import React from 'react';
import { shallow, mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import { EuiPopover, EuiLink } from '@elastic/eui';
@@ -20,19 +20,6 @@ jest.mock('.', () => ({
defaultLabel: 'label',
}));
-const defaultProps = {
- filter: {
- input: { query: 'bytes >= 1', language: 'kuery' },
- label: 'More than one',
- id: '1',
- },
- setFilter: jest.fn(),
- indexPattern: createMockedIndexPattern(),
- Button: ({ onClick }: { onClick: MouseEventHandler }) => (
- trigger
- ),
- initiallyOpen: true,
-};
jest.mock('../../../../../../../../src/plugins/data/public', () => ({
QueryStringInput: () => {
return 'QueryStringInput';
@@ -40,6 +27,67 @@ jest.mock('../../../../../../../../src/plugins/data/public', () => ({
}));
describe('filter popover', () => {
+ let defaultProps: Parameters[0];
+ let mockOnClick: jest.Mock;
+
+ beforeEach(() => {
+ mockOnClick = jest.fn();
+
+ defaultProps = {
+ filter: {
+ input: { query: 'bytes >= 1', language: 'kuery' },
+ label: 'More than one',
+ id: '1',
+ },
+ setFilter: jest.fn(),
+ indexPattern: createMockedIndexPattern(),
+ Button: () => trigger,
+ isOpen: true,
+ triggerClose: () => {},
+ };
+ });
+
+ describe('interactions', () => {
+ it('should open/close according to isOpen', () => {
+ const instance = mount();
+
+ expect(instance.find(EuiPopover).prop('isOpen')).toEqual(true);
+
+ instance.setProps({ ...defaultProps, isOpen: false });
+ instance.update();
+
+ expect(instance.find(EuiPopover).prop('isOpen')).toEqual(false);
+ });
+
+ it('should report click event', () => {
+ const instance = mount();
+
+ expect(mockOnClick).not.toHaveBeenCalled();
+
+ instance.find(EuiPopover).find('button').simulate('click', {});
+
+ expect(mockOnClick).toHaveBeenCalledTimes(1);
+ });
+
+ it('should trigger close', () => {
+ const props = { ...defaultProps, triggerClose: jest.fn() };
+ const instance = mount();
+ expect(instance.find(EuiPopover).prop('isOpen')).toEqual(true);
+
+ // Trigger from EuiPopover
+ act(() => {
+ instance.find(EuiPopover).prop('closePopover')!();
+ });
+ expect(props.triggerClose).toHaveBeenCalledTimes(1);
+
+ // Trigger from submit
+ act(() => {
+ instance.find(LabelInput).prop('onSubmit')!();
+ });
+ expect(props.triggerClose).toHaveBeenCalledTimes(2);
+ });
+ });
+
it('passes correct props to QueryStringInput', () => {
const instance = mount();
instance.update();
@@ -52,16 +100,7 @@ describe('filter popover', () => {
})
);
});
- it('should be open if is open by creation', () => {
- const instance = mount();
- instance.update();
- expect(instance.find(EuiPopover).prop('isOpen')).toEqual(true);
- act(() => {
- instance.find(EuiPopover).prop('closePopover')!();
- });
- instance.update();
- expect(instance.find(EuiPopover).prop('isOpen')).toEqual(false);
- });
+
it('should call setFilter when modifying QueryInput', () => {
const setFilter = jest.fn();
const instance = shallow();
@@ -78,6 +117,7 @@ describe('filter popover', () => {
id: '1',
});
});
+
it('should call setFilter when modifying LabelInput', () => {
const setFilter = jest.fn();
const instance = shallow();
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx
index bfb0cffece57c..cf95ae2dfd0e8 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx
@@ -7,7 +7,7 @@
import './filter_popover.scss';
-import React, { MouseEventHandler, useEffect, useState } from 'react';
+import React from 'react';
import { EuiPopover, EuiSpacer } from '@elastic/eui';
import { FilterValue, defaultLabel, isQueryValid } from '.';
import { IndexPattern } from '../../../types';
@@ -20,28 +20,40 @@ export const FilterPopover = ({
setFilter,
indexPattern,
Button,
- initiallyOpen,
+ isOpen,
+ triggerClose,
}: {
filter: FilterValue;
setFilter: Function;
indexPattern: IndexPattern;
- Button: React.FunctionComponent<{ onClick: MouseEventHandler }>;
- initiallyOpen: boolean;
+ Button: React.FunctionComponent;
+ isOpen: boolean;
+ triggerClose: () => void;
}) => {
- const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const inputRef = React.useRef();
- // set popover open on start to work around EUI bug
- useEffect(() => {
- setIsPopoverOpen(initiallyOpen);
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
+ // The following code is to prevent an keypress
+ // from propagating.
+ //
+ // TODO - It looks like EUI should be handling this
+ // (see https://github.com/elastic/eui/commit/ad97583b0d644690379f72c7a20879cfadb16e7a)
+ const popoverRef = React.useRef(null);
+ let panelElement: HTMLDivElement;
+ const panelRefCallback = (element: HTMLDivElement) => {
+ const listener = (event: KeyboardEvent) => {
+ if (event.key === 'Escape') {
+ event.stopPropagation();
+ panelElement.removeEventListener('keydown', listener);
+ popoverRef.current?.closePopover();
+ }
+ };
- const closePopover = () => {
- if (isPopoverOpen) {
- setIsPopoverOpen(false);
+ if (element) {
+ panelElement = element;
+ panelElement.addEventListener('keydown', listener);
}
};
+ // End handling code
const setFilterLabel = (label: string) => setFilter({ ...filter, label });
const setFilterQuery = (input: Query) => setFilter({ ...filter, input });
@@ -58,19 +70,15 @@ export const FilterPopover = ({
return (
closePopover()}
- button={
-
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx
index 84319d8f8e433..08cd12556eaed 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx
@@ -6,7 +6,7 @@
*/
import './filters.scss';
-import React, { MouseEventHandler, useState } from 'react';
+import React, { useState } from 'react';
import { fromKueryExpression, luceneStringToDsl, toElasticsearchQuery } from '@kbn/es-query';
import { omit } from 'lodash';
import { i18n } from '@kbn/i18n';
@@ -172,7 +172,7 @@ export const FilterList = ({
indexPattern: IndexPattern;
defaultQuery: Filter;
}) => {
- const [isOpenByCreation, setIsOpenByCreation] = useState(false);
+ const [activeFilterId, setActiveFilterId] = useState('');
const [localFilters, setLocalFilters] = useState(() =>
filters.map((filter) => ({ ...filter, id: generateId() }))
);
@@ -183,14 +183,19 @@ export const FilterList = ({
setLocalFilters(updatedFilters);
};
- const onAddFilter = () =>
+ const onAddFilter = () => {
+ const newFilterId = generateId();
+
updateFilters([
...localFilters,
{
...defaultQuery,
- id: generateId(),
+ id: newFilterId,
},
]);
+
+ setActiveFilterId(newFilterId);
+ };
const onRemoveFilter = (id: string) =>
updateFilters(localFilters.filter((filter) => filter.id !== id));
@@ -207,6 +212,14 @@ export const FilterList = ({
)
);
+ const changeActiveFilter = (filterId: string) => {
+ let newActiveFilterId = filterId;
+ if (activeFilterId === filterId) {
+ newActiveFilterId = ''; // toggle off
+ }
+ setActiveFilterId(newActiveFilterId);
+ };
+
return (
<>
changeActiveFilter('')}
indexPattern={indexPattern}
filter={filter}
setFilter={(f: FilterValue) => {
onChangeValue(f.id, f.input, f.label);
}}
- Button={({ onClick }: { onClick: MouseEventHandler }) => (
+ Button={() => (
changeActiveFilter(filter.id)}
color={isInvalid ? 'danger' : 'text'}
title={i18n.translate('xpack.lens.indexPattern.filters.clickToEdit', {
defaultMessage: 'Click to edit',
@@ -262,7 +276,6 @@ export const FilterList = ({
{
onAddFilter();
- setIsOpenByCreation(true);
}}
label={i18n.translate('xpack.lens.indexPattern.filters.addaFilter', {
defaultMessage: 'Add a filter',