diff --git a/src/legacy/ui/public/agg_types/buckets/filters.js b/src/legacy/ui/public/agg_types/buckets/filters.js
index 17166d2d74ab8..b052b3444e8f0 100644
--- a/src/legacy/ui/public/agg_types/buckets/filters.js
+++ b/src/legacy/ui/public/agg_types/buckets/filters.js
@@ -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';
@@ -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;
diff --git a/src/legacy/ui/public/agg_types/controls/filter.tsx b/src/legacy/ui/public/agg_types/controls/filter.tsx
new file mode 100644
index 0000000000000..963d941d3432b
--- /dev/null
+++ b/src/legacy/ui/public/agg_types/controls/filter.tsx
@@ -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 = (
+
+
+ setShowCustomLabel(!showCustomLabel)}
+ />
+
+
+ onRemoveFilter(id)}
+ />
+
+
+ );
+
+ return (
+
+
+ onChangeValue(id, ev.target.value, customLabel)}
+ fullWidth={true}
+ autoFocus={autoFocus}
+ />
+
+ {showCustomLabel ? (
+
+ onChangeValue(id, value, ev.target.value)}
+ fullWidth={true}
+ />
+
+ ) : null}
+
+ );
+}
+
+export { FilterRow };
diff --git a/src/legacy/ui/public/agg_types/controls/filters.html b/src/legacy/ui/public/agg_types/controls/filters.html
deleted file mode 100644
index 13d32c716ecca..0000000000000
--- a/src/legacy/ui/public/agg_types/controls/filters.html
+++ /dev/null
@@ -1,90 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/legacy/ui/public/agg_types/controls/filters.tsx b/src/legacy/ui/public/agg_types/controls/filters.tsx
new file mode 100644
index 0000000000000..1d393b996b4ea
--- /dev/null
+++ b/src/legacy/ui/public/agg_types/controls/filters.tsx
@@ -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) {
+ 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) => (
+
+ ))}
+
+
+
+
+ >
+ );
+}
+
+export { FiltersParamEditor };
diff --git a/src/legacy/ui/public/agg_types/directives/click_focus.js b/src/legacy/ui/public/agg_types/directives/click_focus.js
deleted file mode 100644
index d7cd813469921..0000000000000
--- a/src/legacy/ui/public/agg_types/directives/click_focus.js
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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 _ from 'lodash';
-import $ from 'jquery';
-import { uiModules } from '../../modules';
-const module = uiModules.get('kibana');
-
-module.directive('clickFocus', function () {
- return {
- scope: {
- clickFocus: '='
- },
- restrict: 'A',
- link: function ($scope, $elem) {
- function handler() {
- const focusElem = $.find('input[name=' + $scope.clickFocus + ']');
- if (focusElem[0]) focusElem[0].focus();
- }
-
- $elem.bind('click', handler);
- $scope.$on('$destroy', _.bindKey($elem, 'unbind', 'click', handler));
- }
- };
-});
diff --git a/src/legacy/ui/public/agg_types/directives/parse_query.js b/src/legacy/ui/public/agg_types/directives/parse_query.js
deleted file mode 100644
index 768e0257aa56a..0000000000000
--- a/src/legacy/ui/public/agg_types/directives/parse_query.js
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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 { data } from 'plugins/data';
-const { toUser, fromUser } = data.query.helpers;
-import { uiModules } from '../../modules';
-
-uiModules
- .get('kibana')
- .directive('parseQuery', function () {
-
- return {
- restrict: 'A',
- require: 'ngModel',
- scope: {
- 'ngModel': '='
- },
- link: function ($scope, elem, attr, ngModel) {
- const init = function () {
- $scope.ngModel = fromUser($scope.ngModel);
- };
-
- ngModel.$parsers.push(fromUser);
- ngModel.$formatters.push(toUser);
-
- init();
- }
- };
- });
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 1c3e69c41ba3b..bd7e86c25526d 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -124,13 +124,10 @@
"common.ui.aggTypes.extendedBounds.maxLabel": "最大值",
"common.ui.aggTypes.extendedBounds.minLabel": "最小值",
"common.ui.aggTypes.field.fieldLabel": "字段",
- "common.ui.aggTypes.filters.addFilterButtonLabel": "添加筛选",
"common.ui.aggTypes.filters.definiteFilterLabel": "筛选 {index} 标签",
"common.ui.aggTypes.filters.filterLabel": "筛选 {index}",
"common.ui.aggTypes.filters.labelPlaceholder": "标签",
"common.ui.aggTypes.filters.removeFilterButtonAriaLabel": "移除此筛选",
- "common.ui.aggTypes.filters.requiredFilterDescription": "必须指定至少一个筛选。",
- "common.ui.aggTypes.filters.requiredFilterLabel": "必需:",
"common.ui.aggTypes.filters.toggleFilterButtonAriaLabel": "切换筛选标签",
"common.ui.aggTypes.histogram.missingMaxMinValuesWarning": "无法检索最大值和最小值以自动缩放直方图存储桶。这可能会导致可视化性能低下。",
"common.ui.aggTypes.ipRanges.cidrMask.addRangeButtonLabel": "添加范围",