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;