diff --git a/src/core_plugins/kibana/public/visualize/editor/agg_select.html b/src/core_plugins/kibana/public/visualize/editor/agg_select.html
index 4059ef4884e86..854996b965822 100644
--- a/src/core_plugins/kibana/public/visualize/editor/agg_select.html
+++ b/src/core_plugins/kibana/public/visualize/editor/agg_select.html
@@ -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">
diff --git a/src/ui/public/agg_types/__tests__/metrics/sibling_pipeline.js b/src/ui/public/agg_types/__tests__/metrics/sibling_pipeline.js
new file mode 100644
index 0000000000000..90e756d5dcfd8
--- /dev/null
+++ b/src/ui/public/agg_types/__tests__/metrics/sibling_pipeline.js
@@ -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');
+ });
+
+ });
+ });
+
+});
diff --git a/src/ui/public/agg_types/buckets/terms.js b/src/ui/public/agg_types/buckets/terms.js
index 1ff01e548915e..4383c8c7e8465 100644
--- a/src/ui/public/agg_types/buckets/terms.js
+++ b/src/ui/public/agg_types/buckets/terms.js
@@ -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',
diff --git a/src/ui/public/agg_types/controls/sub_metric.html b/src/ui/public/agg_types/controls/sub_metric.html
new file mode 100644
index 0000000000000..88011451676be
--- /dev/null
+++ b/src/ui/public/agg_types/controls/sub_metric.html
@@ -0,0 +1,14 @@
+
diff --git a/src/ui/public/agg_types/index.js b/src/ui/public/agg_types/index.js
index 5f735860e0cf7..da5a5a6c94521 100644
--- a/src/ui/public/agg_types/index.js
+++ b/src/ui/public/agg_types/index.js
@@ -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),
diff --git a/src/ui/public/agg_types/metrics/bucket_avg.js b/src/ui/public/agg_types/metrics/bucket_avg.js
new file mode 100644
index 0000000000000..dcfc7282248e8
--- /dev/null
+++ b/src/ui/public/agg_types/metrics/bucket_avg.js
@@ -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()
+ ]
+ });
+}
diff --git a/src/ui/public/agg_types/metrics/bucket_max.js b/src/ui/public/agg_types/metrics/bucket_max.js
new file mode 100644
index 0000000000000..82a6b834de719
--- /dev/null
+++ b/src/ui/public/agg_types/metrics/bucket_max.js
@@ -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()
+ ]
+ });
+}
diff --git a/src/ui/public/agg_types/metrics/bucket_min.js b/src/ui/public/agg_types/metrics/bucket_min.js
new file mode 100644
index 0000000000000..588c308b100a7
--- /dev/null
+++ b/src/ui/public/agg_types/metrics/bucket_min.js
@@ -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()
+ ]
+ });
+}
diff --git a/src/ui/public/agg_types/metrics/bucket_sum.js b/src/ui/public/agg_types/metrics/bucket_sum.js
new file mode 100644
index 0000000000000..a4c3c5f265228
--- /dev/null
+++ b/src/ui/public/agg_types/metrics/bucket_sum.js
@@ -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()
+ ]
+ });
+}
diff --git a/src/ui/public/agg_types/metrics/cumulative_sum.js b/src/ui/public/agg_types/metrics/cumulative_sum.js
index 8e7fc93ce9a31..d9856704acffc 100644
--- a/src/ui/public/agg_types/metrics/cumulative_sum.js
+++ b/src/ui/public/agg_types/metrics/cumulative_sum.js
@@ -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()
diff --git a/src/ui/public/agg_types/metrics/derivative.js b/src/ui/public/agg_types/metrics/derivative.js
index 01f075cc9d03b..cc17b871a9030 100644
--- a/src/ui/public/agg_types/metrics/derivative.js
+++ b/src/ui/public/agg_types/metrics/derivative.js
@@ -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()
diff --git a/src/ui/public/agg_types/metrics/lib/parent_pipeline_agg_helper.js b/src/ui/public/agg_types/metrics/lib/parent_pipeline_agg_helper.js
index b55209b3ad08e..6e1b2ce62df4c 100644
--- a/src/ui/public/agg_types/metrics/lib/parent_pipeline_agg_helper.js
+++ b/src/ui/public/agg_types/metrics/lib/parent_pipeline_agg_helper.js
@@ -22,6 +22,7 @@ const ParentPipelineAggHelperProvider = function (Private) {
])).all[0];
return {
+ subtype: 'Parent Pipeline Aggregations',
params: function () {
return [
{
diff --git a/src/ui/public/agg_types/metrics/lib/sibling_pipeline_agg_controller.js b/src/ui/public/agg_types/metrics/lib/sibling_pipeline_agg_controller.js
new file mode 100644
index 0000000000000..2a16b232c3a47
--- /dev/null
+++ b/src/ui/public/agg_types/metrics/lib/sibling_pipeline_agg_controller.js
@@ -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 };
diff --git a/src/ui/public/agg_types/metrics/lib/sibling_pipeline_agg_helper.js b/src/ui/public/agg_types/metrics/lib/sibling_pipeline_agg_helper.js
new file mode 100644
index 0000000000000..372d95feae499
--- /dev/null
+++ b/src/ui/public/agg_types/metrics/lib/sibling_pipeline_agg_helper.js
@@ -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;
diff --git a/src/ui/public/agg_types/metrics/lib/sibling_pipeline_agg_writter.js b/src/ui/public/agg_types/metrics/lib/sibling_pipeline_agg_writter.js
new file mode 100644
index 0000000000000..2b7b51f64d059
--- /dev/null
+++ b/src/ui/public/agg_types/metrics/lib/sibling_pipeline_agg_writter.js
@@ -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 };
diff --git a/src/ui/public/agg_types/metrics/metric_agg_type.js b/src/ui/public/agg_types/metrics/metric_agg_type.js
index 12f712cce4f09..e0727243209c9 100644
--- a/src/ui/public/agg_types/metrics/metric_agg_type.js
+++ b/src/ui/public/agg_types/metrics/metric_agg_type.js
@@ -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]
diff --git a/src/ui/public/agg_types/metrics/moving_avg.js b/src/ui/public/agg_types/metrics/moving_avg.js
index 520d4cc25e233..a9ea5e6d8e020 100644
--- a/src/ui/public/agg_types/metrics/moving_avg.js
+++ b/src/ui/public/agg_types/metrics/moving_avg.js
@@ -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()
diff --git a/src/ui/public/agg_types/metrics/serial_diff.js b/src/ui/public/agg_types/metrics/serial_diff.js
index 014c50ec2c1ea..c67363b6e5807 100644
--- a/src/ui/public/agg_types/metrics/serial_diff.js
+++ b/src/ui/public/agg_types/metrics/serial_diff.js
@@ -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()
diff --git a/src/ui/public/vis/agg_config.js b/src/ui/public/vis/agg_config.js
index 2be2834bd078e..9497366c7fe12 100644
--- a/src/ui/public/vis/agg_config.js
+++ b/src/ui/public/vis/agg_config.js
@@ -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) {
diff --git a/src/ui/public/vislib/lib/types/point_series.js b/src/ui/public/vislib/lib/types/point_series.js
index 3e6999c443735..624608637592a 100644
--- a/src/ui/public/vislib/lib/types/point_series.js
+++ b/src/ui/public/vislib/lib/types/point_series.js
@@ -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;