Skip to content

Commit cde6be3

Browse files
authored
[Lens] create reusable component for filters and range aggregation (#77453) (#77592)
1 parent 8576293 commit cde6be3

File tree

8 files changed

+345
-182
lines changed

8 files changed

+345
-182
lines changed

x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.test.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import { shallow } from 'enzyme';
99
import { act } from 'react-dom/test-utils';
1010
import { EuiPopover, EuiLink } from '@elastic/eui';
1111
import { createMockedIndexPattern } from '../../../mocks';
12-
import { FilterPopover, QueryInput, LabelInput } from './filter_popover';
12+
import { FilterPopover, QueryInput } from './filter_popover';
13+
import { LabelInput } from '../shared_components';
1314

1415
jest.mock('.', () => ({
1516
isQueryValid: () => true,

x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx

Lines changed: 4 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ import './filter_popover.scss';
77

88
import React, { MouseEventHandler, useState } from 'react';
99
import { useDebounce } from 'react-use';
10-
import { EuiPopover, EuiFieldText, EuiSpacer, keys } from '@elastic/eui';
10+
import { EuiPopover, EuiSpacer } from '@elastic/eui';
1111
import { i18n } from '@kbn/i18n';
1212
import { FilterValue, defaultLabel, isQueryValid } from '.';
1313
import { IndexPattern } from '../../../types';
1414
import { QueryStringInput, Query } from '../../../../../../../../src/plugins/data/public';
15+
import { LabelInput } from '../shared_components';
1516

1617
export const FilterPopover = ({
1718
filter,
@@ -51,6 +52,7 @@ export const FilterPopover = ({
5152

5253
return (
5354
<EuiPopover
55+
data-test-subj="indexPattern-filters-existingFilterContainer"
5456
anchorClassName="eui-fullWidth"
5557
panelClassName="lnsIndexPatternDimensionEditor__filtersEditor"
5658
isOpen={isOpenByCreation || isPopoverOpen}
@@ -83,6 +85,7 @@ export const FilterPopover = ({
8385
placeholder={getPlaceholder(filter.input.query)}
8486
inputRef={inputRef}
8587
onSubmit={() => setPopoverOpen(false)}
88+
dataTestSubj="indexPattern-filters-label"
8689
/>
8790
</EuiPopover>
8891
);
@@ -141,53 +144,3 @@ export const QueryInput = ({
141144
/>
142145
);
143146
};
144-
145-
export const LabelInput = ({
146-
value,
147-
onChange,
148-
placeholder,
149-
inputRef,
150-
onSubmit,
151-
}: {
152-
value: string;
153-
onChange: (value: string) => void;
154-
placeholder: string;
155-
inputRef: React.MutableRefObject<HTMLInputElement | undefined>;
156-
onSubmit: () => void;
157-
}) => {
158-
const [inputValue, setInputValue] = useState(value);
159-
160-
React.useEffect(() => {
161-
setInputValue(value);
162-
}, [value, setInputValue]);
163-
164-
useDebounce(() => onChange(inputValue), 256, [inputValue]);
165-
166-
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
167-
const val = String(e.target.value);
168-
setInputValue(val);
169-
};
170-
171-
return (
172-
<EuiFieldText
173-
data-test-subj="indexPattern-filters-label"
174-
value={inputValue}
175-
onChange={handleInputChange}
176-
fullWidth
177-
placeholder={placeholder}
178-
inputRef={(node) => {
179-
if (node) {
180-
inputRef.current = node;
181-
}
182-
}}
183-
onKeyDown={({ key }: React.KeyboardEvent<HTMLInputElement>) => {
184-
if (keys.ENTER === key) {
185-
onSubmit();
186-
}
187-
}}
188-
prepend={i18n.translate('xpack.lens.indexPattern.filters.label', {
189-
defaultMessage: 'Label',
190-
})}
191-
/>
192-
);
193-
};

x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ describe('filters', () => {
231231
expect(
232232
instance
233233
.find('[data-test-subj="indexPattern-filters-existingFilterContainer"]')
234-
.at(2)
234+
.at(3)
235235
.text()
236236
).toEqual('src : 2');
237237
});
@@ -250,7 +250,7 @@ describe('filters', () => {
250250
);
251251

252252
instance
253-
.find('[data-test-subj="indexPattern-filters-existingFilterDelete"]')
253+
.find('[data-test-subj="lns-customBucketContainer-remove"]')
254254
.at(2)
255255
.simulate('click');
256256
expect(setStateSpy).toHaveBeenCalledWith({

x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filters.tsx

Lines changed: 55 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,14 @@ import './filters.scss';
88
import React, { MouseEventHandler, useState } from 'react';
99
import { omit } from 'lodash';
1010
import { i18n } from '@kbn/i18n';
11-
import {
12-
EuiDragDropContext,
13-
EuiDraggable,
14-
EuiDroppable,
15-
EuiFlexGroup,
16-
EuiFlexItem,
17-
EuiPanel,
18-
euiDragDropReorder,
19-
EuiButtonIcon,
20-
EuiButtonEmpty,
21-
EuiIcon,
22-
EuiFormRow,
23-
EuiLink,
24-
htmlIdGenerator,
25-
} from '@elastic/eui';
11+
import { EuiFormRow, EuiLink, htmlIdGenerator } from '@elastic/eui';
2612
import { updateColumnParam } from '../../../state_helpers';
2713
import { OperationDefinition } from '../index';
2814
import { FieldBasedIndexPatternColumn } from '../column_types';
2915
import { FilterPopover } from './filter_popover';
3016
import { IndexPattern } from '../../../types';
3117
import { Query, esKuery, esQuery } from '../../../../../../../../src/plugins/data/public';
18+
import { NewBucketButton, DragDropBuckets, DraggableBucketContainer } from '../shared_components';
3219

3320
const generateId = htmlIdGenerator();
3421

@@ -37,10 +24,11 @@ export interface Filter {
3724
input: Query;
3825
label: string;
3926
}
27+
4028
export interface FilterValue {
29+
id: string;
4130
input: Query;
4231
label: string;
43-
id: string;
4432
}
4533

4634
const customQueryLabel = i18n.translate('xpack.lens.indexPattern.customQuery', {
@@ -73,11 +61,6 @@ export const isQueryValid = (input: Query, indexPattern: IndexPattern) => {
7361
}
7462
};
7563

76-
interface DraggableLocation {
77-
droppableId: string;
78-
index: number;
79-
}
80-
8164
export interface FiltersIndexPatternColumn extends FieldBasedIndexPatternColumn {
8265
operationType: 'filters';
8366
params: {
@@ -219,123 +202,67 @@ export const FilterList = ({
219202
)
220203
);
221204

222-
const onDragEnd = ({
223-
source,
224-
destination,
225-
}: {
226-
source?: DraggableLocation;
227-
destination?: DraggableLocation;
228-
}) => {
229-
if (source && destination) {
230-
const items = euiDragDropReorder(localFilters, source.index, destination.index);
231-
updateFilters(items);
232-
}
233-
};
234-
235205
return (
236206
<>
237-
<EuiDragDropContext onDragEnd={onDragEnd} onDragStart={() => setIsOpenByCreation(false)}>
238-
<EuiDroppable droppableId="FILTERS_DROPPABLE_AREA" spacing="s">
239-
{localFilters?.map((filter: FilterValue, idx: number) => {
240-
const { input, label, id } = filter;
241-
const queryIsValid = isQueryValid(input, indexPattern);
207+
<DragDropBuckets
208+
onDragEnd={updateFilters}
209+
onDragStart={() => setIsOpenByCreation(false)}
210+
droppableId="FILTERS_DROPPABLE_AREA"
211+
items={localFilters}
212+
>
213+
{localFilters?.map((filter: FilterValue, idx: number) => {
214+
const isInvalid = !isQueryValid(filter.input, indexPattern);
242215

243-
return (
244-
<EuiDraggable
245-
spacing="m"
246-
key={id}
247-
index={idx}
248-
draggableId={id}
249-
disableInteractiveElementBlocking
250-
>
251-
{(provided) => (
252-
<EuiPanel paddingSize="none">
253-
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
254-
<EuiFlexItem grow={false}>{/* Empty for spacing */}</EuiFlexItem>
255-
<EuiFlexItem grow={false}>
256-
<EuiIcon
257-
size="s"
258-
color={queryIsValid ? 'subdued' : 'danger'}
259-
type={queryIsValid ? 'grab' : 'alert'}
260-
title={
261-
queryIsValid
262-
? i18n.translate('xpack.lens.indexPattern.filters.dragToReorder', {
263-
defaultMessage: 'Drag to reorder',
264-
})
265-
: i18n.translate('xpack.lens.indexPattern.filters.isInvalid', {
266-
defaultMessage: 'This query is invalid',
267-
})
268-
}
269-
/>
270-
</EuiFlexItem>
271-
<EuiFlexItem
272-
grow={true}
273-
data-test-subj="indexPattern-filters-existingFilterContainer"
274-
>
275-
<FilterPopover
276-
isOpenByCreation={idx === localFilters.length - 1 && isOpenByCreation}
277-
setIsOpenByCreation={setIsOpenByCreation}
278-
indexPattern={indexPattern}
279-
filter={filter}
280-
Button={({ onClick }: { onClick: MouseEventHandler }) => (
281-
<EuiLink
282-
className="lnsFiltersOperation__popoverButton"
283-
data-test-subj="indexPattern-filters-existingFilterTrigger"
284-
onClick={onClick}
285-
color={queryIsValid ? 'text' : 'danger'}
286-
title={i18n.translate('xpack.lens.indexPattern.filters.clickToEdit', {
287-
defaultMessage: 'Click to edit',
288-
})}
289-
>
290-
{label || input.query || defaultLabel}
291-
</EuiLink>
292-
)}
293-
setFilter={(f: FilterValue) => {
294-
onChangeValue(f.id, f.input, f.label);
295-
}}
296-
/>
297-
</EuiFlexItem>
298-
<EuiFlexItem grow={false}>
299-
<EuiButtonIcon
300-
iconSize="s"
301-
iconType="cross"
302-
color="danger"
303-
data-test-subj="indexPattern-filters-existingFilterDelete"
304-
onClick={() => {
305-
onRemoveFilter(filter.id);
306-
}}
307-
aria-label={i18n.translate(
308-
'xpack.lens.indexPattern.filters.removeCustomQuery',
309-
{
310-
defaultMessage: 'Remove custom query',
311-
}
312-
)}
313-
title={i18n.translate('xpack.lens.indexPattern.filters.remove', {
314-
defaultMessage: 'Remove',
315-
})}
316-
/>
317-
</EuiFlexItem>
318-
</EuiFlexGroup>
319-
</EuiPanel>
216+
return (
217+
<DraggableBucketContainer
218+
id={filter.id}
219+
key={filter.id}
220+
idx={idx}
221+
isInvalid={isInvalid}
222+
invalidMessage={i18n.translate('xpack.lens.indexPattern.filters.isInvalid', {
223+
defaultMessage: 'This query is invalid',
224+
})}
225+
onRemoveClick={() => onRemoveFilter(filter.id)}
226+
removeTitle={i18n.translate('xpack.lens.indexPattern.filters.removeCustomQuery', {
227+
defaultMessage: 'Remove custom query',
228+
})}
229+
>
230+
<FilterPopover
231+
data-test-subj="indexPattern-filters-existingFilterContainer"
232+
isOpenByCreation={idx === localFilters.length - 1 && isOpenByCreation}
233+
setIsOpenByCreation={setIsOpenByCreation}
234+
indexPattern={indexPattern}
235+
filter={filter}
236+
setFilter={(f: FilterValue) => {
237+
onChangeValue(f.id, f.input, f.label);
238+
}}
239+
Button={({ onClick }: { onClick: MouseEventHandler }) => (
240+
<EuiLink
241+
className="lnsFiltersOperation__popoverButton"
242+
data-test-subj="indexPattern-filters-existingFilterTrigger"
243+
onClick={onClick}
244+
color={isInvalid ? 'danger' : 'text'}
245+
title={i18n.translate('xpack.lens.indexPattern.filters.clickToEdit', {
246+
defaultMessage: 'Click to edit',
247+
})}
248+
>
249+
{filter.label || filter.input.query || defaultLabel}
250+
</EuiLink>
320251
)}
321-
</EuiDraggable>
322-
);
323-
})}
324-
</EuiDroppable>
325-
</EuiDragDropContext>
326-
327-
<EuiButtonEmpty
328-
size="xs"
329-
iconType="plusInCircle"
252+
/>
253+
</DraggableBucketContainer>
254+
);
255+
})}
256+
</DragDropBuckets>
257+
<NewBucketButton
330258
onClick={() => {
331259
onAddFilter();
332260
setIsOpenByCreation(true);
333261
}}
334-
>
335-
{i18n.translate('xpack.lens.indexPattern.filters.addCustomQuery', {
262+
label={i18n.translate('xpack.lens.indexPattern.filters.addCustomQuery', {
336263
defaultMessage: 'Add a custom query',
337264
})}
338-
</EuiButtonEmpty>
265+
/>
339266
</>
340267
);
341268
};

0 commit comments

Comments
 (0)