diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/date_histogram/_editor.js b/src/legacy/ui/public/agg_types/__tests__/buckets/date_histogram/_editor.js
index 69d898be5c43..4f699ee462f6 100644
--- a/src/legacy/ui/public/agg_types/__tests__/buckets/date_histogram/_editor.js
+++ b/src/legacy/ui/public/agg_types/__tests__/buckets/date_histogram/_editor.js
@@ -108,7 +108,7 @@ describe('editor', function () {
expect(params).to.have.property('field');
expect(params.field).to.have.property('$el');
- expect(params.field.modelValue()).to.be(field);
+ expect($scope.agg.params.field).to.be(field);
});
it('renders the interval editor', function () {
diff --git a/src/legacy/ui/public/agg_types/agg_param.d.ts b/src/legacy/ui/public/agg_types/agg_param.d.ts
index cd708aec63d4..b3ba88538077 100644
--- a/src/legacy/ui/public/agg_types/agg_param.d.ts
+++ b/src/legacy/ui/public/agg_types/agg_param.d.ts
@@ -17,12 +17,14 @@
* under the License.
*/
-import { AggConfig } from '../vis/agg_config';
+import { AggConfig } from '../vis';
interface AggParam {
type: string;
name: string;
+ required?: boolean;
displayName?: string;
+ onChange?(agg: AggConfig): void;
disabled?(agg: AggConfig): boolean;
}
diff --git a/src/legacy/ui/public/agg_types/controls/field.html b/src/legacy/ui/public/agg_types/controls/field.html
deleted file mode 100644
index b6cc1ce7620b..000000000000
--- a/src/legacy/ui/public/agg_types/controls/field.html
+++ /dev/null
@@ -1,52 +0,0 @@
-
diff --git a/src/legacy/ui/public/agg_types/controls/field.tsx b/src/legacy/ui/public/agg_types/controls/field.tsx
new file mode 100644
index 000000000000..caf3bebecd71
--- /dev/null
+++ b/src/legacy/ui/public/agg_types/controls/field.tsx
@@ -0,0 +1,114 @@
+/*
+ * 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 { get } from 'lodash';
+import React, { useEffect } from 'react';
+
+import { EuiComboBox, EuiComboBoxOptionProps, EuiFormRow } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { AggConfig } from 'ui/vis';
+import { formatListAsProse, parseCommaSeparatedList } from '../../../../utils';
+import { AggParamEditorProps } from '../../vis/editors/default';
+import { ComboBoxGroupedOption } from '../../vis/editors/default/default_editor_utils';
+import { FieldParamType } from '../param_types';
+
+const label = i18n.translate('common.ui.aggTypes.field.fieldLabel', { defaultMessage: 'Field' });
+
+function FieldParamEditor({
+ agg,
+ aggParam,
+ indexedFields = [],
+ isInvalid,
+ value,
+ setTouched,
+ setValidity,
+ setValue,
+}: AggParamEditorProps) {
+ const selectedOptions: ComboBoxGroupedOption[] = value
+ ? [{ label: value.displayName, value }]
+ : [];
+
+ const onChange = (options: EuiComboBoxOptionProps[]) => {
+ const selectedOption = get(options, '0.value');
+ if (!(aggParam.required && !selectedOption)) {
+ setValue(selectedOption);
+ }
+
+ if (aggParam.onChange) {
+ aggParam.onChange(agg);
+ }
+ };
+ const errors = [];
+
+ if (!indexedFields.length) {
+ errors.push(
+ i18n.translate('common.ui.aggTypes.field.noCompatibleFieldsDescription', {
+ defaultMessage:
+ 'The index pattern {indexPatternTitle} does not contain any of the following compatible field types: {fieldTypes}',
+ values: {
+ indexPatternTitle: agg.getIndexPattern && agg.getIndexPattern().title,
+ fieldTypes: getFieldTypesString(agg),
+ },
+ })
+ );
+ setTouched();
+ }
+
+ useEffect(
+ () => {
+ setValidity(!!value);
+ },
+ [value]
+ );
+
+ return (
+
+
+
+ );
+}
+
+function getFieldTypesString(agg: AggConfig) {
+ return formatListAsProse(
+ parseCommaSeparatedList(get(agg, 'type.params.byName.field.filterFieldTypes')),
+ { inclusive: false }
+ );
+}
+
+export { FieldParamEditor };
diff --git a/src/legacy/ui/public/agg_types/controls/raw_json.tsx b/src/legacy/ui/public/agg_types/controls/raw_json.tsx
index 5dcd555ea969..f2084b6d7f73 100644
--- a/src/legacy/ui/public/agg_types/controls/raw_json.tsx
+++ b/src/legacy/ui/public/agg_types/controls/raw_json.tsx
@@ -27,10 +27,11 @@ import { isValidJson } from '../utils';
function RawJsonParamEditor({
agg,
- value,
- setValue,
isInvalid,
+ value,
setValidity,
+ setValue,
+ setTouched,
}: AggParamEditorProps) {
const label = (
<>
@@ -68,6 +69,7 @@ function RawJsonParamEditor({
onChange={onChange}
rows={2}
fullWidth={true}
+ onBlur={setTouched}
/>
);
diff --git a/src/legacy/ui/public/agg_types/controls/string.tsx b/src/legacy/ui/public/agg_types/controls/string.tsx
index db783fe8ed64..90a27b1e8b61 100644
--- a/src/legacy/ui/public/agg_types/controls/string.tsx
+++ b/src/legacy/ui/public/agg_types/controls/string.tsx
@@ -17,29 +17,49 @@
* under the License.
*/
-import React from 'react';
+import React, { useEffect } from 'react';
import { EuiFieldText, EuiFormRow } from '@elastic/eui';
import { AggParamEditorProps } from '../../vis/editors/default';
-function StringParamEditor({ agg, aggParam, value, setValue }: AggParamEditorProps) {
+function StringParamEditor({
+ agg,
+ aggParam,
+ isInvalid,
+ value,
+ setValidity,
+ setValue,
+ setTouched,
+}: AggParamEditorProps) {
if (aggParam.disabled && aggParam.disabled(agg)) {
// reset model value
setValue();
return null;
}
+ useEffect(
+ () => {
+ if (aggParam.required) {
+ setValidity(!!value);
+ }
+ },
+ [value]
+ );
+
return (
setValue(ev.target.value)}
fullWidth={true}
+ onBlur={setTouched}
+ isInvalid={isInvalid}
/>
);
diff --git a/src/legacy/ui/public/agg_types/filter/__tests__/comma_list.js b/src/legacy/ui/public/agg_types/filter/__tests__/comma_list.js
deleted file mode 100644
index f5b155b989ac..000000000000
--- a/src/legacy/ui/public/agg_types/filter/__tests__/comma_list.js
+++ /dev/null
@@ -1,50 +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 expect from '@kbn/expect';
-import ngMock from 'ng_mock';
-import '../comma_list';
-
-describe('Comma-List filter', function () {
-
- let commaList;
-
- beforeEach(ngMock.module('kibana'));
- beforeEach(ngMock.inject(function ($injector) {
- commaList = $injector.get('commaListFilter');
- }));
-
- it('converts a string to a pretty list', function () {
- expect(commaList('john,jaine,jim', true)).to.be('john, jaine, and jim');
- expect(commaList('john,jaine,jim', false)).to.be('john, jaine, or jim');
- });
-
- it('can accept an array too', function () {
- expect(commaList(['john', 'jaine', 'jim'])).to.be('john, jaine, or jim');
- });
-
- it('handles undefined ok', function () {
- expect(commaList()).to.be('');
- });
-
- it('handles single values ok', function () {
- expect(commaList(['john'])).to.be('john');
- });
-
-});
diff --git a/src/legacy/ui/public/agg_types/param_types/field.js b/src/legacy/ui/public/agg_types/param_types/field.js
index 7a02d2ac6ef2..4974559225f3 100644
--- a/src/legacy/ui/public/agg_types/param_types/field.js
+++ b/src/legacy/ui/public/agg_types/param_types/field.js
@@ -19,9 +19,8 @@
import { sortBy } from 'lodash';
import { SavedObjectNotFound } from '../../errors';
+import { FieldParamEditor } from '../controls/field';
import '../directives/scroll_bottom';
-import '../filter/comma_list';
-import editorHtml from '../controls/field.html';
import { BaseParamType } from './base';
import '../filters/sort_prefix_first';
import '../../filters/field_type';
@@ -39,7 +38,8 @@ export function FieldParamType(config) {
createLegacyClass(FieldParamType).inherits(BaseParamType);
-FieldParamType.prototype.editor = editorHtml;
+FieldParamType.prototype.editorComponent = FieldParamEditor;
+FieldParamType.prototype.required = true;
FieldParamType.prototype.scriptable = true;
FieldParamType.prototype.filterFieldTypes = '*';
// retain only the fields with the aggregatable property if the onlyAggregatable option is true
diff --git a/src/legacy/ui/public/agg_types/utils.tsx b/src/legacy/ui/public/agg_types/utils.tsx
index 98aa18fcc169..497709c9ad2f 100644
--- a/src/legacy/ui/public/agg_types/utils.tsx
+++ b/src/legacy/ui/public/agg_types/utils.tsx
@@ -24,6 +24,10 @@ function isValidJson(value: string): boolean {
const trimmedValue = value.trim();
+ if (trimmedValue.length === 0) {
+ return true;
+ }
+
if (trimmedValue[0] === '{' || trimmedValue[0] === '[') {
try {
JSON.parse(trimmedValue);
diff --git a/src/legacy/ui/public/vis/editors/default/agg.html b/src/legacy/ui/public/vis/editors/default/agg.html
index ceb65ded0f87..64319ee4cf60 100644
--- a/src/legacy/ui/public/vis/editors/default/agg.html
+++ b/src/legacy/ui/public/vis/editors/default/agg.html
@@ -23,12 +23,12 @@
-