Skip to content

Commit

Permalink
[Lens] create reusable component for filters and range aggregation (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
mbondyra authored Sep 16, 2020
1 parent 8576293 commit cde6be3
Show file tree
Hide file tree
Showing 8 changed files with 345 additions and 182 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { shallow } from 'enzyme';
import { act } from 'react-dom/test-utils';
import { EuiPopover, EuiLink } from '@elastic/eui';
import { createMockedIndexPattern } from '../../../mocks';
import { FilterPopover, QueryInput, LabelInput } from './filter_popover';
import { FilterPopover, QueryInput } from './filter_popover';
import { LabelInput } from '../shared_components';

jest.mock('.', () => ({
isQueryValid: () => true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import './filter_popover.scss';

import React, { MouseEventHandler, useState } from 'react';
import { useDebounce } from 'react-use';
import { EuiPopover, EuiFieldText, EuiSpacer, keys } from '@elastic/eui';
import { EuiPopover, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FilterValue, defaultLabel, isQueryValid } from '.';
import { IndexPattern } from '../../../types';
import { QueryStringInput, Query } from '../../../../../../../../src/plugins/data/public';
import { LabelInput } from '../shared_components';

export const FilterPopover = ({
filter,
Expand Down Expand Up @@ -51,6 +52,7 @@ export const FilterPopover = ({

return (
<EuiPopover
data-test-subj="indexPattern-filters-existingFilterContainer"
anchorClassName="eui-fullWidth"
panelClassName="lnsIndexPatternDimensionEditor__filtersEditor"
isOpen={isOpenByCreation || isPopoverOpen}
Expand Down Expand Up @@ -83,6 +85,7 @@ export const FilterPopover = ({
placeholder={getPlaceholder(filter.input.query)}
inputRef={inputRef}
onSubmit={() => setPopoverOpen(false)}
dataTestSubj="indexPattern-filters-label"
/>
</EuiPopover>
);
Expand Down Expand Up @@ -141,53 +144,3 @@ export const QueryInput = ({
/>
);
};

export const LabelInput = ({
value,
onChange,
placeholder,
inputRef,
onSubmit,
}: {
value: string;
onChange: (value: string) => void;
placeholder: string;
inputRef: React.MutableRefObject<HTMLInputElement | undefined>;
onSubmit: () => void;
}) => {
const [inputValue, setInputValue] = useState(value);

React.useEffect(() => {
setInputValue(value);
}, [value, setInputValue]);

useDebounce(() => onChange(inputValue), 256, [inputValue]);

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const val = String(e.target.value);
setInputValue(val);
};

return (
<EuiFieldText
data-test-subj="indexPattern-filters-label"
value={inputValue}
onChange={handleInputChange}
fullWidth
placeholder={placeholder}
inputRef={(node) => {
if (node) {
inputRef.current = node;
}
}}
onKeyDown={({ key }: React.KeyboardEvent<HTMLInputElement>) => {
if (keys.ENTER === key) {
onSubmit();
}
}}
prepend={i18n.translate('xpack.lens.indexPattern.filters.label', {
defaultMessage: 'Label',
})}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ describe('filters', () => {
expect(
instance
.find('[data-test-subj="indexPattern-filters-existingFilterContainer"]')
.at(2)
.at(3)
.text()
).toEqual('src : 2');
});
Expand All @@ -250,7 +250,7 @@ describe('filters', () => {
);

instance
.find('[data-test-subj="indexPattern-filters-existingFilterDelete"]')
.find('[data-test-subj="lns-customBucketContainer-remove"]')
.at(2)
.simulate('click');
expect(setStateSpy).toHaveBeenCalledWith({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,14 @@ import './filters.scss';
import React, { MouseEventHandler, useState } from 'react';
import { omit } from 'lodash';
import { i18n } from '@kbn/i18n';
import {
EuiDragDropContext,
EuiDraggable,
EuiDroppable,
EuiFlexGroup,
EuiFlexItem,
EuiPanel,
euiDragDropReorder,
EuiButtonIcon,
EuiButtonEmpty,
EuiIcon,
EuiFormRow,
EuiLink,
htmlIdGenerator,
} from '@elastic/eui';
import { EuiFormRow, EuiLink, htmlIdGenerator } from '@elastic/eui';
import { updateColumnParam } from '../../../state_helpers';
import { OperationDefinition } from '../index';
import { FieldBasedIndexPatternColumn } from '../column_types';
import { FilterPopover } from './filter_popover';
import { IndexPattern } from '../../../types';
import { Query, esKuery, esQuery } from '../../../../../../../../src/plugins/data/public';
import { NewBucketButton, DragDropBuckets, DraggableBucketContainer } from '../shared_components';

const generateId = htmlIdGenerator();

Expand All @@ -37,10 +24,11 @@ export interface Filter {
input: Query;
label: string;
}

export interface FilterValue {
id: string;
input: Query;
label: string;
id: string;
}

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

interface DraggableLocation {
droppableId: string;
index: number;
}

export interface FiltersIndexPatternColumn extends FieldBasedIndexPatternColumn {
operationType: 'filters';
params: {
Expand Down Expand Up @@ -219,123 +202,67 @@ export const FilterList = ({
)
);

const onDragEnd = ({
source,
destination,
}: {
source?: DraggableLocation;
destination?: DraggableLocation;
}) => {
if (source && destination) {
const items = euiDragDropReorder(localFilters, source.index, destination.index);
updateFilters(items);
}
};

return (
<>
<EuiDragDropContext onDragEnd={onDragEnd} onDragStart={() => setIsOpenByCreation(false)}>
<EuiDroppable droppableId="FILTERS_DROPPABLE_AREA" spacing="s">
{localFilters?.map((filter: FilterValue, idx: number) => {
const { input, label, id } = filter;
const queryIsValid = isQueryValid(input, indexPattern);
<DragDropBuckets
onDragEnd={updateFilters}
onDragStart={() => setIsOpenByCreation(false)}
droppableId="FILTERS_DROPPABLE_AREA"
items={localFilters}
>
{localFilters?.map((filter: FilterValue, idx: number) => {
const isInvalid = !isQueryValid(filter.input, indexPattern);

return (
<EuiDraggable
spacing="m"
key={id}
index={idx}
draggableId={id}
disableInteractiveElementBlocking
>
{(provided) => (
<EuiPanel paddingSize="none">
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
<EuiFlexItem grow={false}>{/* Empty for spacing */}</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiIcon
size="s"
color={queryIsValid ? 'subdued' : 'danger'}
type={queryIsValid ? 'grab' : 'alert'}
title={
queryIsValid
? i18n.translate('xpack.lens.indexPattern.filters.dragToReorder', {
defaultMessage: 'Drag to reorder',
})
: i18n.translate('xpack.lens.indexPattern.filters.isInvalid', {
defaultMessage: 'This query is invalid',
})
}
/>
</EuiFlexItem>
<EuiFlexItem
grow={true}
data-test-subj="indexPattern-filters-existingFilterContainer"
>
<FilterPopover
isOpenByCreation={idx === localFilters.length - 1 && isOpenByCreation}
setIsOpenByCreation={setIsOpenByCreation}
indexPattern={indexPattern}
filter={filter}
Button={({ onClick }: { onClick: MouseEventHandler }) => (
<EuiLink
className="lnsFiltersOperation__popoverButton"
data-test-subj="indexPattern-filters-existingFilterTrigger"
onClick={onClick}
color={queryIsValid ? 'text' : 'danger'}
title={i18n.translate('xpack.lens.indexPattern.filters.clickToEdit', {
defaultMessage: 'Click to edit',
})}
>
{label || input.query || defaultLabel}
</EuiLink>
)}
setFilter={(f: FilterValue) => {
onChangeValue(f.id, f.input, f.label);
}}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonIcon
iconSize="s"
iconType="cross"
color="danger"
data-test-subj="indexPattern-filters-existingFilterDelete"
onClick={() => {
onRemoveFilter(filter.id);
}}
aria-label={i18n.translate(
'xpack.lens.indexPattern.filters.removeCustomQuery',
{
defaultMessage: 'Remove custom query',
}
)}
title={i18n.translate('xpack.lens.indexPattern.filters.remove', {
defaultMessage: 'Remove',
})}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
return (
<DraggableBucketContainer
id={filter.id}
key={filter.id}
idx={idx}
isInvalid={isInvalid}
invalidMessage={i18n.translate('xpack.lens.indexPattern.filters.isInvalid', {
defaultMessage: 'This query is invalid',
})}
onRemoveClick={() => onRemoveFilter(filter.id)}
removeTitle={i18n.translate('xpack.lens.indexPattern.filters.removeCustomQuery', {
defaultMessage: 'Remove custom query',
})}
>
<FilterPopover
data-test-subj="indexPattern-filters-existingFilterContainer"
isOpenByCreation={idx === localFilters.length - 1 && isOpenByCreation}
setIsOpenByCreation={setIsOpenByCreation}
indexPattern={indexPattern}
filter={filter}
setFilter={(f: FilterValue) => {
onChangeValue(f.id, f.input, f.label);
}}
Button={({ onClick }: { onClick: MouseEventHandler }) => (
<EuiLink
className="lnsFiltersOperation__popoverButton"
data-test-subj="indexPattern-filters-existingFilterTrigger"
onClick={onClick}
color={isInvalid ? 'danger' : 'text'}
title={i18n.translate('xpack.lens.indexPattern.filters.clickToEdit', {
defaultMessage: 'Click to edit',
})}
>
{filter.label || filter.input.query || defaultLabel}
</EuiLink>
)}
</EuiDraggable>
);
})}
</EuiDroppable>
</EuiDragDropContext>

<EuiButtonEmpty
size="xs"
iconType="plusInCircle"
/>
</DraggableBucketContainer>
);
})}
</DragDropBuckets>
<NewBucketButton
onClick={() => {
onAddFilter();
setIsOpenByCreation(true);
}}
>
{i18n.translate('xpack.lens.indexPattern.filters.addCustomQuery', {
label={i18n.translate('xpack.lens.indexPattern.filters.addCustomQuery', {
defaultMessage: 'Add a custom query',
})}
</EuiButtonEmpty>
/>
</>
);
};
Loading

0 comments on commit cde6be3

Please sign in to comment.