From 59d3e0d21e6a8c86c21eade946b89cd49bca8652 Mon Sep 17 00:00:00 2001 From: Daniil Suleiman <31325372+sulemanof@users.noreply.github.com> Date: Wed, 29 May 2019 14:25:16 +0300 Subject: [PATCH] [Vis: Default editor] EUIficate top_aggregate and size param editors (#36567) * EUIficate top_aggregate_and_size param editor * Remove template * Change typescript interfaces * Fix browser tests * Add an icon alert * Changes due to comments * Move error to a help text * Move error to a field * Change validation logic * Fix discarding changes action * Remove changed translation --- .../agg_types/__tests__/metrics/top_hit.js | 2 +- .../agg_types/buckets/_inline_comp_wrapper.js | 2 +- .../ui/public/agg_types/controls/field.tsx | 8 +- .../ui/public/agg_types/controls/order.tsx | 3 +- .../ui/public/agg_types/controls/size.tsx | 26 +++- .../agg_types/controls/top_aggregate.tsx | 138 ++++++++++++++++++ .../controls/top_aggregate_and_size.html | 43 ------ .../public/agg_types/controls/top_field.tsx | 40 +++++ .../ui/public/agg_types/controls/top_size.tsx | 47 ++++++ .../ui/public/agg_types/metrics/top_hit.js | 47 +++--- .../public/agg_types/param_types/select.d.ts | 8 +- .../public/vis/editors/default/agg_param.js | 2 + .../editors/default/agg_param_editor_props.ts | 10 +- .../default/agg_param_react_wrapper.tsx | 17 +-- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 16 files changed, 290 insertions(+), 105 deletions(-) create mode 100644 src/legacy/ui/public/agg_types/controls/top_aggregate.tsx delete mode 100644 src/legacy/ui/public/agg_types/controls/top_aggregate_and_size.html create mode 100644 src/legacy/ui/public/agg_types/controls/top_field.tsx create mode 100644 src/legacy/ui/public/agg_types/controls/top_size.tsx diff --git a/src/legacy/ui/public/agg_types/__tests__/metrics/top_hit.js b/src/legacy/ui/public/agg_types/__tests__/metrics/top_hit.js index 3e1446ed1ff97..4ed86da68c408 100644 --- a/src/legacy/ui/public/agg_types/__tests__/metrics/top_hit.js +++ b/src/legacy/ui/public/agg_types/__tests__/metrics/top_hit.js @@ -42,7 +42,7 @@ describe('Top hit metric', function () { value: sortOrder }; params.aggregate = { - val: aggregate + value: aggregate }; params.size = size; const vis = new Vis(indexPattern, { diff --git a/src/legacy/ui/public/agg_types/buckets/_inline_comp_wrapper.js b/src/legacy/ui/public/agg_types/buckets/_inline_comp_wrapper.js index 8091d8e28dde5..0c8161035adcf 100644 --- a/src/legacy/ui/public/agg_types/buckets/_inline_comp_wrapper.js +++ b/src/legacy/ui/public/agg_types/buckets/_inline_comp_wrapper.js @@ -21,7 +21,7 @@ import React from 'react'; const wrapWithInlineComp = Component => props => (
- +
); export { wrapWithInlineComp }; diff --git a/src/legacy/ui/public/agg_types/controls/field.tsx b/src/legacy/ui/public/agg_types/controls/field.tsx index 254ae4fa92715..0fb44cc9f920b 100644 --- a/src/legacy/ui/public/agg_types/controls/field.tsx +++ b/src/legacy/ui/public/agg_types/controls/field.tsx @@ -31,12 +31,14 @@ import { FieldParamType } from '../param_types'; const label = i18n.translate('common.ui.aggTypes.field.fieldLabel', { defaultMessage: 'Field' }); interface FieldParamEditorProps extends AggParamEditorProps { + customError?: string; customLabel?: string; } function FieldParamEditor({ agg, aggParam, + customError, customLabel, indexedFields = [], showValidation, @@ -61,6 +63,10 @@ function FieldParamEditor({ }; const errors = []; + if (customError) { + errors.push(customError); + } + if (!indexedFields.length) { errors.push( i18n.translate('common.ui.aggTypes.field.noCompatibleFieldsDescription', { @@ -75,7 +81,7 @@ function FieldParamEditor({ setTouched(); } - const isValid = !!value && !!indexedFields.length; + const isValid = !!value && !errors.length; useEffect( () => { diff --git a/src/legacy/ui/public/agg_types/controls/order.tsx b/src/legacy/ui/public/agg_types/controls/order.tsx index 0042c3d97d0ba..a122ad4c0543d 100644 --- a/src/legacy/ui/public/agg_types/controls/order.tsx +++ b/src/legacy/ui/public/agg_types/controls/order.tsx @@ -30,6 +30,7 @@ function OrderParamEditor({ setValue, setValidity, setTouched, + wrappedWithInlineComp, }: AggParamEditorProps & SelectParamEditorProps) { const label = i18n.translate('common.ui.aggTypes.orderLabel', { defaultMessage: 'Order', @@ -48,7 +49,7 @@ function OrderParamEditor({ label={label} fullWidth={true} isInvalid={showValidation ? !isValid : false} - className="visEditorSidebar__aggParamFormRow" + className={wrappedWithInlineComp ? undefined : 'visEditorSidebar__aggParamFormRow'} > { + iconTip?: React.ReactNode; + disabled?: boolean; +} function SizeParamEditor({ + disabled, + iconTip, value, setValue, showValidation, setValidity, setTouched, -}: AggParamEditorProps) { - const label = i18n.translate('common.ui.aggTypes.sizeLabel', { - defaultMessage: 'Size', - }); - const isValid = Number(value) > 0; + wrappedWithInlineComp, +}: SizeParamEditorProps) { + const label = ( + <> + + {iconTip} + + ); + const isValid = disabled || Number(value) > 0; useEffect( () => { @@ -47,7 +58,7 @@ function SizeParamEditor({ label={label} fullWidth={true} isInvalid={showValidation ? !isValid : false} - className="visEditorSidebar__aggParamFormRow" + className={wrappedWithInlineComp ? undefined : 'visEditorSidebar__aggParamFormRow'} > diff --git a/src/legacy/ui/public/agg_types/controls/top_aggregate.tsx b/src/legacy/ui/public/agg_types/controls/top_aggregate.tsx new file mode 100644 index 0000000000000..6416ab96ffac1 --- /dev/null +++ b/src/legacy/ui/public/agg_types/controls/top_aggregate.tsx @@ -0,0 +1,138 @@ +/* + * 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, { useEffect, useRef } from 'react'; +import { EuiFormRow, EuiIconTip, EuiSelect } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { AggParamEditorProps } from 'ui/vis/editors/default'; +import { AggConfig } from 'ui/vis'; +import { AggParam } from '../agg_param'; +import { SelectValueProp, SelectParamEditorProps } from '../param_types/select'; + +interface AggregateValueProp extends SelectValueProp { + isCompatibleType(filedType: string): boolean; + isCompatibleVis(visName: string): boolean; +} + +function getCompatibleAggs(agg: AggConfig, visName: string): AggregateValueProp[] { + const fieldType = agg.params.field && agg.params.field.type; + const { options = [] } = agg.getAggParams().find(({ name }: AggParam) => name === 'aggregate'); + return options.filter( + (option: AggregateValueProp) => + fieldType && option.isCompatibleType(fieldType) && option.isCompatibleVis(visName) + ); +} + +function TopAggregateParamEditor({ + agg, + aggParam, + value, + visName, + showValidation, + setValue, + setValidity, + setTouched, + wrappedWithInlineComp, +}: AggParamEditorProps & SelectParamEditorProps) { + const isFirstRun = useRef(true); + const fieldType = agg.params.field && agg.params.field.type; + const emptyValue = { text: '', value: 'EMPTY_VALUE', disabled: true, hidden: true }; + const filteredOptions = getCompatibleAggs(agg, visName) + .map(({ text, value: val }) => ({ text, value: val })) + .sort((a, b) => a.text.toLowerCase().localeCompare(b.text.toLowerCase())); + const options = [emptyValue, ...filteredOptions]; + const disabled = fieldType && !filteredOptions.length; + const isValid = disabled || !!value; + + const label = ( + <> + {' '} + + + ); + + useEffect( + () => { + setValidity(isValid); + }, + [isValid] + ); + + useEffect( + () => { + if (isFirstRun.current) { + isFirstRun.current = false; + return; + } + + if (value) { + if (aggParam.options.byValue[value.value]) { + return; + } + + setValue(); + } + + if (filteredOptions.length === 1) { + setValue(aggParam.options.byValue[filteredOptions[0].value]); + } + }, + [fieldType, visName] + ); + + const handleChange = (event: React.ChangeEvent) => { + if (event.target.value === emptyValue.value) { + setValue(); + } else { + setValue(aggParam.options.byValue[event.target.value]); + } + }; + + return ( + + + + ); +} + +export { TopAggregateParamEditor, getCompatibleAggs }; diff --git a/src/legacy/ui/public/agg_types/controls/top_aggregate_and_size.html b/src/legacy/ui/public/agg_types/controls/top_aggregate_and_size.html deleted file mode 100644 index c96f7570081e1..0000000000000 --- a/src/legacy/ui/public/agg_types/controls/top_aggregate_and_size.html +++ /dev/null @@ -1,43 +0,0 @@ -
-
- - - -
-
- - - -
-
diff --git a/src/legacy/ui/public/agg_types/controls/top_field.tsx b/src/legacy/ui/public/agg_types/controls/top_field.tsx new file mode 100644 index 0000000000000..9069f081ccf99 --- /dev/null +++ b/src/legacy/ui/public/agg_types/controls/top_field.tsx @@ -0,0 +1,40 @@ +/* + * 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 from 'react'; +import { i18n } from '@kbn/i18n'; +import { AggParamEditorProps } from '../../vis/editors/default'; +import { FieldParamType } from '../param_types'; +import { FieldParamEditor } from './field'; +import { getCompatibleAggs } from './top_aggregate'; + +function TopFieldParamEditor(props: AggParamEditorProps) { + const compatibleAggs = getCompatibleAggs(props.agg, props.visName); + let customError; + + if (!compatibleAggs.length) { + customError = i18n.translate('common.ui.aggTypes.aggregateWith.noAggsErrorTooltip', { + defaultMessage: 'The chosen field has no compatible aggregations.', + }); + } + + return ; +} + +export { TopFieldParamEditor }; diff --git a/src/legacy/ui/public/agg_types/controls/top_size.tsx b/src/legacy/ui/public/agg_types/controls/top_size.tsx new file mode 100644 index 0000000000000..513b4681ecc74 --- /dev/null +++ b/src/legacy/ui/public/agg_types/controls/top_size.tsx @@ -0,0 +1,47 @@ +/* + * 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 from 'react'; +import { AggParamEditorProps } from 'ui/vis/editors/default'; +import { EuiIconTip } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { SizeParamEditor } from './size'; +import { getCompatibleAggs } from './top_aggregate'; + +function TopSizeParamEditor(props: AggParamEditorProps) { + const iconTip = ( + <> + {' '} + + + ); + const fieldType = props.agg.params.field && props.agg.params.field.type; + const disabled = fieldType && !getCompatibleAggs(props.agg, props.visName).length; + + return ; +} + +export { TopSizeParamEditor }; diff --git a/src/legacy/ui/public/agg_types/metrics/top_hit.js b/src/legacy/ui/public/agg_types/metrics/top_hit.js index bedac2a82c157..602df5fb6bc8b 100644 --- a/src/legacy/ui/public/agg_types/metrics/top_hit.js +++ b/src/legacy/ui/public/agg_types/metrics/top_hit.js @@ -19,11 +19,14 @@ import _ from 'lodash'; import { MetricAggType } from './metric_agg_type'; -import aggregateAndSizeEditor from '../controls/top_aggregate_and_size.html'; import { TopSortFieldParamEditor } from '../controls/top_sort_field'; import { OrderParamEditor } from '../controls/order'; import { aggTypeFieldFilters } from '../param_types/filter'; import { i18n } from '@kbn/i18n'; +import { wrapWithInlineComp } from '../buckets/_inline_comp_wrapper'; +import { TopFieldParamEditor } from '../controls/top_field'; +import { TopSizeParamEditor } from '../controls/top_size'; +import { TopAggregateParamEditor } from '../controls/top_aggregate'; const isNumber = function (type) { return type === 'number'; @@ -66,6 +69,7 @@ export const topHitMetricAgg = new MetricAggType({ { name: 'field', type: 'field', + editorComponent: TopFieldParamEditor, onlyAggregatable: false, filterFieldTypes: '*', write(agg, output) { @@ -95,47 +99,47 @@ export const topHitMetricAgg = new MetricAggType({ }, { name: 'aggregate', - type: 'optioned', - editor: aggregateAndSizeEditor, + type: 'select', + editorComponent: wrapWithInlineComp(TopAggregateParamEditor), options: [ { - display: i18n.translate('common.ui.aggTypes.metrics.topHit.minLabel', { + text: i18n.translate('common.ui.aggTypes.metrics.topHit.minLabel', { defaultMessage: 'Min' }), isCompatibleType: isNumber, isCompatibleVis: _.constant(true), disabled: true, - val: 'min' + value: 'min' }, { - display: i18n.translate('common.ui.aggTypes.metrics.topHit.maxLabel', { + text: i18n.translate('common.ui.aggTypes.metrics.topHit.maxLabel', { defaultMessage: 'Max' }), isCompatibleType: isNumber, isCompatibleVis: _.constant(true), disabled: true, - val: 'max' + value: 'max' }, { - display: i18n.translate('common.ui.aggTypes.metrics.topHit.sumLabel', { + text: i18n.translate('common.ui.aggTypes.metrics.topHit.sumLabel', { defaultMessage: 'Sum' }), isCompatibleType: isNumber, isCompatibleVis: _.constant(true), disabled: true, - val: 'sum' + value: 'sum' }, { - display: i18n.translate('common.ui.aggTypes.metrics.topHit.averageLabel', { + text: i18n.translate('common.ui.aggTypes.metrics.topHit.averageLabel', { defaultMessage: 'Average' }), isCompatibleType: isNumber, isCompatibleVis: _.constant(true), disabled: true, - val: 'average' + value: 'average' }, { - display: i18n.translate('common.ui.aggTypes.metrics.topHit.concatenateLabel', { + text: i18n.translate('common.ui.aggTypes.metrics.topHit.concatenateLabel', { defaultMessage: 'Concatenate' }), isCompatibleType: _.constant(true), @@ -143,27 +147,14 @@ export const topHitMetricAgg = new MetricAggType({ return name === 'metric' || name === 'table'; }, disabled: true, - val: 'concat' + value: 'concat' } ], - controller: function ($scope) { - $scope.options = []; - $scope.$watchGroup([ 'vis.type.name', 'agg.params.field.type' ], function ([ visName, fieldType ]) { - if (fieldType && visName) { - $scope.options = _.filter($scope.aggParam.options, option => { - return option.isCompatibleVis(visName) && option.isCompatibleType(fieldType); - }); - if ($scope.options.length === 1) { - $scope.agg.params.aggregate = $scope.options[0]; - } - } - }); - }, write: _.noop }, { name: 'size', - editor: null, // size setting is done together with the aggregation setting + editorComponent: wrapWithInlineComp(TopSizeParamEditor), default: 1 }, { @@ -245,7 +236,7 @@ export const topHitMetricAgg = new MetricAggType({ if (!_.compact(values).length) { return null; } - switch (agg.params.aggregate.val) { + switch (agg.params.aggregate.value) { case 'max': return _.max(values); case 'min': diff --git a/src/legacy/ui/public/agg_types/param_types/select.d.ts b/src/legacy/ui/public/agg_types/param_types/select.d.ts index 06aeb0b37e066..d2f26b34ade2e 100644 --- a/src/legacy/ui/public/agg_types/param_types/select.d.ts +++ b/src/legacy/ui/public/agg_types/param_types/select.d.ts @@ -24,15 +24,15 @@ interface SelectValueProp { text: string; } -interface SelectOptions extends IndexedArray { +interface SelectOptions extends IndexedArray { byValue: { - [key: string]: SelectValueProp; + [key: string]: T; }; } -interface SelectParamEditorProps { +interface SelectParamEditorProps { aggParam: { - options: SelectOptions; + options: SelectOptions; }; } 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 cc5a8f2ec05fc..4640dbe08c004 100644 --- a/src/legacy/ui/public/vis/editors/default/agg_param.js +++ b/src/legacy/ui/public/vis/editors/default/agg_param.js @@ -36,6 +36,7 @@ uiModules ['setValidity', { watchDepth: 'reference' }], 'showValidation', 'value', + 'visName' ])) .directive('visAggParamEditor', function (config) { return { @@ -64,6 +65,7 @@ uiModules indexed-fields="indexedFields" show-validation="showValidation" value="paramValue" + vis-name="vis.type.name" on-change="onChange" set-touched="setTouched" set-validity="setValidity" 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 0b65e1a631a63..b696ffe40965a 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 @@ -26,7 +26,8 @@ import { EditorConfig } from '../config/types'; // as there is currently a bug on babel typescript transform plugin for it // https://github.com/babel/babel/issues/7641 // -export interface AggParamEditorProps { + +export interface AggParamCommonProps { agg: AggConfig; aggParam: AggParam; config: any; @@ -34,7 +35,12 @@ export interface AggParamEditorProps { indexedFields?: FieldParamType[]; showValidation: boolean; value: T; + visName: string; setValidity(isValid: boolean): void; - setValue(value?: T): void; setTouched(): void; } + +export interface AggParamEditorProps extends AggParamCommonProps { + wrappedWithInlineComp?: boolean; + setValue(value?: T): 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 ebc9a8ff253b4..eb694385f3be8 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 @@ -19,24 +19,11 @@ import React, { useEffect } 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'; -import { EditorConfig } from '../config/types'; +import { AggParamEditorProps, AggParamCommonProps } from './agg_param_editor_props'; -interface AggParamReactWrapperProps { - agg: AggConfig; - aggParam: AggParam; - config: any; - editorConfig: EditorConfig; - indexedFields: FieldParamType[]; - showValidation: boolean; +interface AggParamReactWrapperProps extends AggParamCommonProps { paramEditor: React.FunctionComponent>; - value: T; onChange(value?: T): void; - setTouched(): void; - setValidity(isValid: boolean): void; } function AggParamReactWrapper(props: AggParamReactWrapperProps) { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 57a9137e9e770..3e15b1df6d743 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -80,7 +80,6 @@ "common.ui.aggResponse.fieldLabel": "フィールド", "common.ui.aggResponse.valueLabel": "値", "common.ui.aggTypes.aggregateWithLabel": "集約基準", - "common.ui.aggTypes.aggregateWithTooltip": "複数ヒットまたは複数値のフィールドを 1 つのメトリックにまとめる方法を選択してください", "common.ui.aggTypes.buckets.dateHistogramLabel": "{intervalDescription} ごとに {fieldName}", "common.ui.aggTypes.buckets.dateHistogramTitle": "Date Histogram", "common.ui.aggTypes.buckets.dateRangeTitle": "日付範囲", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 913f51b63a336..86def61ba7415 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -80,7 +80,6 @@ "common.ui.aggResponse.fieldLabel": "字段", "common.ui.aggResponse.valueLabel": "值", "common.ui.aggTypes.aggregateWithLabel": "聚合对象", - "common.ui.aggTypes.aggregateWithTooltip": "选择将多个命中或多值字段组合成单个指标的策略", "common.ui.aggTypes.buckets.dateHistogramLabel": "{fieldName}/{intervalDescription}", "common.ui.aggTypes.buckets.dateHistogramTitle": "日期直方图", "common.ui.aggTypes.buckets.dateRangeTitle": "日期范围",