mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
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:
commit
ecc78ab5ee
18 changed files with 194 additions and 371 deletions
|
@ -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: {
|
||||
|
|
|
@ -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} />;
|
||||
|
|
|
@ -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) => (
|
||||
|
|
|
@ -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} />;
|
||||
|
|
|
@ -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', {
|
||||
|
|
|
@ -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} />);
|
||||
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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',
|
||||
]));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue