Skip to content

Commit

Permalink
[Vis: Default editor] EUIficate filters control (elastic#35464)
Browse files Browse the repository at this point in the history
EUIfication of filters control for Filters aggregation parameter.
  • Loading branch information
sulemanof committed May 14, 2019
1 parent bba50a8 commit 2f66c86
Show file tree
Hide file tree
Showing 7 changed files with 258 additions and 183 deletions.
6 changes: 2 additions & 4 deletions src/legacy/ui/public/agg_types/buckets/filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@ import angular from 'angular';
import { BucketAggType } from './_bucket_agg_type';
import { createFilterFilters } from './create_filter/filters';
import { decorateQuery, luceneStringToDsl } from '@kbn/es-query';
import '../directives/click_focus';
import '../directives/parse_query';
import filtersTemplate from '../controls/filters.html';
import { FiltersParamEditor } from '../controls/filters';
import { i18n } from '@kbn/i18n';

import chrome from 'ui/chrome';
Expand All @@ -41,7 +39,7 @@ export const filtersBucketAgg = new BucketAggType({
params: [
{
name: 'filters',
editor: filtersTemplate,
editorComponent: FiltersParamEditor,
default: [ { input: {}, label: '' } ],
write: function (aggConfig, output) {
const inFilters = aggConfig.params.filters;
Expand Down
136 changes: 136 additions & 0 deletions src/legacy/ui/public/agg_types/controls/filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import React, { useState } from 'react';
import {
EuiForm,
EuiFlexGroup,
EuiFlexItem,
EuiButtonIcon,
EuiFieldText,
EuiFormRow,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';

interface FilterRowProps {
id: string;
arrayIndex: number;
customLabel: string;
value: string;
autoFocus: boolean;
disableRemove: boolean;
dataTestSubj: string;
onChangeValue(id: string, query: string, label: string): void;
onRemoveFilter(id: string): void;
}

function FilterRow({
id,
arrayIndex,
customLabel,
value,
autoFocus,
disableRemove,
dataTestSubj,
onChangeValue,
onRemoveFilter,
}: FilterRowProps) {
const [showCustomLabel, setShowCustomLabel] = useState(false);
const filterLabel = i18n.translate('common.ui.aggTypes.filters.filterLabel', {
defaultMessage: 'Filter {index}',
values: {
index: arrayIndex + 1,
},
});

const FilterControl = (
<EuiFlexGroup gutterSize="s" responsive={false}>
<EuiFlexItem>
<EuiButtonIcon
iconType="tag"
aria-label={i18n.translate('common.ui.aggTypes.filters.toggleFilterButtonAriaLabel', {
defaultMessage: 'Toggle filter label',
})}
aria-expanded={showCustomLabel}
aria-controls={`visEditorFilterLabel${arrayIndex}`}
onClick={() => setShowCustomLabel(!showCustomLabel)}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiButtonIcon
iconType="trash"
color="danger"
disabled={disableRemove}
aria-label={i18n.translate('common.ui.aggTypes.filters.removeFilterButtonAriaLabel', {
defaultMessage: 'Remove this filter',
})}
onClick={() => onRemoveFilter(id)}
/>
</EuiFlexItem>
</EuiFlexGroup>
);

return (
<EuiForm>
<EuiFormRow
label={`${filterLabel}${customLabel ? ` - ${customLabel}` : ''}`}
labelAppend={FilterControl}
fullWidth={true}
className="visEditorSidebar__aggParamFormRow"
>
<EuiFieldText
value={value}
placeholder={i18n.translate('common.ui.aggTypes.filters.filterPlaceholder', {
defaultMessage: 'Lucene or Query DSL',
})}
data-test-subj={dataTestSubj}
onChange={ev => onChangeValue(id, ev.target.value, customLabel)}
fullWidth={true}
autoFocus={autoFocus}
/>
</EuiFormRow>
{showCustomLabel ? (
<EuiFormRow
id={`visEditorFilterLabel${arrayIndex}`}
label={i18n.translate('common.ui.aggTypes.filters.definiteFilterLabel', {
defaultMessage: 'Filter {index} label',
description:
"'Filter {index}' represents the name of the filter as a noun, similar to 'label for filter 1'.",
values: {
index: arrayIndex + 1,
},
})}
fullWidth={true}
className="visEditorSidebar__aggParamFormRow"
>
<EuiFieldText
value={customLabel}
placeholder={i18n.translate('common.ui.aggTypes.filters.labelPlaceholder', {
defaultMessage: 'Label',
})}
onChange={ev => onChangeValue(id, value, ev.target.value)}
fullWidth={true}
/>
</EuiFormRow>
) : null}
</EuiForm>
);
}

export { FilterRow };
90 changes: 0 additions & 90 deletions src/legacy/ui/public/agg_types/controls/filters.html

This file was deleted.

120 changes: 120 additions & 0 deletions src/legacy/ui/public/agg_types/controls/filters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import React, { useState, useEffect } from 'react';
import { omit, isEqual } from 'lodash';
import { htmlIdGenerator, EuiButton, EuiSpacer } from '@elastic/eui';
import { AggParamEditorProps } from 'ui/vis/editors/default';
import { FormattedMessage } from '@kbn/i18n/react';
import { data } from 'plugins/data';
import { FilterRow } from './filter';

const { toUser, fromUser } = data.query.helpers;
const generateId = htmlIdGenerator();

interface FilterValue {
input: any;
label: string;
id: string;
}

function FiltersParamEditor({ agg, value, setValue }: AggParamEditorProps<FilterValue[]>) {
const [filters, setFilters] = useState(() =>
value.map(filter => ({ ...filter, id: generateId() }))
);

useEffect(() => {
// set parsed values into model after initialization
setValue(
filters.map(filter =>
omit({ ...filter, input: { query: fromUser(filter.input.query) } }, 'id')
)
);
}, []);

useEffect(
() => {
// responsible for discarding changes
if (
value.length !== filters.length ||
value.some((filter, index) => !isEqual(filter, omit(filters[index], 'id')))
) {
setFilters(value.map(filter => ({ ...filter, id: generateId() })));
}
},
[value]
);

const updateFilters = (updatedFilters: FilterValue[]) => {
// do not set internal id parameter into saved object
setValue(updatedFilters.map(filter => omit(filter, 'id')));
setFilters(updatedFilters);
};

const onAddFilter = () =>
updateFilters([...filters, { input: { query: '' }, label: '', id: generateId() }]);
const onRemoveFilter = (id: string) => updateFilters(filters.filter(filter => filter.id !== id));
const onChangeValue = (id: string, query: string, label: string) =>
updateFilters(
filters.map(filter =>
filter.id === id
? {
...filter,
input: { query: fromUser(query) },
label,
}
: filter
)
);

return (
<>
{filters.map(({ input, label, id }, arrayIndex) => (
<FilterRow
key={id}
id={id}
arrayIndex={arrayIndex}
customLabel={label}
value={toUser(input.query)}
autoFocus={arrayIndex === filters.length - 1}
disableRemove={arrayIndex === 0 && filters.length === 1}
dataTestSubj={`visEditorFilterInput_${agg.id}_${arrayIndex}`}
onChangeValue={onChangeValue}
onRemoveFilter={onRemoveFilter}
/>
))}
<EuiButton
iconType="plusInCircle"
fill={true}
fullWidth={true}
onClick={onAddFilter}
size="s"
data-test-subj="visEditorAddFilterButton"
>
<FormattedMessage
id="common.ui.aggTypes.filters.addFilterButtonLabel"
defaultMessage="Add filter"
/>
</EuiButton>
<EuiSpacer size="m" />
</>
);
}

export { FiltersParamEditor };
Loading

0 comments on commit 2f66c86

Please sign in to comment.