Sum, Avg, Min and Max bucket pipeline aggregation (#10070)

* allow parent aggs to have sub aggs defined

* adding bucket_sum aggregation

* adding bucket_avg, bucket_min and bucket_max

* fixing based on UI review

* adding tests

* disable terms sorting on pipeline aggs

* fixing based on Staceys review

* adding defaults

* updated based on review

* fixing error with stacking
This commit is contained in:
Peter Pisljar 2017-03-01 16:20:49 +01:00 committed by GitHub
parent e32656777e
commit 841a3d204f
20 changed files with 351 additions and 4 deletions

View file

@ -7,6 +7,6 @@
ng-model="agg.type"
required
auto-select-if-only-one="aggTypeOptions | aggFilter:agg.schema.aggFilter"
ng-options="agg as agg.title for agg in aggTypeOptions | aggFilter:agg.schema.aggFilter">
ng-options="agg as agg.title group by agg.subtype for agg in aggTypeOptions | aggFilter:agg.schema.aggFilter">
</select>
</div>

View file

@ -0,0 +1,110 @@
import _ from 'lodash';
import expect from 'expect.js';
import ngMock from 'ng_mock';
import BucketSum from 'ui/agg_types/metrics/bucket_sum';
import BucketAvg from 'ui/agg_types/metrics/bucket_avg';
import BucketMin from 'ui/agg_types/metrics/bucket_min';
import BucketMax from 'ui/agg_types/metrics/bucket_max';
import VisProvider from 'ui/vis';
import StubbedIndexPattern from 'fixtures/stubbed_logstash_index_pattern';
const metrics = [
{ name: 'sum_bucket', title: 'Overall Sum', provider: BucketSum },
{ name: 'avg_bucket', title: 'Overall Average', provider: BucketAvg },
{ name: 'min_bucket', title: 'Overall Min', provider: BucketMin },
{ name: 'max_bucket', title: 'Overall Max', provider: BucketMax },
];
describe('sibling pipeline aggs', function () {
metrics.forEach(metric => {
describe(`${metric.title} metric`, function () {
let aggDsl;
let metricAgg;
let aggConfig;
function init(settings) {
ngMock.module('kibana');
ngMock.inject(function (Private) {
const Vis = Private(VisProvider);
const indexPattern = Private(StubbedIndexPattern);
metricAgg = Private(metric.provider);
const params = settings || {
customMetric: {
id: '5',
type: 'count',
schema: 'metric'
},
customBucket: {
id: '6',
type: 'date_histogram',
schema: 'bucket',
params: { field: '@timestamp' }
}
};
const vis = new Vis(indexPattern, {
title: 'New Visualization',
type: 'metric',
params: {
fontSize: 60,
handleNoResults: true
},
aggs: [
{
id: '1',
type: 'count',
schema: 'metric'
},
{
id: '2',
type: metric.name,
schema: 'metric',
params
}
],
listeners: {}
});
// Grab the aggConfig off the vis (we don't actually use the vis for anything else)
aggConfig = vis.aggs[1];
aggDsl = aggConfig.toDsl();
});
}
it(`should return a label prefixed with ${metric.title} of`, function () {
init();
expect(metricAgg.makeLabel(aggConfig)).to.eql(`${metric.title} of Count`);
});
it('should set parent aggs', function () {
init();
expect(aggDsl[metric.name].buckets_path).to.be('2-bucket>_count');
expect(aggDsl.parentAggs['2-bucket'].date_histogram).to.not.be.undefined;
});
it('should set nested parent aggs', function () {
init({
customMetric: {
id: '5',
type: 'avg',
schema: 'metric',
params: { field: 'bytes' },
},
customBucket: {
id: '6',
type: 'date_histogram',
schema: 'bucket',
params: { field: '@timestamp' },
}
});
expect(aggDsl[metric.name].buckets_path).to.be('2-bucket>2-metric');
expect(aggDsl.parentAggs['2-bucket'].date_histogram).to.not.be.undefined;
expect(aggDsl.parentAggs['2-bucket'].aggs['2-metric'].avg.field).to.equal('bytes');
});
});
});
});

View file

@ -18,8 +18,10 @@ export default function TermsAggDefinition(Private) {
const aggFilter = [
'!top_hits', '!percentiles', '!median', '!std_dev',
'!derivative', '!cumulative_sum', '!moving_avg', '!serial_diff'
'!derivative', '!moving_avg', '!serial_diff', '!cumulative_sum',
'!avg_bucket', '!max_bucket', '!min_bucket', '!sum_bucket'
];
const orderAggSchema = (new Schemas([
{
group: 'none',

View file

@ -0,0 +1,14 @@
<div ng-controller="aggParam.controller">
<div class="form-group" ng-if="agg.params[aggType]">
<label>{{aggTitle}}</label>
<div class="vis-editor-agg-order-agg">
<ng-form name="{{aggType}}Form">
<vis-editor-agg-params
agg="agg.params[aggType]"
group-name="'{{aggGroup}}'">
</vis-editor-agg-params>
</ng-form>
</div>
</div>
</div>

View file

@ -24,6 +24,10 @@ import AggTypesBucketsTermsProvider from 'ui/agg_types/buckets/terms';
import AggTypesBucketsFiltersProvider from 'ui/agg_types/buckets/filters';
import AggTypesBucketsSignificantTermsProvider from 'ui/agg_types/buckets/significant_terms';
import AggTypesBucketsGeoHashProvider from 'ui/agg_types/buckets/geo_hash';
import AggTypesMetricsBucketSumProvider from 'ui/agg_types/metrics/bucket_sum';
import AggTypesMetricsBucketAvgProvider from 'ui/agg_types/metrics/bucket_avg';
import AggTypesMetricsBucketMinProvider from 'ui/agg_types/metrics/bucket_min';
import AggTypesMetricsBucketMaxProvider from 'ui/agg_types/metrics/bucket_max';
export default function AggTypeService(Private) {
const aggs = {
@ -42,7 +46,11 @@ export default function AggTypeService(Private) {
Private(AggTypesMetricsDerivativeProvider),
Private(AggTypesMetricsCumulativeSumProvider),
Private(AggTypesMetricsMovingAvgProvider),
Private(AggTypesMetricsSerialDiffProvider)
Private(AggTypesMetricsSerialDiffProvider),
Private(AggTypesMetricsBucketAvgProvider),
Private(AggTypesMetricsBucketSumProvider),
Private(AggTypesMetricsBucketMinProvider),
Private(AggTypesMetricsBucketMaxProvider),
],
buckets: [
Private(AggTypesBucketsDateHistogramProvider),

View file

@ -0,0 +1,18 @@
import AggTypesMetricsMetricAggTypeProvider from 'ui/agg_types/metrics/metric_agg_type';
import { makeNestedLabel } from './lib/make_nested_label';
import SiblingPipelineAggHelperProvider from './lib/sibling_pipeline_agg_helper';
export default function AggTypesMetricsBucketAvgProvider(Private) {
const MetricAggType = Private(AggTypesMetricsMetricAggTypeProvider);
const siblingPipelineHelper = Private(SiblingPipelineAggHelperProvider);
return new MetricAggType({
name: 'avg_bucket',
title: 'Average Bucket',
makeLabel: agg => makeNestedLabel(agg, 'overall average'),
subtype: siblingPipelineHelper.subtype,
params: [
...siblingPipelineHelper.params()
]
});
}

View file

@ -0,0 +1,18 @@
import AggTypesMetricsMetricAggTypeProvider from 'ui/agg_types/metrics/metric_agg_type';
import { makeNestedLabel } from './lib/make_nested_label';
import SiblingPipelineAggHelperProvider from './lib/sibling_pipeline_agg_helper';
export default function AggTypesMetricsBucketMaxProvider(Private) {
const MetricAggType = Private(AggTypesMetricsMetricAggTypeProvider);
const siblingPipelineHelper = Private(SiblingPipelineAggHelperProvider);
return new MetricAggType({
name: 'max_bucket',
title: 'Max Bucket',
makeLabel: agg => makeNestedLabel(agg, 'overall max'),
subtype: siblingPipelineHelper.subtype,
params: [
...siblingPipelineHelper.params()
]
});
}

View file

@ -0,0 +1,18 @@
import AggTypesMetricsMetricAggTypeProvider from 'ui/agg_types/metrics/metric_agg_type';
import { makeNestedLabel } from './lib/make_nested_label';
import SiblingPipelineAggHelperProvider from './lib/sibling_pipeline_agg_helper';
export default function AggTypesMetricsBucketMinProvider(Private) {
const MetricAggType = Private(AggTypesMetricsMetricAggTypeProvider);
const siblingPipelineHelper = Private(SiblingPipelineAggHelperProvider);
return new MetricAggType({
name: 'min_bucket',
title: 'Min Bucket',
makeLabel: agg => makeNestedLabel(agg, 'overall min'),
subtype: siblingPipelineHelper.subtype,
params: [
...siblingPipelineHelper.params()
]
});
}

View file

@ -0,0 +1,18 @@
import AggTypesMetricsMetricAggTypeProvider from 'ui/agg_types/metrics/metric_agg_type';
import { makeNestedLabel } from './lib/make_nested_label';
import SiblingPipelineAggHelperProvider from './lib/sibling_pipeline_agg_helper';
export default function AggTypesMetricsBucketSumProvider(Private) {
const MetricAggType = Private(AggTypesMetricsMetricAggTypeProvider);
const siblingPipelineHelper = Private(SiblingPipelineAggHelperProvider);
return new MetricAggType({
name: 'sum_bucket',
title: 'Sum Bucket',
makeLabel: agg => makeNestedLabel(agg, 'overall sum'),
subtype: siblingPipelineHelper.subtype,
params: [
...siblingPipelineHelper.params()
]
});
}

View file

@ -9,6 +9,7 @@ export default function AggTypeMetricComulativeSumProvider(Private) {
return new MetricAggType({
name: 'cumulative_sum',
title: 'Cumulative Sum',
subtype: parentPipelineAggHelper.subtype,
makeLabel: agg => makeNestedLabel(agg, 'cumulative sum'),
params: [
...parentPipelineAggHelper.params()

View file

@ -9,6 +9,7 @@ export default function AggTypeMetricDerivativeProvider(Private) {
return new MetricAggType({
name: 'derivative',
title: 'Derivative',
subtype: parentPipelineAggHelper.subtype,
makeLabel: agg => makeNestedLabel(agg, 'derivative'),
params: [
...parentPipelineAggHelper.params()

View file

@ -22,6 +22,7 @@ const ParentPipelineAggHelperProvider = function (Private) {
])).all[0];
return {
subtype: 'Parent Pipeline Aggregations',
params: function () {
return [
{

View file

@ -0,0 +1,23 @@
import safeMakeLabel from './safe_make_label';
const siblingPipelineAggController = function (type) {
return function ($scope) {
$scope.aggType = type;
$scope.aggTitle = type === 'customMetric' ? 'Metric' : 'Bucket';
$scope.aggGroup = type === 'customMetric' ? 'metrics' : 'buckets';
$scope.safeMakeLabel = safeMakeLabel;
function updateAgg() {
const agg = $scope.agg;
const params = agg.params;
const paramDef = agg.type.params.byName[type];
params[type] = params[type] || paramDef.makeAgg(agg);
}
updateAgg();
};
};
export { siblingPipelineAggController };

View file

@ -0,0 +1,92 @@
import _ from 'lodash';
import VisAggConfigProvider from 'ui/vis/agg_config';
import VisSchemasProvider from 'ui/vis/schemas';
import { siblingPipelineAggController } from './sibling_pipeline_agg_controller';
import { siblingPipelineAggWritter } from './sibling_pipeline_agg_writter';
import metricAggTemplate from 'ui/agg_types/controls/sub_metric.html';
const SiblingPipelineAggHelperProvider = function (Private) {
const AggConfig = Private(VisAggConfigProvider);
const Schemas = Private(VisSchemasProvider);
const metricAggFilter = [
'!top_hits', '!percentiles', '!percentile_ranks', '!median', '!std_dev',
'!sum_bucket', '!avg_bucket', '!min_bucket', '!max_bucket',
'!derivative', '!moving_avg', '!serial_diff', '!cumulative_sum'
];
const metricAggSchema = (new Schemas([
{
group: 'none',
name: 'metricAgg',
title: 'Metric Agg',
aggFilter: metricAggFilter
}
])).all[0];
const bucketAggFilter = [];
const bucketAggSchema = (new Schemas([
{
group: 'none',
title: 'Bucket Agg',
name: 'bucketAgg',
aggFilter: bucketAggFilter
}
])).all[0];
return {
subtype: 'Sibling Pipeline Aggregations',
params: function () {
return [
{
name: 'customBucket',
type: AggConfig,
default: null,
serialize: function (customMetric) {
return customMetric.toJSON();
},
deserialize: function (state, agg) {
return this.makeAgg(agg, state);
},
makeAgg: function (agg, state) {
state = state || { type: 'date_histogram' };
state.schema = bucketAggSchema;
const orderAgg = new AggConfig(agg.vis, state);
orderAgg.id = agg.id + '-bucket';
return orderAgg;
},
editor: metricAggTemplate,
controller: siblingPipelineAggController('customBucket'),
write: _.noop
},
{
name: 'customMetric',
type: AggConfig,
default: null,
serialize: function (customMetric) {
return customMetric.toJSON();
},
deserialize: function (state, agg) {
return this.makeAgg(agg, state);
},
makeAgg: function (agg, state) {
state = state || { type: 'count' };
state.schema = metricAggSchema;
const orderAgg = new AggConfig(agg.vis, state);
orderAgg.id = agg.id + '-metric';
return orderAgg;
},
editor: metricAggTemplate,
controller: siblingPipelineAggController('customMetric'),
write: siblingPipelineAggWritter
}
];
}
};
};
export default SiblingPipelineAggHelperProvider;

View file

@ -0,0 +1,19 @@
const siblingPipelineAggWritter = function (agg, output) {
if (!agg.params.customMetric) return;
const metricAgg = agg.params.customMetric;
const bucketAgg = agg.params.customBucket;
// if a bucket is selected, we must add this agg as a sibling to it, and add a metric to that bucket (or select one of its)
if (metricAgg.type.name !== 'count') {
bucketAgg.subAggs = (output.subAggs || []).concat(metricAgg);
output.params.buckets_path = `${bucketAgg.id}>${metricAgg.id}`;
} else {
output.params.buckets_path = bucketAgg.id + '>_count';
}
output.parentAggs = (output.parentAggs || []).concat(bucketAgg);
};
export { siblingPipelineAggWritter };

View file

@ -17,6 +17,7 @@ export default function MetricAggTypeProvider(Private) {
}, this);
}
MetricAggType.prototype.subtype = 'Metric Aggregations';
/**
* Read the values for this metric from the
* @param {[type]} bucket [description]

View file

@ -9,6 +9,7 @@ export default function AggTypeMetricMovingAvgProvider(Private) {
return new MetricAggType({
name: 'moving_avg',
title: 'Moving Avg',
subtype: parentPipelineAggHelper.subtype,
makeLabel: agg => makeNestedLabel(agg, 'moving avg'),
params: [
...parentPipelineAggHelper.params()

View file

@ -9,6 +9,7 @@ export default function AggTypeMetricSerialDiffProvider(Private) {
return new MetricAggType({
name: 'serial_diff',
title: 'Serial Diff',
subtype: parentPipelineAggHelper.subtype,
makeLabel: agg => makeNestedLabel(agg, 'serial diff'),
params: [
...parentPipelineAggHelper.params()

View file

@ -210,6 +210,7 @@ export default function AggConfigFactory(Private, fieldTypeFilter) {
configDsl[this.type.dslName || this.type.name] = output.params;
// if the config requires subAggs, write them to the dsl as well
if (this.subAggs && !output.subAggs) output.subAggs = this.subAggs;
if (output.subAggs) {
const subDslLvl = configDsl.aggs || (configDsl.aggs = {});
output.subAggs.forEach(function nestAdhocSubAggs(subAggConfig) {

View file

@ -4,7 +4,7 @@ export default function ColumnHandler(Private) {
const createSerieFromParams = (cfg, seri) => {
const matchingSeriParams = cfg.seriesParams ? cfg.seriesParams.find(seriConfig => {
return seri.label === seriConfig.data.label;
return seri.label.endsWith(seriConfig.data.label);
}) : null;