From 1e564765102722fc61016f30134d83167c742c81 Mon Sep 17 00:00:00 2001
From: Maryia Lapata
Date: Thu, 28 Mar 2019 15:43:14 +0300
Subject: [PATCH] [Vis: Default editor] EUIficate agg-select (#31892) (#34049)
* EUIficate agg-select
* Improve validation; update TS
* Apply styles for helpLink
* Fix unit test
* Update functional tests
* Adjust comboBox service to chose the item where the text mates exactly
* Update vis page object
* Add default value for agg
* Move aggs grouping function to a separate file
* Use labelAppend prop for help link node
* Add watcher for aggType to manage to discard changes
* Add default value for agg type title
* Fix defining selected option when aggType is defined
* Fix validation issues
* Remove a bootstrap specific class
* Change css selector in test
* Update according to SASS guidelines
* Update functinal comboBox service
* Added check for undefined
* Add jsdoc for groupAggregationsBy function
* Add unit tests for groupAggregationsBy
* Move setValidity invocation to DefaultEditorAggSelect component
* Wrap setValidity into useEffect due to react warning when select is cleaned at the first time
* Move help link definition to select component
---
.../ui/public/agg_types/controls/string.tsx | 2 +-
.../__tests__/default_editor_utils.test.tsx | 194 ++++++++++++++++++
.../vis/editors/default/_agg_select.scss | 4 +
.../public/vis/editors/default/_sidebar.scss | 1 +
.../ui/public/vis/editors/default/agg.js | 2 +-
.../vis/editors/default/agg_params.html | 7 +
.../public/vis/editors/default/agg_params.js | 27 ++-
.../public/vis/editors/default/agg_select.js | 91 ++++++++
.../components/default_editor_agg_select.tsx | 124 +++++++++++
.../editors/default/default_editor_utils.tsx | 79 +++++++
.../functional/page_objects/visualize_page.js | 25 +--
test/functional/services/combo_box.js | 15 +-
.../translations/translations/zh-CN.json | 4 -
13 files changed, 538 insertions(+), 37 deletions(-)
create mode 100644 src/legacy/ui/public/vis/editors/default/__tests__/default_editor_utils.test.tsx
create mode 100644 src/legacy/ui/public/vis/editors/default/agg_select.js
create mode 100644 src/legacy/ui/public/vis/editors/default/components/default_editor_agg_select.tsx
create mode 100644 src/legacy/ui/public/vis/editors/default/default_editor_utils.tsx
diff --git a/src/legacy/ui/public/agg_types/controls/string.tsx b/src/legacy/ui/public/agg_types/controls/string.tsx
index c29a54cebd319..330be44b9da88 100644
--- a/src/legacy/ui/public/agg_types/controls/string.tsx
+++ b/src/legacy/ui/public/agg_types/controls/string.tsx
@@ -26,8 +26,8 @@ function StringParamEditor({ agg, aggParam, value, setValue }: AggParamEditorPro
return (
{
+ it('should return aggs grouped by default type field', () => {
+ const groupedAggs = [
+ {
+ label: 'metrics',
+ options: [
+ {
+ label: 'Average',
+ value: {
+ title: 'Average',
+ type: 'metrics',
+ subtype: 'Metric Aggregations',
+ },
+ },
+ {
+ label: 'Count',
+ value: {
+ title: 'Count',
+ type: 'metrics',
+ subtype: 'Metric Aggregations',
+ },
+ },
+ {
+ label: 'Cumulative Sum',
+ value: {
+ title: 'Cumulative Sum',
+ type: 'metrics',
+ subtype: 'Parent Pipeline Aggregations',
+ },
+ },
+
+ {
+ label: 'Min Bucket',
+ value: {
+ title: 'Min Bucket',
+ type: 'metrics',
+ subtype: 'Parent Pipeline Aggregations',
+ },
+ },
+ ],
+ },
+ {
+ label: 'string',
+ options: [
+ {
+ label: 'String agg',
+ value: {
+ title: 'String agg',
+ type: 'string',
+ subtype: 'String aggregations',
+ },
+ },
+ {
+ label: 'Sub string agg',
+ value: {
+ title: 'Sub string agg',
+ type: 'string',
+ subtype: 'Sub-String aggregations',
+ },
+ },
+ ],
+ },
+ ];
+ expect(groupAggregationsBy(aggs)).toEqual(groupedAggs);
+ });
+ it('should return aggs grouped by subtype field', () => {
+ const groupedAggs = [
+ {
+ label: 'Metric Aggregations',
+ options: [
+ {
+ label: 'Average',
+ value: {
+ title: 'Average',
+ type: 'metrics',
+ subtype: 'Metric Aggregations',
+ },
+ },
+ {
+ label: 'Count',
+ value: {
+ title: 'Count',
+ type: 'metrics',
+ subtype: 'Metric Aggregations',
+ },
+ },
+ ],
+ },
+ {
+ label: 'Parent Pipeline Aggregations',
+ options: [
+ {
+ label: 'Cumulative Sum',
+ value: {
+ title: 'Cumulative Sum',
+ type: 'metrics',
+ subtype: 'Parent Pipeline Aggregations',
+ },
+ },
+
+ {
+ label: 'Min Bucket',
+ value: {
+ title: 'Min Bucket',
+ type: 'metrics',
+ subtype: 'Parent Pipeline Aggregations',
+ },
+ },
+ ],
+ },
+ {
+ label: 'String aggregations',
+ options: [
+ {
+ label: 'String agg',
+ value: {
+ title: 'String agg',
+ type: 'string',
+ subtype: 'String aggregations',
+ },
+ },
+ ],
+ },
+ {
+ label: 'Sub-String aggregations',
+ options: [
+ {
+ label: 'Sub string agg',
+ value: {
+ title: 'Sub string agg',
+ type: 'string',
+ subtype: 'Sub-String aggregations',
+ },
+ },
+ ],
+ },
+ ];
+ expect(groupAggregationsBy(aggs, 'subtype')).toEqual(groupedAggs);
+ });
+});
diff --git a/src/legacy/ui/public/vis/editors/default/_agg_select.scss b/src/legacy/ui/public/vis/editors/default/_agg_select.scss
index 501344bc1e4cf..cd3a84cb9d3af 100644
--- a/src/legacy/ui/public/vis/editors/default/_agg_select.scss
+++ b/src/legacy/ui/public/vis/editors/default/_agg_select.scss
@@ -20,3 +20,7 @@
.visEditorAggSelect__helpLink {
@include euiFontSizeXS;
}
+
+.visEditorAggSelect__formRow {
+ margin-bottom: $euiSizeS;
+}
diff --git a/src/legacy/ui/public/vis/editors/default/_sidebar.scss b/src/legacy/ui/public/vis/editors/default/_sidebar.scss
index a13935b50d886..2fcc0e79f4a42 100644
--- a/src/legacy/ui/public/vis/editors/default/_sidebar.scss
+++ b/src/legacy/ui/public/vis/editors/default/_sidebar.scss
@@ -222,5 +222,6 @@
}
.visEditorSidebar__aggParamFormRow {
+ margin-top: $euiSizeS;
margin-bottom: $euiSizeS;
}
diff --git a/src/legacy/ui/public/vis/editors/default/agg.js b/src/legacy/ui/public/vis/editors/default/agg.js
index ce3de939dca4d..c80bdc45e0086 100644
--- a/src/legacy/ui/public/vis/editors/default/agg.js
+++ b/src/legacy/ui/public/vis/editors/default/agg.js
@@ -53,7 +53,7 @@ uiModules
* @return {[type]} [description]
*/
$scope.describe = function () {
- if (!$scope.agg.type.makeLabel) return '';
+ if (!$scope.agg.type || !$scope.agg.type.makeLabel) return '';
const label = $scope.agg.type.makeLabel($scope.agg);
return label ? label : '';
};
diff --git a/src/legacy/ui/public/vis/editors/default/agg_params.html b/src/legacy/ui/public/vis/editors/default/agg_params.html
index 28a2a3472faef..411357dbfa78e 100644
--- a/src/legacy/ui/public/vis/editors/default/agg_params.html
+++ b/src/legacy/ui/public/vis/editors/default/agg_params.html
@@ -33,4 +33,11 @@
+
+
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 2663e09497498..b3005a2b0929d 100644
--- a/src/legacy/ui/public/vis/editors/default/agg_params.js
+++ b/src/legacy/ui/public/vis/editors/default/agg_params.js
@@ -18,18 +18,18 @@
*/
import $ from 'jquery';
-import { get, has } from 'lodash';
+import { get } from 'lodash';
import { aggTypes } from '../../../agg_types';
import { aggTypeFilters } from '../../../agg_types/filter';
import { aggTypeFieldFilters } from '../../../agg_types/param_types/filter';
-import { documentationLinks } from '../../../documentation_links/documentation_links';
import '../../../filters/match_any';
import { uiModules } from '../../../modules';
import { editorConfigProviders } from '../config/editor_config_providers';
import advancedToggleHtml from './advanced_toggle.html';
import './agg_param';
+import './agg_select';
import aggParamsTemplate from './agg_params.html';
-import aggSelectHtml from './agg_select.html';
+import { groupAggregationsBy } from './default_editor_utils';
uiModules
.get('app/visualize')
@@ -56,6 +56,15 @@ uiModules
updateEditorConfig('default');
});
+ $scope.groupedAggTypeOptions = groupAggregationsBy($scope.aggTypeOptions, 'subtype');
+ $scope.isSubAggregation = $scope.$index >= 1 && $scope.groupName === 'buckets';
+
+ $scope.onAggTypeChange = (agg, value) => {
+ if (agg.type !== value) {
+ agg.type = value;
+ }
+ };
+
$scope.onParamChange = (agg, paramName, value) => {
if(agg.params[paramName] !== value) {
agg.params[paramName] = value;
@@ -91,9 +100,6 @@ uiModules
// controls for the agg, which is why they are first
addSchemaEditor();
- // allow selection of an aggregation
- addAggSelector();
-
function addSchemaEditor() {
const $schemaEditor = $('').addClass('schemaEditors form-group').appendTo($el);
@@ -103,21 +109,12 @@ uiModules
}
}
- function addAggSelector() {
- const $aggSelect = $(aggSelectHtml).appendTo($el);
- $compile($aggSelect)($scope);
- }
-
// params for the selected agg, these are rebuilt every time the agg in $aggSelect changes
let $aggParamEditors; // container for agg type param editors
let $aggParamEditorsScope;
function updateAggParamEditor() {
updateEditorConfig();
- $scope.aggHelpLink = null;
- if (has($scope, 'agg.type.name')) {
- $scope.aggHelpLink = get(documentationLinks, ['aggs', $scope.agg.type.name]);
- }
if ($aggParamEditors) {
$aggParamEditors.remove();
diff --git a/src/legacy/ui/public/vis/editors/default/agg_select.js b/src/legacy/ui/public/vis/editors/default/agg_select.js
new file mode 100644
index 0000000000000..5bcf5aaeef717
--- /dev/null
+++ b/src/legacy/ui/public/vis/editors/default/agg_select.js
@@ -0,0 +1,91 @@
+/*
+ * 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 'ngreact';
+import { uiModules } from '../../../modules';
+import { DefaultEditorAggSelect } from './components/default_editor_agg_select';
+import { wrapInI18nContext } from 'ui/i18n';
+
+uiModules
+ .get('app/visualize', ['react'])
+ .directive('visAggSelectReactWrapper', reactDirective => reactDirective(wrapInI18nContext(DefaultEditorAggSelect), [
+ ['agg', { watchDepth: 'collection' }],
+ ['aggTypeOptions', { watchDepth: 'collection' }],
+ ['setValue', { watchDepth: 'reference' }],
+ ['setTouched', { watchDepth: 'reference' }],
+ ['setValidity', { watchDepth: 'reference' }],
+ 'value',
+ 'isSubAggregation',
+ 'aggHelpLink',
+ 'isSelectInvalid'
+ ]))
+ .directive('visAggSelect', function () {
+ return {
+ restrict: 'E',
+ scope: true,
+ 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);
+ },
+ post: function ($scope, $el, attr, ngModelCtrl) {
+ $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.onChange = (value) => {
+ // 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);
+
+ ngModelCtrl.$setDirty();
+ };
+
+ $scope.setTouched = () => {
+ ngModelCtrl.$setTouched();
+ $scope.isSelectInvalid = !$scope.paramValue;
+ };
+
+ $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.
+ ngModelCtrl.$setValidity(`agg${$scope.agg.id}`, isValid);
+ };
+ }
+ }
+ };
+ });
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
new file mode 100644
index 0000000000000..71d09288a2be6
--- /dev/null
+++ b/src/legacy/ui/public/vis/editors/default/components/default_editor_agg_select.tsx
@@ -0,0 +1,124 @@
+/*
+ * 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, has, isFunction } from 'lodash';
+import React, { useEffect } from 'react';
+
+import { EuiComboBox, EuiFormRow, EuiLink } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { AggType } from 'ui/agg_types';
+import { AggConfig } from 'ui/vis/agg_config';
+import { documentationLinks } from '../../../../documentation_links/documentation_links';
+import { ComboBoxGroupedOption } from '../default_editor_utils';
+
+interface DefaultEditorAggSelectProps {
+ agg: AggConfig;
+ value: AggType;
+ setValue: (aggType: AggType) => void;
+ aggTypeOptions: AggType[];
+ isSubAggregation: boolean;
+ isSelectInvalid: boolean;
+ setTouched: () => void;
+ setValidity: (isValid: boolean) => void;
+}
+
+function DefaultEditorAggSelect({
+ agg = {},
+ value = { title: '' },
+ setValue,
+ aggTypeOptions = [],
+ isSelectInvalid,
+ isSubAggregation,
+ setTouched,
+ setValidity,
+}: DefaultEditorAggSelectProps) {
+ const isAggTypeDefined = value && Boolean(value.title);
+ const selectedOptions: ComboBoxGroupedOption[] = isAggTypeDefined
+ ? [{ label: value.title, value }]
+ : [];
+
+ const label = isSubAggregation ? (
+
+ ) : (
+
+ );
+
+ let aggHelpLink = null;
+ if (has(agg, 'type.name')) {
+ aggHelpLink = get(documentationLinks, ['aggs', agg.type.name]);
+ }
+
+ const helpLink = isAggTypeDefined && aggHelpLink && (
+
+
+
+ );
+
+ useEffect(
+ () => {
+ if (isFunction(setValidity)) {
+ setValidity(isAggTypeDefined);
+ }
+ },
+ [isAggTypeDefined]
+ );
+
+ return (
+
+ setValue(get(options, '0.value'))}
+ data-test-subj="defaultEditorAggSelect"
+ isClearable={false}
+ isInvalid={isSelectInvalid}
+ fullWidth={true}
+ onBlur={() => setTouched()}
+ />
+
+ );
+}
+
+export { DefaultEditorAggSelect };
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
new file mode 100644
index 0000000000000..5d0c50012795d
--- /dev/null
+++ b/src/legacy/ui/public/vis/editors/default/default_editor_utils.tsx
@@ -0,0 +1,79 @@
+/*
+ * 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 { EuiComboBoxOptionProps } from '@elastic/eui';
+import { AggType } from 'ui/agg_types';
+
+export type ComboBoxGroupedOption = EuiComboBoxOptionProps & {
+ value?: AggType;
+ options?: ComboBoxGroupedOption[];
+};
+
+/**
+ * Groups and sorts alphabetically aggregation objects and returns an array of options that are compatible with EuiComboBox options.
+ *
+ * @param aggs An array of aggregations that will be grouped.
+ * @param groupBy A field name which aggregations is grouped by.
+ *
+ * @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'
+): ComboBoxGroupedOption[] | [] {
+ if (!Array.isArray(aggs)) {
+ return [];
+ }
+
+ const groupedOptions: ComboBoxGroupedOption[] = aggs.reduce((array: AggType[], type: AggType) => {
+ const group = array.find(element => element.label === type[groupBy]);
+ const option = {
+ label: type.title,
+ value: type,
+ };
+
+ if (group) {
+ group.options.push(option);
+ } else {
+ array.push({ label: type[groupBy], options: [option] });
+ }
+
+ return array;
+ }, []);
+
+ groupedOptions.sort(sortByLabel);
+
+ groupedOptions.forEach((group: ComboBoxGroupedOption) => {
+ if (Array.isArray(group.options)) {
+ group.options.sort(sortByLabel);
+ }
+ });
+
+ if (groupedOptions.length === 1 && !groupedOptions[0].label) {
+ return groupedOptions[0].options || [];
+ }
+
+ return groupedOptions;
+}
+
+function sortByLabel(a: { label: string }, b: { label: string }) {
+ return a.label.toLowerCase().localeCompare(b.label.toLowerCase());
+}
+
+export { groupAggregationsBy };
diff --git a/test/functional/page_objects/visualize_page.js b/test/functional/page_objects/visualize_page.js
index 10822b92219bb..94f951bd793c1 100644
--- a/test/functional/page_objects/visualize_page.js
+++ b/test/functional/page_objects/visualize_page.js
@@ -34,6 +34,7 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli
const globalNav = getService('globalNav');
const PageObjects = getPageObjects(['common', 'header']);
const defaultFindTimeout = config.get('timeouts.find');
+ const comboBox = getService('comboBox');
class VisualizePage {
@@ -431,19 +432,14 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli
}
async selectAggregation(myString, groupName = 'buckets', childAggregationType = null) {
- const selector = `
+ const comboBoxElement = await find.byCssSelector(`
[group-name="${groupName}"]
vis-editor-agg-params:not(.ng-hide)
${childAggregationType ? `vis-editor-agg-params[group-name="'${childAggregationType}'"]:not(.ng-hide)` : ''}
- .agg-select
- `;
+ [data-test-subj="defaultEditorAggSelect"]
+ `);
- await retry.try(async () => {
- await find.clickByCssSelector(selector);
- const input = await find.byCssSelector(`${selector} input.ui-select-search`);
- await input.type(myString);
- await input.pressKeys(browser.keys.RETURN);
- });
+ await comboBox.setElement(comboBoxElement, myString);
await PageObjects.common.sleep(500);
}
@@ -481,13 +477,12 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli
// So to modify a metric or aggregation tests need to keep track of the
// order they are added.
await this.toggleOpenEditor(index);
- const aggSelect = await find
- .byCssSelector(`#visAggEditorParams${index} div [data-test-subj="visEditorAggSelect"] div span[aria-label="Select box activate"]`);
- // open agg selection list
- await aggSelect.click();
+
// select our agg
- const aggItem = await find.byCssSelector(`[data-test-subj="${agg}"]`);
- await aggItem.click();
+ const aggSelect = await find
+ .byCssSelector(`#visAggEditorParams${index} [data-test-subj="defaultEditorAggSelect"]`);
+ await comboBox.setElement(aggSelect, agg);
+
const fieldSelect = await find
.byCssSelector(`#visAggEditorParams${index} > [agg-param="agg.type.params[0]"] > div > div > div.ui-select-match > span`);
// open field selection list
diff --git a/test/functional/services/combo_box.js b/test/functional/services/combo_box.js
index 33d0efc5b3932..297e0ee6abaaf 100644
--- a/test/functional/services/combo_box.js
+++ b/test/functional/services/combo_box.js
@@ -36,7 +36,20 @@ export function ComboBoxProvider({ getService }) {
log.debug(`comboBox.setElement, value: ${value}`);
await this._filterOptionsList(comboBoxElement, value);
await this.openOptionsList(comboBoxElement);
- await find.clickByCssSelector('.euiComboBoxOption');
+
+ if (value !== undefined) {
+ const options = await find.allByCssSelector(`.euiComboBoxOption[title^="${value.toString().trim()}"]`);
+
+ if (options.length > 0) {
+ await options[0].click();
+ } else {
+ // if it doesn't find the item which text starts with value, it will choose the first option
+ await find.clickByCssSelector('.euiComboBoxOption');
+ }
+ } else {
+ await find.clickByCssSelector('.euiComboBoxOption');
+ }
+
await this.closeOptionsList(comboBoxElement);
}
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 32c41f20aaaac..3fc1b47e8eab4 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -641,10 +641,6 @@
"common.ui.vis.editors.aggGroups.metricsText": "指标",
"common.ui.vis.editors.aggParams.errors.aggWrongRunOrderErrorMessage": "“{schema}” 聚合必须在所有其他存储桶之前运行!",
"common.ui.vis.editors.aggParams.errors.schemaIsDeprecatedErrorMessage": "‘’{schema}”已弃用。",
- "common.ui.vis.editors.aggSelect.aggregationLabel": "聚合",
- "common.ui.vis.editors.aggSelect.helpLinkLabel": "{aggTitle} 帮助",
- "common.ui.vis.editors.aggSelect.selectAggPlaceholder": "选择聚合",
- "common.ui.vis.editors.aggSelect.subAggregationLabel": "子聚合",
"common.ui.vis.editors.howToModifyScreenReaderPriorityDescription": "使用此按钮上的向上和向下键上移和下移此聚合的优先级顺序。",
"common.ui.vis.editors.resizeAriaLabels": "按向左/向右键以调整编辑器的大小",
"common.ui.vis.editors.sidebar.applyChangesAriaLabel": "使用您的更改更新可视化",