diff --git a/public/app/plugins/datasource/elasticsearch/elastic_response.js b/public/app/plugins/datasource/elasticsearch/elastic_response.js index 73050ce7f2b..fd53873aa8d 100644 --- a/public/app/plugins/datasource/elasticsearch/elastic_response.js +++ b/public/app/plugins/datasource/elasticsearch/elastic_response.js @@ -76,8 +76,12 @@ function (_, queryDef) { newSeries = { datapoints: [], metric: metric.type, field: metric.field, props: props}; for (i = 0; i < esAgg.buckets.length; i++) { bucket = esAgg.buckets[i]; - value = bucket[metric.id].value; - newSeries.datapoints.push([value, bucket.key]); + + value = bucket[metric.id]; + if (value !== undefined) { + newSeries.datapoints.push([value.value, bucket.key]); + } + } seriesList.push(newSeries); break; @@ -193,7 +197,14 @@ function (_, queryDef) { }); } - if (series.field) { + if (series.field && series.metric === 'moving_avg') { + var appliedAgg = _.findWhere(target.metrics, { id: series.field }); + if (appliedAgg) { + metricName += ' ' + queryDef.describeMetric(appliedAgg); + } else { + metricName = 'Unset'; + } + } else if (series.field) { metricName += ' ' + series.field; } diff --git a/public/app/plugins/datasource/elasticsearch/metric_agg.js b/public/app/plugins/datasource/elasticsearch/metric_agg.js index c4e04dad325..1d6f82a292f 100644 --- a/public/app/plugins/datasource/elasticsearch/metric_agg.js +++ b/public/app/plugins/datasource/elasticsearch/metric_agg.js @@ -13,14 +13,21 @@ function (angular, _, queryDef) { $scope.metricAggTypes = queryDef.metricAggTypes; $scope.extendedStats = queryDef.extendedStats; + $scope.mavgOptions = []; $scope.init = function() { $scope.agg = metricAggs[$scope.index]; $scope.validateModel(); + $scope.updateMavgOptions(); + }; + + $scope.updateMavgOptions = function() { + $scope.mavgOptions = queryDef.getMovingAverageOptions($scope.target); }; $rootScope.onAppEvent('elastic-query-updated', function() { $scope.index = _.indexOf(metricAggs, $scope.agg); + $scope.updateMavgOptions(); $scope.validateModel(); }, $scope); @@ -35,6 +42,12 @@ function (angular, _, queryDef) { } switch($scope.agg.type) { + case 'moving_avg': { + $scope.agg.pipelineAgg = $scope.agg.pipelineAgg || 'Metric to apply moving average'; + $scope.settingsLinkText = 'Moving average options'; + $scope.agg.field = $scope.agg.pipelineAgg; + break; + } case 'percentiles': { $scope.agg.settings.percents = $scope.agg.settings.percents || [25,50,75,95,99]; $scope.settingsLinkText = 'values: ' + $scope.agg.settings.percents.join(','); @@ -65,6 +78,11 @@ function (angular, _, queryDef) { $scope.toggleOptions = function() { $scope.showOptions = !$scope.showOptions; + $scope.updateMavgOptions(); + }; + + $scope.onChangeInternal = function() { + $scope.onChange(); }; $scope.onTypeChange = function() { diff --git a/public/app/plugins/datasource/elasticsearch/partials/metricAgg.html b/public/app/plugins/datasource/elasticsearch/partials/metricAgg.html index 65030af5655..7c21e64721e 100644 --- a/public/app/plugins/datasource/elasticsearch/partials/metricAgg.html +++ b/public/app/plugins/datasource/elasticsearch/partials/metricAgg.html @@ -7,7 +7,7 @@
  • - +
  • {{settingsLinkText}} @@ -27,6 +27,17 @@
    +
    +
      +
    • + Based on +
    • +
    • + +
    • +
    +
    +
    • diff --git a/public/app/plugins/datasource/elasticsearch/query_builder.js b/public/app/plugins/datasource/elasticsearch/query_builder.js index d736966285c..64128943f40 100644 --- a/public/app/plugins/datasource/elasticsearch/query_builder.js +++ b/public/app/plugins/datasource/elasticsearch/query_builder.js @@ -167,14 +167,25 @@ function () { continue; } - var metricAgg = {field: metric.field}; + var aggField = {}; + var metricAgg = null; + + if (metric.type === 'moving_avg') { + if (metric.pipelineAgg && /^\d*$/.test(metric.pipelineAgg)) { + metricAgg = { buckets_path: metric.pipelineAgg }; + } else { + continue; + } + } else { + metricAgg = {field: metric.field}; + } + for (var prop in metric.settings) { if (metric.settings.hasOwnProperty(prop) && metric.settings[prop] !== null) { metricAgg[prop] = metric.settings[prop]; } } - var aggField = {}; aggField[metric.type] = metricAgg; nestedAggs.aggs[metric.id] = aggField; } @@ -217,5 +228,4 @@ function () { }; return ElasticQueryBuilder; - }); diff --git a/public/app/plugins/datasource/elasticsearch/query_def.js b/public/app/plugins/datasource/elasticsearch/query_def.js index baab2378e9f..6fed5580576 100644 --- a/public/app/plugins/datasource/elasticsearch/query_def.js +++ b/public/app/plugins/datasource/elasticsearch/query_def.js @@ -13,6 +13,7 @@ function (_) { {text: "Min", value: 'min', requiresField: true}, {text: "Extended Stats", value: 'extended_stats', requiresField: true}, {text: "Percentiles", value: 'percentiles', requiresField: true}, + {text: "Moving Average", value: 'moving_avg', requiresField: false }, {text: "Unique Count", value: "cardinality", requiresField: true}, {text: "Raw Document", value: "raw_document", requiresField: false} ], @@ -66,6 +67,28 @@ function (_) { {text: '1d', value: '1d'}, ], + pipelineAggs: ['moving_avg'], + + isPipelineAgg: function(metric) { + if (metric.type) { + return this.pipelineAggs.indexOf(metric.type) > -1; + } + + return false; + }, + + getMovingAverageOptions: function(targets) { + var self = this; + var result = []; + _.each(targets.metrics, function(metric) { + if (metric.type !== 'moving_avg') { + result.push({text: self.describeMetric(metric), value: metric.id }); + } + }); + + return result; + }, + getOrderByOptions: function(target) { var self = this; var metricRefs = []; diff --git a/public/app/plugins/datasource/elasticsearch/specs/query_builder_specs.ts b/public/app/plugins/datasource/elasticsearch/specs/query_builder_specs.ts index 3cfdf61620e..91cdba7248e 100644 --- a/public/app/plugins/datasource/elasticsearch/specs/query_builder_specs.ts +++ b/public/app/plugins/datasource/elasticsearch/specs/query_builder_specs.ts @@ -155,4 +155,64 @@ describe('ElasticQueryBuilder', function() { expect(query.size).to.be(500); }); + it('with moving average', function() { + var query = builder.build({ + metrics: [ + { + id: '3', + type: 'sum', + field: '@value' + }, + { + id: '2', + type: 'moving_avg', + field: '3', + pipelineAgg: '3' + } + ], + bucketAggs: [ + {type: 'date_histogram', field: '@timestamp', id: '3'} + ], + }); + + var firstLevel = query.aggs["3"]; + + expect(firstLevel.aggs["2"]).not.to.be(undefined); + expect(firstLevel.aggs["2"].moving_avg).not.to.be(undefined); + expect(firstLevel.aggs["2"].moving_avg.buckets_path).to.be("3"); + }); + + it('with broken moving average', function() { + var query = builder.build({ + metrics: [ + { + id: '3', + type: 'sum', + field: '@value' + }, + { + id: '2', + type: 'moving_avg', + field: '3', + pipelineAgg: '3' + }, + { + id: '4', + type: 'moving_avg', + field: '3', + pipelineAgg: 'Metric to apply moving average' + } + ], + bucketAggs: [ + { type: 'date_histogram', field: '@timestamp', id: '3' } + ], + }); + + var firstLevel = query.aggs["3"]; + + expect(firstLevel.aggs["2"]).not.to.be(undefined); + expect(firstLevel.aggs["2"].moving_avg).not.to.be(undefined); + expect(firstLevel.aggs["2"].moving_avg.buckets_path).to.be("3"); + expect(firstLevel.aggs["4"]).to.be(undefined); + }); }); diff --git a/public/app/plugins/datasource/elasticsearch/specs/query_def_specs.ts b/public/app/plugins/datasource/elasticsearch/specs/query_def_specs.ts new file mode 100644 index 00000000000..cf375430ae1 --- /dev/null +++ b/public/app/plugins/datasource/elasticsearch/specs/query_def_specs.ts @@ -0,0 +1,68 @@ +/// +/// + +import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; + +declare var helpers: any; +declare var QueryDef: any; + +describe('ElasticQueryDef', function() { + + describe('getMovingAverageOptions', function() { + describe('with zero targets', function() { + var response = QueryDef.getMovingAverageOptions([]); + + it('should return zero', function() { + expect(response.length).to.be(0); + }); + }); + + describe('with count and sum targets', function() { + var targets = { + metrics: [ + { type: 'count', field: '@value' }, + { type: 'sum', field: '@value' } + ] + }; + + var response = QueryDef.getMovingAverageOptions(targets); + + it('should return zero', function() { + expect(response.length).to.be(2); + }); + }); + + describe('with count and moving average targets', function() { + var targets = { + metrics: [ + { type: 'count', field: '@value' }, + { type: 'moving_avg', field: '@value' } + ] + }; + + var response = QueryDef.getMovingAverageOptions(targets); + + it('should return zero', function() { + expect(response.length).to.be(1); + }); + }); + }); + + describe('isPipelineMetric', function() { + describe('moving_avg', function() { + var result = QueryDef.isPipelineAgg({ type: 'moving_avg' }); + + it('is pipe line metric', function() { + expect(result).to.be(true); + }); + }); + + describe('count', function() { + var result = QueryDef.isPipelineAgg({ type: 'count' }); + + it('is not pipe line metric', function() { + expect(result).to.be(false); + }); + }); + }); +});