Merge remote-tracking branch 'origin/7.x' into backport/7.x/pr-40715

# Conflicts:
#	src/legacy/ui/public/vis/editors/default/components/__snapshots__/default_editor_agg_params.test.tsx.snap
#	src/legacy/ui/public/vis/editors/default/components/default_editor_agg_params.test.tsx
#	src/legacy/ui/public/vis/editors/default/components/default_editor_agg_params_helper.test.ts
#	src/legacy/ui/public/vis/editors/default/default.js
This commit is contained in:
sulemanof 2019-07-16 14:57:48 +03:00
commit ecc78ab5ee
18 changed files with 194 additions and 371 deletions

View file

@ -26,7 +26,7 @@ describe('Terms Agg', function () {
let $rootScope;
function init({ responseValueAggs = [], aggParams = {} }) {
function init({ metricAggs = [], aggParams = {} }) {
ngMock.module('kibana');
ngMock.inject(function ($controller, _$rootScope_) {
const terms = aggTypes.byName.terms;
@ -41,7 +41,7 @@ describe('Terms Agg', function () {
aggs: []
}
};
$rootScope.responseValueAggs = responseValueAggs;
$rootScope.metricAggs = metricAggs;
$controller(orderAggController, { $scope: $rootScope });
$rootScope.$digest();
});
@ -50,7 +50,7 @@ describe('Terms Agg', function () {
// should be rewritten after EUIficate order_agg.html
it.skip('selects _key if the selected metric becomes incompatible', function () {
init({
responseValueAggs: [
metricAggs: [
{
id: 'agg1',
type: {
@ -60,7 +60,7 @@ describe('Terms Agg', function () {
]
});
expect($rootScope.agg.params.orderBy).to.be('agg1');
$rootScope.responseValueAggs = [
$rootScope.metricAggs = [
{
id: 'agg1',
type: {
@ -75,7 +75,7 @@ describe('Terms Agg', function () {
// should be rewritten after EUIficate order_agg.html
it.skip('selects _key if the selected metric is removed', function () {
init({
responseValueAggs: [
metricAggs: [
{
id: 'agg1',
type: {
@ -85,7 +85,7 @@ describe('Terms Agg', function () {
]
});
expect($rootScope.agg.params.orderBy).to.be('agg1');
$rootScope.responseValueAggs = [];
$rootScope.metricAggs = [];
$rootScope.$digest();
expect($rootScope.agg.params.orderBy).to.be('_key');
});
@ -93,7 +93,7 @@ describe('Terms Agg', function () {
describe.skip('custom field formatter', () => {
beforeEach(() => {
init({
responseValueAggs: [
metricAggs: [
{
id: 'agg1',
type: {

View file

@ -49,12 +49,8 @@ function FromToList({ showValidation, onBlur, ...rest }: FromToListProps) {
from: { value: '0.0.0.0', model: '0.0.0.0', isInvalid: false },
to: { value: '255.255.255.255', model: '255.255.255.255', isInvalid: false },
},
defaultEmptyValue: {
from: { value: EMPTY_STRING, model: EMPTY_STRING, isInvalid: false },
to: { value: EMPTY_STRING, model: EMPTY_STRING, isInvalid: false },
},
validateClass: Ipv4Address,
getModelValue: (item: FromToObject) => ({
getModelValue: (item: FromToObject = {}) => ({
from: {
value: item.from || EMPTY_STRING,
model: item.from || EMPTY_STRING,
@ -117,10 +113,7 @@ function FromToList({ showValidation, onBlur, ...rest }: FromToListProps) {
</EuiFlexItem>
</>
),
validateModel: (validateFn, object: FromToObject, model: FromToModel) => {
validateFn(object.from, model.from);
validateFn(object.to, model.to);
},
modelNames: ['from', 'to'],
};
return <InputList config={fromToListConfig} {...rest} />;

View file

@ -18,6 +18,7 @@
*/
import React, { useState, useEffect, Fragment } from 'react';
import { isEmpty, isEqual, mapValues, omit, pick } from 'lodash';
import {
EuiButtonIcon,
EuiFlexGroup,
@ -30,9 +31,8 @@ import { FormattedMessage } from '@kbn/i18n/react';
export interface InputListConfig {
defaultValue: InputItemModel;
defaultEmptyValue: InputItemModel;
validateClass: new (value: string) => { toString(): string };
getModelValue(item: InputObject): InputItemModel;
getModelValue(item?: InputObject): InputItemModel;
getRemoveBtnAriaLabel(model: InputModel): string;
onChangeFn(model: InputModel): InputObject;
hasInvalidValuesFn(model: InputModel): boolean;
@ -41,11 +41,7 @@ export interface InputListConfig {
index: number,
onChangeFn: (index: number, value: string, modelName: string) => void
): React.ReactNode;
validateModel(
validateFn: (value: string | undefined, modelObj: InputItem) => void,
object: InputObject,
model: InputModel
): void;
modelNames: string | string[];
}
interface InputModelBase {
id: string;
@ -74,121 +70,109 @@ interface InputListProps {
}
const generateId = htmlIdGenerator();
const validateValue = (inputValue: string | undefined, config: InputListConfig) => {
const result = {
model: inputValue || '',
isInvalid: false,
};
if (!inputValue) {
result.isInvalid = false;
return result;
}
try {
const InputObject = config.validateClass;
result.model = new InputObject(inputValue).toString();
result.isInvalid = false;
return result;
} catch (e) {
result.isInvalid = true;
return result;
}
};
function InputList({ config, list, onChange, setValidity }: InputListProps) {
const [models, setModels] = useState(
list.length
? list.map(
const [models, setModels] = useState(() =>
list.map(
item =>
({
id: generateId(),
...config.getModelValue(item),
} as InputModel)
)
);
const hasInvalidValues = models.some(config.hasInvalidValuesFn);
const updateValues = (modelList: InputModel[]) => {
setModels(modelList);
onChange(modelList.map(config.onChangeFn));
};
const onChangeValue = (index: number, value: string, modelName: string) => {
const { model, isInvalid } = validateValue(value, config);
updateValues(
models.map((range, arrayIndex) =>
arrayIndex === index
? {
...range,
[modelName]: {
value,
model,
isInvalid,
},
}
: range
)
);
};
const onDelete = (id: string) => updateValues(models.filter(model => model.id !== id));
const onAdd = () =>
updateValues([
...models,
{
id: generateId(),
...config.getModelValue(),
} as InputModel,
]);
useEffect(() => {
// resposible for setting up an initial value when there is no default value
if (!list.length) {
updateValues([
{
id: generateId(),
...config.defaultValue,
} as InputModel,
]);
}
}, []);
useEffect(() => {
setValidity(!hasInvalidValues);
}, [hasInvalidValues]);
useEffect(() => {
// responsible for discarding changes
if (
list.length !== models.length ||
list.some((item, index) => {
// make model to be the same shape as stored value
const model: InputObject = mapValues(pick(models[index], config.modelNames), 'model');
// we need to skip empty values since they are not stored in saved object
return !isEqual(item, omit(model, isEmpty));
})
) {
setModels(
list.map(
item =>
({
id: generateId(),
...config.getModelValue(item),
} as InputModel)
)
: [
{
id: generateId(),
...config.defaultValue,
} as InputModel,
]
);
const onUpdate = (modelList: InputModel[]) => {
setModels(modelList);
onChange(modelList.map(config.onChangeFn));
};
const onChangeValue = (index: number, value: string, modelName: string) => {
const range = models[index][modelName];
const { model, isInvalid } = validateValue(value);
range.value = value;
range.model = model;
range.isInvalid = isInvalid;
onUpdate(models);
};
const onDelete = (id: string) => {
const newArray = models.filter(model => model.id !== id);
onUpdate(newArray);
};
const onAdd = () => {
const newArray = [
...models,
{
id: generateId(),
...config.defaultEmptyValue,
} as InputModel,
];
onUpdate(newArray);
};
const getUpdatedModels = (objList: InputObject[], modelList: InputModel[]) => {
if (!objList.length) {
return modelList;
);
}
return objList.map((item, index) => {
const model = modelList[index] || {
id: generateId(),
...config.getModelValue(item),
};
config.validateModel(validateItem, item, model);
return model;
});
};
const validateItem = (value: string | undefined, modelObj: InputItem) => {
const { model, isInvalid } = validateValue(value);
if (value !== modelObj.model) {
modelObj.value = model;
}
modelObj.model = model;
modelObj.isInvalid = isInvalid;
};
const validateValue = (inputValue: string | undefined) => {
const result = {
model: inputValue || '',
isInvalid: false,
};
if (!inputValue) {
result.isInvalid = false;
return result;
}
try {
const InputObject = config.validateClass;
result.model = new InputObject(inputValue).toString();
result.isInvalid = false;
return result;
} catch (e) {
result.isInvalid = true;
return result;
}
};
const hasInvalidValues = (modelList: InputModel[]) => {
return !!modelList.find(config.hasInvalidValuesFn);
};
// responsible for discarding changes
useEffect(() => {
setModels(getUpdatedModels(list, models));
}, [list]);
useEffect(() => {
setValidity(!hasInvalidValues(models));
}, [models]);
// resposible for setting up an initial value when there is no default value
useEffect(() => {
onChange(models.map(config.onChangeFn));
}, []);
if (!list || !list.length) {
return null;
}
return (
<>
{models.map((item, index) => (

View file

@ -46,11 +46,8 @@ function MaskList({ showValidation, onBlur, ...rest }: MaskListProps) {
defaultValue: {
mask: { model: '0.0.0.0/1', value: '0.0.0.0/1', isInvalid: false },
},
defaultEmptyValue: {
mask: { model: EMPTY_STRING, value: EMPTY_STRING, isInvalid: false },
},
validateClass: CidrMask,
getModelValue: (item: MaskObject) => ({
getModelValue: (item: MaskObject = {}) => ({
mask: {
model: item.mask || EMPTY_STRING,
value: item.mask || EMPTY_STRING,
@ -91,9 +88,7 @@ function MaskList({ showValidation, onBlur, ...rest }: MaskListProps) {
/>
</EuiFlexItem>
),
validateModel: (validateFn, object: MaskObject, model: MaskModel) => {
validateFn(object.mask, model.mask);
},
modelNames: 'mask',
};
return <InputList config={maskListConfig} {...rest} />;

View file

@ -18,10 +18,8 @@
*/
import React, { useEffect } from 'react';
import { findLast } from 'lodash';
import { EuiFormRow, EuiSelect } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { AggConfig } from 'ui/vis';
import { AggParamEditorProps } from 'ui/vis/editors/default';
import { safeMakeLabel, isCompatibleAggregation } from '../agg_utils';
@ -32,13 +30,11 @@ const EMPTY_VALUE = 'EMPTY_VALUE';
function MetricAggParamEditor({
agg,
value,
state,
showValidation,
setValue,
setValidity,
setTouched,
subAggParams,
responseValueAggs,
metricAggs,
}: AggParamEditorProps<string>) {
const label = i18n.translate('common.ui.aggTypes.metricLabel', {
defaultMessage: 'Metric',
@ -50,9 +46,9 @@ function MetricAggParamEditor({
}, [isValid]);
useEffect(() => {
if (responseValueAggs && value && value !== 'custom') {
if (metricAggs && value && value !== 'custom') {
// ensure that metricAgg is set to a valid agg
const respAgg = responseValueAggs
const respAgg = metricAggs
.filter(isCompatibleAgg)
.find(aggregation => aggregation.id === value);
@ -60,56 +56,10 @@ function MetricAggParamEditor({
setValue();
}
}
}, [responseValueAggs]);
}, [metricAggs]);
useEffect(() => {
// check buckets
const lastBucket: AggConfig = findLast(
state.aggs,
aggr => aggr.type && aggr.type.type === 'buckets'
);
const bucketHasType = lastBucket && lastBucket.type;
const bucketIsHistogram =
bucketHasType && ['date_histogram', 'histogram'].includes(lastBucket.type.name);
const canUseAggregation = lastBucket && bucketIsHistogram;
// remove errors on all buckets
state.aggs.forEach((aggr: AggConfig) => {
if (aggr.error) {
subAggParams.onAggErrorChanged(aggr);
}
});
if (canUseAggregation) {
subAggParams.onAggParamsChange(
lastBucket.params,
'min_doc_count',
lastBucket.type.name === 'histogram' ? 1 : 0
);
} else {
if (lastBucket) {
subAggParams.onAggErrorChanged(
lastBucket,
i18n.translate('common.ui.aggTypes.metrics.wrongLastBucketTypeErrorMessage', {
defaultMessage:
'Last bucket aggregation must be "Date Histogram" or "Histogram" when using "{type}" metric aggregation.',
values: { type: agg.type.title },
description: 'Date Histogram and Histogram should not be translated',
})
);
}
}
return () => {
// clear errors in last bucket before component destroyed
if (lastBucket && lastBucket.error) {
subAggParams.onAggErrorChanged(lastBucket);
}
};
}, [value, responseValueAggs]);
const options = responseValueAggs
? responseValueAggs
const options = metricAggs
? metricAggs
.filter(respAgg => respAgg.type.name !== agg.type.name)
.map(respAgg => ({
text: i18n.translate('common.ui.aggTypes.definiteMetricLabel', {

View file

@ -48,7 +48,7 @@ describe('OrderAggParamEditor component', () => {
});
it('defaults to the first metric agg after init', () => {
const responseValueAggs = [
const metricAggs = [
{
id: 'agg1',
type: {
@ -62,7 +62,7 @@ describe('OrderAggParamEditor component', () => {
},
},
];
const props = { ...defaultProps, responseValueAggs };
const props = { ...defaultProps, metricAggs };
mount(<OrderByParamEditor {...props} />);
@ -70,7 +70,7 @@ describe('OrderAggParamEditor component', () => {
});
it('defaults to the first metric agg that is compatible with the terms bucket', () => {
const responseValueAggs = [
const metricAggs = [
{
id: 'agg1',
type: {
@ -102,7 +102,7 @@ describe('OrderAggParamEditor component', () => {
},
},
];
const props = { ...defaultProps, responseValueAggs };
const props = { ...defaultProps, metricAggs };
mount(<OrderByParamEditor {...props} />);
@ -110,7 +110,7 @@ describe('OrderAggParamEditor component', () => {
});
it('defaults to the _key metric if no agg is compatible', () => {
const responseValueAggs = [
const metricAggs = [
{
id: 'agg1',
type: {
@ -118,7 +118,7 @@ describe('OrderAggParamEditor component', () => {
},
},
];
const props = { ...defaultProps, responseValueAggs };
const props = { ...defaultProps, metricAggs };
mount(<OrderByParamEditor {...props} />);
@ -126,7 +126,7 @@ describe('OrderAggParamEditor component', () => {
});
it('selects first metric if it is avg', () => {
const responseValueAggs = [
const metricAggs = [
{
id: 'agg1',
type: {
@ -135,7 +135,7 @@ describe('OrderAggParamEditor component', () => {
},
},
];
const props = { ...defaultProps, responseValueAggs };
const props = { ...defaultProps, metricAggs };
mount(<OrderByParamEditor {...props} />);
@ -143,7 +143,7 @@ describe('OrderAggParamEditor component', () => {
});
it('selects _key if the first metric is avg_bucket', () => {
const responseValueAggs = [
const metricAggs = [
{
id: 'agg1',
type: {
@ -152,7 +152,7 @@ describe('OrderAggParamEditor component', () => {
},
},
];
const props = { ...defaultProps, responseValueAggs };
const props = { ...defaultProps, metricAggs };
mount(<OrderByParamEditor {...props} />);

View file

@ -24,7 +24,7 @@ import { AggConfig } from '../../vis';
function OrderAggParamEditor({
agg,
value,
responseValueAggs,
metricAggs,
state,
setValue,
setValidity,
@ -32,7 +32,7 @@ function OrderAggParamEditor({
subAggParams,
}: AggParamEditorProps<AggConfig>) {
useEffect(() => {
if (responseValueAggs) {
if (metricAggs) {
const orderBy = agg.params.orderBy;
// we aren't creating a custom aggConfig
@ -43,7 +43,7 @@ function OrderAggParamEditor({
setValue(value || paramDef.makeOrderAgg(agg));
}
}
}, [agg.params.orderBy, responseValueAggs]);
}, [agg.params.orderBy, metricAggs]);
const [innerState, setInnerState] = useState(true);
@ -58,7 +58,7 @@ function OrderAggParamEditor({
className="visEditorAgg__subAgg"
formIsTouched={subAggParams.formIsTouched}
indexPattern={agg.getIndexPattern()}
responseValueAggs={responseValueAggs}
metricAggs={metricAggs}
state={state}
onAggParamsChange={(...rest) => {
// to force update when sub-agg params are changed
@ -66,7 +66,6 @@ function OrderAggParamEditor({
subAggParams.onAggParamsChange(...rest);
}}
onAggTypeChange={subAggParams.onAggTypeChange}
onAggErrorChanged={subAggParams.onAggErrorChanged}
setValidity={setValidity}
setTouched={setTouched}
/>

View file

@ -46,7 +46,7 @@ function OrderByParamEditor({
setValue,
setValidity,
setTouched,
responseValueAggs,
metricAggs,
}: AggParamEditorProps<string>) {
const label = i18n.translate('common.ui.aggTypes.orderAgg.orderByLabel', {
defaultMessage: 'Order by',
@ -62,8 +62,8 @@ function OrderByParamEditor({
if (!value) {
let respAgg = { id: '_key' };
if (responseValueAggs) {
respAgg = responseValueAggs.filter(isCompatibleAgg)[0] || respAgg;
if (metricAggs) {
respAgg = metricAggs.filter(isCompatibleAgg)[0] || respAgg;
}
setValue(respAgg.id);
@ -71,9 +71,9 @@ function OrderByParamEditor({
}, []);
useEffect(() => {
if (responseValueAggs && value && value !== 'custom') {
if (metricAggs && value && value !== 'custom') {
// ensure that orderBy is set to a valid agg
const respAgg = responseValueAggs
const respAgg = metricAggs
.filter(isCompatibleAgg)
.find(aggregation => aggregation.id === value);
@ -81,7 +81,7 @@ function OrderByParamEditor({
setValue('_key');
}
}
}, [responseValueAggs]);
}, [metricAggs]);
const defaultOptions = [
{
@ -98,8 +98,8 @@ function OrderByParamEditor({
},
];
const options = responseValueAggs
? responseValueAggs.map(respAgg => ({
const options = metricAggs
? metricAggs.map(respAgg => ({
text: i18n.translate('common.ui.aggTypes.orderAgg.metricLabel', {
defaultMessage: 'Metric: {metric}',
values: {

View file

@ -24,7 +24,7 @@ import { AggConfig } from '../../vis';
function SubAggParamEditor({
agg,
value,
responseValueAggs,
metricAggs,
state,
setValue,
setValidity,
@ -38,7 +38,7 @@ function SubAggParamEditor({
} else if (!agg.params.customMetric) {
setValue(agg.type.params.byName.customMetric.makeAgg(agg));
}
}, [value, responseValueAggs]);
}, [value, metricAggs]);
const [innerState, setInnerState] = useState(true);
@ -53,7 +53,7 @@ function SubAggParamEditor({
className="visEditorAgg__subAgg"
formIsTouched={subAggParams.formIsTouched}
indexPattern={agg.getIndexPattern()}
responseValueAggs={responseValueAggs}
metricAggs={metricAggs}
state={state}
onAggParamsChange={(...rest) => {
// to force update when sub-agg params are changed
@ -61,7 +61,6 @@ function SubAggParamEditor({
subAggParams.onAggParamsChange(...rest);
}}
onAggTypeChange={subAggParams.onAggTypeChange}
onAggErrorChanged={subAggParams.onAggErrorChanged}
setValidity={setValidity}
setTouched={setTouched}
/>

View file

@ -27,7 +27,7 @@ import { AggGroupNames } from '../../vis/editors/default/agg_groups';
function SubMetricParamEditor({
agg,
aggParam,
responseValueAggs,
metricAggs,
state,
setValue,
setValidity,
@ -64,7 +64,7 @@ function SubMetricParamEditor({
className="visEditorAgg__subAgg"
formIsTouched={subAggParams.formIsTouched}
indexPattern={agg.getIndexPattern()}
responseValueAggs={responseValueAggs}
metricAggs={metricAggs}
state={state}
onAggParamsChange={(...rest) => {
// to force update when sub-agg params are changed
@ -72,7 +72,6 @@ function SubMetricParamEditor({
subAggParams.onAggParamsChange(...rest);
}}
onAggTypeChange={subAggParams.onAggTypeChange}
onAggErrorChanged={subAggParams.onAggErrorChanged}
setValidity={setValidity}
setTouched={setTouched}
/>

View file

@ -1,123 +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 angular from 'angular';
import _ from 'lodash';
import expect from '@kbn/expect';
import ngMock from 'ng_mock';
import '../agg_params';
import { VisProvider } from '../../..';
import { AggConfig } from '../../../agg_config';
import { Schemas } from '../schemas';
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
describe.skip('Vis-Editor-Agg-Params plugin directive', function () {
let $parentScope = {};
let Vis;
let vis;
let $elem;
let compile;
let rootScope;
const aggFilter = [
'!top_hits', '!percentiles', '!median', '!std_dev',
'!derivative', '!cumulative_sum', '!moving_avg', '!serial_diff'
];
let indexPattern;
let orderAggSchema;
beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject(function (Private, $rootScope, $compile) {
rootScope = $rootScope;
compile = $compile;
Vis = Private(VisProvider);
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
}));
function init(config) {
$parentScope = {};
_.defaults($parentScope, rootScope, Object.getPrototypeOf(rootScope));
orderAggSchema = (new Schemas([config])).all[0];
$parentScope.groupName = 'metrics';
const state = {
schema: orderAggSchema,
type: 'count'
};
vis = new Vis(indexPattern, {
type: 'histogram',
aggs: [
{
type: 'date_histogram',
schema: 'segment'
}
]
});
$parentScope.agg = new AggConfig(vis.aggs, state);
$parentScope.vis = vis;
// make the element
$elem = angular.element(
`<vis-editor-agg-params index-pattern="vis.indexPattern" agg="agg" group-name="groupName"></vis-editor-agg-params>`
);
// compile the html
compile($elem)($parentScope);
// Digest everything
$elem.scope().$digest();
}
afterEach(function () {
$parentScope.$destroy();
$parentScope = null;
});
it('should show custom label parameter', function () {
init ({
group: 'none',
name: 'orderAgg',
title: 'Order Agg',
aggFilter: aggFilter
});
const customLabelElement = $elem.find('label:contains("Custom label")');
expect(customLabelElement.length).to.be(1);
});
it('should hide custom label parameter', function () {
init ({
group: 'none',
name: 'orderAgg',
title: 'Order Agg',
hideCustomLabel: true,
aggFilter: aggFilter
});
const customLabelElement = $elem.find('label:contains("Custom label")');
expect(customLabelElement.length).to.be(0);
});
});

View file

@ -117,18 +117,18 @@
agg-index="$index"
agg-is-too-low="aggIsTooLow"
agg-params="agg.params"
agg-error="agg.error"
agg-error="error"
disabled-params="disabledParams"
editor-config="editorConfig"
form-is-touched="formIsTouched"
group-name="groupName"
index-pattern="vis.indexPattern"
response-value-aggs="responseValueAggs"
metric-aggs="metricAggs"
state="state"
on-agg-type-change="onAggTypeChange"
on-agg-params-change="onAggParamsChange"
set-touched="setTouched"
set-validity="setValidity"
on-agg-error-changed="onAggErrorChanged">
set-validity="setValidity">
</vis-editor-agg-params>
</div>

View file

@ -17,6 +17,7 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
import './agg_params';
import './agg_add';
import './controls/agg_controls';
@ -51,6 +52,37 @@ uiModules
$scope.aggIsTooLow = calcAggIsTooLow();
});
if ($scope.groupName === 'buckets') {
$scope.$watchMulti([
'$last',
'lastParentPipelineAggTitle',
'agg.type'
], function ([isLastBucket, lastParentPipelineAggTitle, aggType]) {
$scope.error = null;
$scope.disabledParams = [];
if (!lastParentPipelineAggTitle || !isLastBucket || !aggType) {
return;
}
if (['date_histogram', 'histogram'].includes(aggType.name)) {
$scope.onAggParamsChange(
$scope.agg.params,
'min_doc_count',
// "histogram" agg has an editor for "min_doc_count" param, which accepts boolean
// "date_histogram" agg doesn't have an editor for "min_doc_count" param, it should be set as a numeric value
aggType.name === 'histogram' ? true : 0);
$scope.disabledParams = ['min_doc_count'];
} else {
$scope.error = i18n.translate('common.ui.aggTypes.metrics.wrongLastBucketTypeErrorMessage', {
defaultMessage: 'Last bucket aggregation must be "Date Histogram" or "Histogram" when using "{type}" metric aggregation.',
values: { type: lastParentPipelineAggTitle },
description: 'Date Histogram and Histogram should not be translated',
});
}
});
}
/**
* Describe the aggregation, for display in the collapsed agg header
* @return {[type]} [description]
@ -151,14 +183,6 @@ uiModules
ngModelCtrl.$setUntouched();
}
};
$scope.onAggErrorChanged = (agg, error) => {
if (error) {
agg.error = error;
} else {
delete agg.error;
}
};
}
};
});

View file

@ -28,16 +28,16 @@ uiModules
['agg', { watchDepth: 'reference' }],
['aggParams', { watchDepth: 'collection' }],
['indexPattern', { watchDepth: 'reference' }],
['responseValueAggs', { watchDepth: 'reference' }], // we watch reference to identify each aggs change in useEffects
['metricAggs', { watchDepth: 'reference' }], // we watch reference to identify each aggs change in useEffects
['state', { watchDepth: 'reference' }],
['onAggErrorChanged', { watchDepth: 'reference' }],
['onAggTypeChange', { watchDepth: 'reference' }],
['onAggParamsChange', { watchDepth: 'reference' }],
['setTouched', { watchDepth: 'reference' }],
['setValidity', { watchDepth: 'reference' }],
'aggError',
'aggIndex',
'disabledParams',
'groupName',
'aggIsTooLow',
'formIsTouched'
'formIsTouched',
]));

View file

@ -32,12 +32,13 @@ import { SubAggParamsProp } from './default_editor_agg_params';
export interface AggParamCommonProps<T> {
agg: AggConfig;
aggParam: AggParam;
disabled?: boolean;
editorConfig: EditorConfig;
indexedFields?: FieldParamType[];
showValidation: boolean;
state: VisState;
value: T;
responseValueAggs: AggConfig[] | null;
metricAggs: AggConfig[];
subAggParams: SubAggParamsProp;
setValidity(isValid: boolean): void;
setTouched(): void;

View file

@ -57,16 +57,17 @@ export interface SubAggParamsProp {
formIsTouched: boolean;
onAggParamsChange: (agg: AggParams, paramName: string, value: unknown) => void;
onAggTypeChange: (agg: AggConfig, aggType: AggType) => void;
onAggErrorChanged: (agg: AggConfig, error?: string) => void;
}
interface DefaultEditorAggParamsProps extends SubAggParamsProp {
export interface DefaultEditorAggParamsProps extends SubAggParamsProp {
agg: AggConfig;
aggError?: string | null;
aggIndex?: number;
aggIsTooLow?: boolean;
className?: string;
disabledParams?: string[];
groupName: string;
indexPattern: IndexPattern;
responseValueAggs: AggConfig[] | null;
metricAggs: AggConfig[];
state: VisState;
setTouched: (isTouched: boolean) => void;
setValidity: (isValid: boolean) => void;
@ -74,19 +75,20 @@ interface DefaultEditorAggParamsProps extends SubAggParamsProp {
function DefaultEditorAggParams({
agg,
aggError,
aggIndex = 0,
aggIsTooLow = false,
className,
disabledParams,
groupName,
formIsTouched,
indexPattern,
responseValueAggs,
metricAggs,
state = {} as VisState,
onAggParamsChange,
onAggTypeChange,
setTouched,
setValidity,
onAggErrorChanged,
}: DefaultEditorAggParamsProps) {
const groupedAggTypeOptions = getAggTypeOptions(agg, indexPattern, groupName);
const errors = getError(agg, aggIsTooLow);
@ -96,7 +98,7 @@ function DefaultEditorAggParams({
indexPattern,
agg
);
const params = getAggParamsToRender({ agg, editorConfig, responseValueAggs, state });
const params = getAggParamsToRender({ agg, editorConfig, metricAggs, state });
const allParams = [...params.basic, ...params.advanced];
const [paramsState, onChangeParamsState] = useReducer(
aggParamsReducer,
@ -163,6 +165,7 @@ function DefaultEditorAggParams({
return (
<DefaultEditorAggParam
key={`${paramInstance.aggParam.name}${agg.type ? agg.type.name : ''}`}
disabled={disabledParams && disabledParams.includes(paramInstance.aggParam.name)}
showValidation={formIsTouched || model.touched}
onChange={onAggParamsChange}
setValidity={valid => {
@ -183,7 +186,6 @@ function DefaultEditorAggParams({
subAggParams={{
onAggParamsChange,
onAggTypeChange,
onAggErrorChanged,
formIsTouched,
}}
{...paramInstance}
@ -199,7 +201,7 @@ function DefaultEditorAggParams({
data-test-subj="visAggEditorParams"
>
<DefaultEditorAggSelect
aggError={agg.error}
aggError={aggError}
id={agg.id}
indexPattern={indexPattern}
value={agg.type}

View file

@ -32,7 +32,7 @@ import { AggParamEditorProps } from './default_editor_agg_param_props';
interface ParamInstanceBase {
agg: AggConfig;
editorConfig: EditorConfig;
responseValueAggs: AggConfig[] | null;
metricAggs: AggConfig[];
state: VisState;
}
@ -43,7 +43,7 @@ export interface ParamInstance extends ParamInstanceBase {
value: unknown;
}
function getAggParamsToRender({ agg, editorConfig, responseValueAggs, state }: ParamInstanceBase) {
function getAggParamsToRender({ agg, editorConfig, metricAggs, state }: ParamInstanceBase) {
const params = {
basic: [] as ParamInstance[],
advanced: [] as ParamInstance[],
@ -88,7 +88,7 @@ function getAggParamsToRender({ agg, editorConfig, responseValueAggs, state }: P
editorConfig,
indexedFields,
paramEditor: param.editorComponent,
responseValueAggs,
metricAggs,
state,
value: agg.params[param.name],
} as ParamInstance);

View file

@ -28,7 +28,7 @@ import { documentationLinks } from '../../../../documentation_links/documentatio
import { ComboBoxGroupedOption } from '../default_editor_utils';
interface DefaultEditorAggSelectProps {
aggError?: string;
aggError?: string | null;
aggTypeOptions: AggType[];
id: string;
indexPattern: IndexPattern;