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 @@ -
- - - - - {{$select.selected.displayName}} - - -
-
-
- -
-

- - - - {{ agg.type.params.byName.field.filterFieldTypes | commaList:false }} -

-
- -
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 @@ - + {{ describe() }} - + {{ aggForm.describeErrors() }} diff --git a/src/legacy/ui/public/vis/editors/default/agg_param.js b/src/legacy/ui/public/vis/editors/default/agg_param.js index 7ce49a801bd6..de1d1b797994 100644 --- a/src/legacy/ui/public/vis/editors/default/agg_param.js +++ b/src/legacy/ui/public/vis/editors/default/agg_param.js @@ -27,12 +27,14 @@ uiModules .directive('visAggParamReactWrapper', reactDirective => reactDirective(wrapInI18nContext(AggParamReactWrapper), [ ['agg', { watchDepth: 'collection' }], ['aggParam', { watchDepth: 'reference' }], + ['indexedFields', { watchDepth: 'collection' }], ['paramEditor', { wrapApply: false }], ['onChange', { watchDepth: 'reference' }], + ['setTouched', { watchDepth: 'reference' }], ['setValidity', { watchDepth: 'reference' }], - 'value', + 'field', 'isInvalid', - 'field' + 'value', ])) .directive('visAggParamEditor', function (config) { return { @@ -55,11 +57,13 @@ uiModules param-editor="editorComponent" agg="agg" agg-param="aggParam" - on-change="onChange" - value="paramValue" - is-invalid="isInvalid" - set-validity="setValidity" field="agg.params.field" + indexed-fields="indexedFields" + is-invalid="isInvalid" + value="paramValue" + on-change="onChange" + set-touched="setTouched" + set-validity="setValidity" >`; } @@ -70,8 +74,10 @@ uiModules $scope.$bind('aggParam', attr.aggParam); $scope.$bind('agg', attr.agg); $scope.$bind('editorComponent', attr.editorComponent); + $scope.$bind('indexedFields', attr.indexedFields); }, post: function ($scope, $el, attr, ngModelCtrl) { + let _isInvalid = false; $scope.config = config; $scope.optionEnabled = function (option) { @@ -87,6 +93,18 @@ uiModules // Whenever the value of the parameter changed (e.g. by a reset or actually by calling) // we store the new value in $scope.paramValue, which will be passed as a new value to the react component. $scope.paramValue = value; + + $scope.setValidity(true); + showValidation(); + }, true); + + $scope.$watch(() => { + // The model can become touched either onBlur event or when the form is submitted. + return ngModelCtrl.$touched; + }, (value) => { + if (value) { + showValidation(); + } }, true); $scope.paramValue = $scope.agg.params[$scope.aggParam.name]; } @@ -96,17 +114,22 @@ uiModules // to bind function values, this is right now the best temporary fix, until all of this will be gone. $scope.$parent.onParamChange($scope.agg, $scope.aggParam.name, value); - if(ngModelCtrl) { - ngModelCtrl.$setDirty(); - } + ngModelCtrl.$setDirty(); + }; + + $scope.setTouched = () => { + ngModelCtrl.$setTouched(); + showValidation(); }; $scope.setValidity = (isValid) => { - if(ngModelCtrl) { - $scope.isInvalid = !isValid; - ngModelCtrl.$setValidity(`agg${$scope.agg.id}${$scope.aggParam.name}`, isValid); - } + _isInvalid = !isValid; + ngModelCtrl.$setValidity(`agg${$scope.agg.id}${$scope.aggParam.name}`, isValid); }; + + function showValidation() { + $scope.isInvalid = _isInvalid; + } } } }; diff --git a/src/legacy/ui/public/vis/editors/default/agg_param_editor_props.ts b/src/legacy/ui/public/vis/editors/default/agg_param_editor_props.ts index d0517967a636..970502c75fa0 100644 --- a/src/legacy/ui/public/vis/editors/default/agg_param_editor_props.ts +++ b/src/legacy/ui/public/vis/editors/default/agg_param_editor_props.ts @@ -27,8 +27,10 @@ import { AggConfig } from '../../agg_config'; export interface AggParamEditorProps { agg: AggConfig; aggParam: AggParam; - value: T; + indexedFields?: any[]; isInvalid: boolean; - setValue(value?: T): void; + value: T; setValidity(isValid: boolean): void; + setValue(value?: T): void; + setTouched(): void; } diff --git a/src/legacy/ui/public/vis/editors/default/agg_param_react_wrapper.tsx b/src/legacy/ui/public/vis/editors/default/agg_param_react_wrapper.tsx index 3146c205dcea..436b0e9a7704 100644 --- a/src/legacy/ui/public/vis/editors/default/agg_param_react_wrapper.tsx +++ b/src/legacy/ui/public/vis/editors/default/agg_param_react_wrapper.tsx @@ -20,16 +20,19 @@ import React from 'react'; import { AggParam } from '../../../agg_types'; +import { FieldParamType } from '../../../agg_types/param_types'; import { AggConfig } from '../../agg_config'; import { AggParamEditorProps } from './agg_param_editor_props'; interface AggParamReactWrapperProps { agg: AggConfig; aggParam: AggParam; + indexedFields: FieldParamType[]; + isInvalid: boolean; paramEditor: React.FunctionComponent>; value: T; - isInvalid: boolean; onChange(value: T): void; + setTouched(): void; setValidity(isValid: boolean): void; } @@ -37,20 +40,24 @@ function AggParamReactWrapper(props: AggParamReactWrapperProps) { const { agg, aggParam, - paramEditor: ParamEditor, - onChange, - value, + indexedFields, isInvalid, + paramEditor: ParamEditor, + value, + onChange, setValidity, + setTouched, } = props; return ( ); } diff --git a/src/legacy/ui/public/vis/editors/default/agg_params.js b/src/legacy/ui/public/vis/editors/default/agg_params.js index 9f8878c90534..d09fb3b860ac 100644 --- a/src/legacy/ui/public/vis/editors/default/agg_params.js +++ b/src/legacy/ui/public/vis/editors/default/agg_params.js @@ -150,8 +150,9 @@ uiModules // if field param exists, compute allowed fields if (param.type === 'field') { const availableFields = param.getAvailableFields($scope.agg.getIndexPattern().fields); - fields = $scope.indexedFields = $aggParamEditorsScope[`${param.name}Options`] = + fields = $aggParamEditorsScope[`${param.name}Options`] = aggTypeFieldFilters.filter(availableFields, param.type, $scope.agg, $scope.vis); + $scope.indexedFields = groupAggregationsBy(fields, 'type', 'displayName'); } if (fields) { @@ -202,6 +203,7 @@ uiModules if (param.editorComponent) { attrs['editor-component'] = `agg.type.params[${idx}].editorComponent`; + attrs['indexed-fields'] = 'indexedFields'; // The form should interact with reactified components as well. // So we set the ng-model (using a random ng-model variable) to have the method to set dirty // inside the agg_param.js directive, which can get access to the ngModelController to manipulate it. diff --git a/src/legacy/ui/public/vis/editors/default/agg_select.js b/src/legacy/ui/public/vis/editors/default/agg_select.js index 5bcf5aaeef71..0a1864e141e0 100644 --- a/src/legacy/ui/public/vis/editors/default/agg_select.js +++ b/src/legacy/ui/public/vis/editors/default/agg_select.js @@ -27,13 +27,13 @@ uiModules .directive('visAggSelectReactWrapper', reactDirective => reactDirective(wrapInI18nContext(DefaultEditorAggSelect), [ ['agg', { watchDepth: 'collection' }], ['aggTypeOptions', { watchDepth: 'collection' }], - ['setValue', { watchDepth: 'reference' }], ['setTouched', { watchDepth: 'reference' }], ['setValidity', { watchDepth: 'reference' }], - 'value', - 'isSubAggregation', + ['setValue', { watchDepth: 'reference' }], 'aggHelpLink', - 'isSelectInvalid' + 'isSelectInvalid', + 'isSubAggregation', + 'value', ])) .directive('visAggSelect', function () { return { @@ -42,31 +42,50 @@ uiModules require: '^ngModel', template: function () { return ``; }, link: { pre: function ($scope, $el, attr) { $scope.$bind('agg', attr.agg); - $scope.$bind('isSubAggregation', attr.isSubAggregation); $scope.$bind('aggTypeOptions', attr.aggTypeOptions); + $scope.$bind('isSubAggregation', attr.isSubAggregation); }, post: function ($scope, $el, attr, ngModelCtrl) { + let _isSelectInvalid = false; + $scope.$watch('agg.type', (value) => { // Whenever the value of the parameter changed (e.g. by a reset or actually by calling) // we store the new value in $scope.paramValue, which will be passed as a new value to the react component. $scope.paramValue = value; + + $scope.setValidity(true); + $scope.isSelectInvalid = false; }); + $scope.$watch(() => { + // The model can become touched either onBlur event or when the form is submitted. + return ngModelCtrl.$touched; + }, (value) => { + if (value === true) { + showValidation(); + } + }, true); + $scope.onChange = (value) => { + if (!value) { + // We prevent to make the field empty. + return; + } // This is obviously not a good code quality, but without using scope binding (which we can't see above) // to bind function values, this is right now the best temporary fix, until all of this will be gone. $scope.$parent.onAggTypeChange($scope.agg, value); @@ -76,15 +95,17 @@ uiModules $scope.setTouched = () => { ngModelCtrl.$setTouched(); - $scope.isSelectInvalid = !$scope.paramValue; + showValidation(); }; $scope.setValidity = (isValid) => { - // The field will be marked as invalid when the value is empty and the field is touched. - $scope.isSelectInvalid = ngModelCtrl.$touched ? !isValid : false; - // Since aggType is required field, the form should become invalid when the aggregation field is set to empty. + _isSelectInvalid = !isValid; ngModelCtrl.$setValidity(`agg${$scope.agg.id}`, isValid); }; + + function showValidation() { + $scope.isSelectInvalid = _isSelectInvalid; + } } } }; diff --git a/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_select.tsx b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_select.tsx index 71d09288a2be..1f32f6d52c5b 100644 --- a/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_select.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_select.tsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { get, has, isFunction } from 'lodash'; +import { get, has } from 'lodash'; import React, { useEffect } from 'react'; import { EuiComboBox, EuiFormRow, EuiLink } from '@elastic/eui'; @@ -29,29 +29,26 @@ import { ComboBoxGroupedOption } from '../default_editor_utils'; interface DefaultEditorAggSelectProps { agg: AggConfig; - value: AggType; - setValue: (aggType: AggType) => void; aggTypeOptions: AggType[]; - isSubAggregation: boolean; isSelectInvalid: boolean; - setTouched: () => void; + isSubAggregation: boolean; + value: AggType; setValidity: (isValid: boolean) => void; + setValue: (aggType: AggType) => void; + setTouched: () => void; } function DefaultEditorAggSelect({ - agg = {}, - value = { title: '' }, + agg, + value, setValue, - aggTypeOptions = [], + aggTypeOptions, isSelectInvalid, isSubAggregation, setTouched, setValidity, }: DefaultEditorAggSelectProps) { - const isAggTypeDefined = value && Boolean(value.title); - const selectedOptions: ComboBoxGroupedOption[] = isAggTypeDefined - ? [{ label: value.title, value }] - : []; + const selectedOptions: ComboBoxGroupedOption[] = value ? [{ label: value.title, value }] : []; const label = isSubAggregation ? ( ); + const errors = []; + + if (!aggTypeOptions.length) { + errors.push( + i18n.translate('common.ui.vis.defaultEditor.aggSelect.noCompatibleAggsDescription', { + defaultMessage: 'The index pattern {indexPatternTitle} does not contain any aggregations.', + values: { + indexPatternTitle: agg.getIndexPattern && agg.getIndexPattern().title, + }, + }) + ); + setTouched(); + } + useEffect( () => { - if (isFunction(setValidity)) { - setValidity(isAggTypeDefined); - } + // The selector will be invalid when the value is empty. + setValidity(!!value); }, - [isAggTypeDefined] + [value] ); return ( setValue(get(options, '0.value'))} data-test-subj="defaultEditorAggSelect" isClearable={false} isInvalid={isSelectInvalid} fullWidth={true} - onBlur={() => setTouched()} /> ); diff --git a/src/legacy/ui/public/vis/editors/default/default_editor_utils.tsx b/src/legacy/ui/public/vis/editors/default/default_editor_utils.tsx index 5d0c50012795..967adf36bad4 100644 --- a/src/legacy/ui/public/vis/editors/default/default_editor_utils.tsx +++ b/src/legacy/ui/public/vis/editors/default/default_editor_utils.tsx @@ -20,7 +20,12 @@ import { EuiComboBoxOptionProps } from '@elastic/eui'; import { AggType } from 'ui/agg_types'; +// NOTE: we cannot export the interface with export { InterfaceName } +// as there is currently a bug on babel typescript transform plugin for it +// https://github.com/babel/babel/issues/7641 +// export type ComboBoxGroupedOption = EuiComboBoxOptionProps & { + label: string; value?: AggType; options?: ComboBoxGroupedOption[]; }; @@ -30,12 +35,14 @@ export type ComboBoxGroupedOption = EuiComboBoxOptionProps & { * * @param aggs An array of aggregations that will be grouped. * @param groupBy A field name which aggregations is grouped by. + * @param labelName A name of a property which value will be displayed. * * @returns An array of grouped and sorted alphabetically `aggs` that are compatible with EuiComboBox options. If `aggs` is not an array, the function returns an ampry array. */ function groupAggregationsBy( aggs: AggType[], - groupBy: string = 'type' + groupBy: string = 'type', + labelName = 'title' ): ComboBoxGroupedOption[] | [] { if (!Array.isArray(aggs)) { return []; @@ -44,7 +51,7 @@ function groupAggregationsBy( const groupedOptions: ComboBoxGroupedOption[] = aggs.reduce((array: AggType[], type: AggType) => { const group = array.find(element => element.label === type[groupBy]); const option = { - label: type.title, + label: type[labelName], value: type, }; @@ -72,7 +79,7 @@ function groupAggregationsBy( return groupedOptions; } -function sortByLabel(a: { label: string }, b: { label: string }) { +function sortByLabel(a: ComboBoxGroupedOption, b: ComboBoxGroupedOption) { return a.label.toLowerCase().localeCompare(b.label.toLowerCase()); } diff --git a/src/legacy/ui/public/vis/editors/default/sidebar.html b/src/legacy/ui/public/vis/editors/default/sidebar.html index 0ce0bd8ec434..87b46a0dc4fe 100644 --- a/src/legacy/ui/public/vis/editors/default/sidebar.html +++ b/src/legacy/ui/public/vis/editors/default/sidebar.html @@ -1,7 +1,7 @@