Skip to content

Commit bea7667

Browse files
committed
refactor customBucketContainer
1 parent b2ff5b4 commit bea7667

File tree

7 files changed

+175
-127
lines changed

7 files changed

+175
-127
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: 38 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,8 @@ import {
1212
EuiDragDropContext,
1313
EuiDraggable,
1414
EuiDroppable,
15-
EuiFlexGroup,
16-
EuiFlexItem,
17-
EuiPanel,
1815
euiDragDropReorder,
19-
EuiButtonIcon,
2016
EuiButtonEmpty,
21-
EuiIcon,
2217
EuiFormRow,
2318
EuiLink,
2419
htmlIdGenerator,
@@ -29,6 +24,7 @@ import { FieldBasedIndexPatternColumn } from '../column_types';
2924
import { FilterPopover } from './filter_popover';
3025
import { IndexPattern } from '../../../types';
3126
import { Query, esKuery, esQuery } from '../../../../../../../../src/plugins/data/public';
27+
import { CustomBucketContainer } from '../shared_components';
3228

3329
const generateId = htmlIdGenerator();
3430

@@ -238,7 +234,7 @@ export const FilterList = ({
238234
<EuiDroppable droppableId="FILTERS_DROPPABLE_AREA" spacing="s">
239235
{localFilters?.map((filter: FilterValue, idx: number) => {
240236
const { input, label, id } = filter;
241-
const queryIsValid = isQueryValid(input, indexPattern);
237+
const isInvalid = !isQueryValid(input, indexPattern);
242238

243239
return (
244240
<EuiDraggable
@@ -249,74 +245,43 @@ export const FilterList = ({
249245
disableInteractiveElementBlocking
250246
>
251247
{(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',
248+
<CustomBucketContainer
249+
isInvalid={isInvalid}
250+
invalidMessage={i18n.translate('xpack.lens.indexPattern.filters.isInvalid', {
251+
defaultMessage: 'This query is invalid',
252+
})}
253+
onRemoveClick={() => onRemoveFilter(filter.id)}
254+
removeTitle={i18n.translate(
255+
'xpack.lens.indexPattern.filters.removeCustomQuery',
256+
{
257+
defaultMessage: 'Remove custom query',
258+
}
259+
)}
260+
>
261+
<FilterPopover
262+
data-test-subj="indexPattern-filters-existingFilterContainer"
263+
isOpenByCreation={idx === localFilters.length - 1 && isOpenByCreation}
264+
setIsOpenByCreation={setIsOpenByCreation}
265+
indexPattern={indexPattern}
266+
filter={filter}
267+
setFilter={(f: FilterValue) => {
268+
onChangeValue(f.id, f.input, f.label);
269+
}}
270+
Button={({ onClick }: { onClick: MouseEventHandler }) => (
271+
<EuiLink
272+
className="lnsFiltersOperation__popoverButton"
273+
data-test-subj="indexPattern-filters-existingFilterTrigger"
274+
onClick={onClick}
275+
color={isInvalid ? 'danger' : 'text'}
276+
title={i18n.translate('xpack.lens.indexPattern.filters.clickToEdit', {
277+
defaultMessage: 'Click to edit',
315278
})}
316-
/>
317-
</EuiFlexItem>
318-
</EuiFlexGroup>
319-
</EuiPanel>
279+
>
280+
{label || input.query || defaultLabel}
281+
</EuiLink>
282+
)}
283+
/>
284+
</CustomBucketContainer>
320285
)}
321286
</EuiDraggable>
322287
);
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import React from 'react';
8+
import { i18n } from '@kbn/i18n';
9+
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiButtonIcon, EuiIcon } from '@elastic/eui';
10+
11+
export const CustomBucketContainer = ({
12+
isInvalid,
13+
invalidMessage,
14+
onRemoveClick,
15+
removeTitle,
16+
children,
17+
dataTestSubj,
18+
}: {
19+
isInvalid?: boolean;
20+
invalidMessage: string;
21+
onRemoveClick: () => void;
22+
removeTitle: string;
23+
children: React.ReactNode;
24+
dataTestSubj?: string;
25+
}) => {
26+
return (
27+
<EuiPanel paddingSize="none" data-test-subj={dataTestSubj}>
28+
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
29+
<EuiFlexItem grow={false}>{/* Empty for spacing */}</EuiFlexItem>
30+
<EuiFlexItem grow={false}>
31+
<EuiIcon
32+
size="s"
33+
color={isInvalid ? 'danger' : 'subdued'}
34+
type={isInvalid ? 'alert' : 'grab'}
35+
title={
36+
isInvalid
37+
? invalidMessage
38+
: i18n.translate('xpack.lens.customBucketContainer.dragToReorder', {
39+
defaultMessage: 'Drag to reorder',
40+
})
41+
}
42+
/>
43+
</EuiFlexItem>
44+
<EuiFlexItem grow={true}>{children}</EuiFlexItem>
45+
<EuiFlexItem grow={false}>
46+
<EuiButtonIcon
47+
iconSize="s"
48+
iconType="cross"
49+
color="danger"
50+
data-test-subj="lns-customBucketContainer-remove"
51+
onClick={onRemoveClick}
52+
aria-label={removeTitle}
53+
title={removeTitle}
54+
/>
55+
</EuiFlexItem>
56+
</EuiFlexGroup>
57+
</EuiPanel>
58+
);
59+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
export * from './label_input';
8+
export * from './custom_bucket_container';
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import React, { useState, useEffect } from 'react';
8+
import { useDebounce } from 'react-use';
9+
import { EuiFieldText, keys } from '@elastic/eui';
10+
import { i18n } from '@kbn/i18n';
11+
12+
export const LabelInput = ({
13+
value,
14+
onChange,
15+
placeholder,
16+
inputRef,
17+
onSubmit,
18+
dataTestSubj,
19+
}: {
20+
value: string;
21+
onChange: (value: string) => void;
22+
placeholder?: string;
23+
inputRef?: React.MutableRefObject<HTMLInputElement | undefined>;
24+
onSubmit?: () => void;
25+
dataTestSubj?: string;
26+
}) => {
27+
const [inputValue, setInputValue] = useState(value);
28+
29+
useEffect(() => {
30+
setInputValue(value);
31+
}, [value, setInputValue]);
32+
33+
useDebounce(() => onChange(inputValue), 256, [inputValue]);
34+
35+
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
36+
const val = String(e.target.value);
37+
setInputValue(val);
38+
};
39+
40+
return (
41+
<EuiFieldText
42+
data-test-subj={dataTestSubj || 'lens-labelInput'}
43+
value={inputValue}
44+
onChange={handleInputChange}
45+
fullWidth
46+
placeholder={placeholder || ''}
47+
inputRef={(node) => {
48+
if (inputRef && node) {
49+
inputRef.current = node;
50+
}
51+
}}
52+
onKeyDown={({ key }: React.KeyboardEvent<HTMLInputElement>) => {
53+
if (keys.ENTER === key && onSubmit) {
54+
onSubmit();
55+
}
56+
}}
57+
prepend={i18n.translate('xpack.lens.labelInput.label', {
58+
defaultMessage: 'Label',
59+
})}
60+
/>
61+
);
62+
};

0 commit comments

Comments
 (0)