mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
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:
parent
e32656777e
commit
841a3d204f
20 changed files with 351 additions and 4 deletions
|
@ -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>
|
||||
|
|
110
src/ui/public/agg_types/__tests__/metrics/sibling_pipeline.js
Normal file
110
src/ui/public/agg_types/__tests__/metrics/sibling_pipeline.js
Normal 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');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -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',
|
||||
|
|
14
src/ui/public/agg_types/controls/sub_metric.html
Normal file
14
src/ui/public/agg_types/controls/sub_metric.html
Normal 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>
|
|
@ -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),
|
||||
|
|
18
src/ui/public/agg_types/metrics/bucket_avg.js
Normal file
18
src/ui/public/agg_types/metrics/bucket_avg.js
Normal 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()
|
||||
]
|
||||
});
|
||||
}
|
18
src/ui/public/agg_types/metrics/bucket_max.js
Normal file
18
src/ui/public/agg_types/metrics/bucket_max.js
Normal 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()
|
||||
]
|
||||
});
|
||||
}
|
18
src/ui/public/agg_types/metrics/bucket_min.js
Normal file
18
src/ui/public/agg_types/metrics/bucket_min.js
Normal 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()
|
||||
]
|
||||
});
|
||||
}
|
18
src/ui/public/agg_types/metrics/bucket_sum.js
Normal file
18
src/ui/public/agg_types/metrics/bucket_sum.js
Normal 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()
|
||||
]
|
||||
});
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -22,6 +22,7 @@ const ParentPipelineAggHelperProvider = function (Private) {
|
|||
])).all[0];
|
||||
|
||||
return {
|
||||
subtype: 'Parent Pipeline Aggregations',
|
||||
params: function () {
|
||||
return [
|
||||
{
|
||||
|
|
|
@ -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 };
|
|
@ -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;
|
|
@ -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 };
|
|
@ -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]
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue