Merge branch 'moving_avg_es_support'
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
# 2.6.0 (2015-12-04)
|
||||
|
||||
### New Features
|
||||
* **Elasticsearch**: Support for pipeline aggregations Moving average and derivative, closes [#3451](https://github.com/grafana/grafana/issues/3451)
|
||||
|
||||
### Bug Fixes
|
||||
* **metric editors**: Fix for clicking typeahead auto dropdown option, fixes [#3428](https://github.com/grafana/grafana/issues/3428)
|
||||
* **influxdb**: Fixed issue showing Group By label only on first query, fixes [#3453](https://github.com/grafana/grafana/issues/3453)
|
||||
|
||||
@@ -15,6 +15,9 @@ function (_, queryDef) {
|
||||
|
||||
for (y = 0; y < target.metrics.length; y++) {
|
||||
metric = target.metrics[y];
|
||||
if (metric.hide) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch(metric.type) {
|
||||
case 'count': {
|
||||
@@ -76,8 +79,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 +200,14 @@ function (_, queryDef) {
|
||||
});
|
||||
}
|
||||
|
||||
if (series.field) {
|
||||
if (series.field && queryDef.isPipelineAgg(series.metric)) {
|
||||
var appliedAgg = _.findWhere(target.metrics, { id: series.field });
|
||||
if (appliedAgg) {
|
||||
metricName += ' ' + queryDef.describeMetric(appliedAgg);
|
||||
} else {
|
||||
metricName = 'Unset';
|
||||
}
|
||||
} else if (series.field) {
|
||||
metricName += ' ' + series.field;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,14 +13,21 @@ function (angular, _, queryDef) {
|
||||
|
||||
$scope.metricAggTypes = queryDef.metricAggTypes;
|
||||
$scope.extendedStats = queryDef.extendedStats;
|
||||
$scope.pipelineAggOptions = [];
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.agg = metricAggs[$scope.index];
|
||||
$scope.validateModel();
|
||||
$scope.updatePipelineAggOptions();
|
||||
};
|
||||
|
||||
$scope.updatePipelineAggOptions = function() {
|
||||
$scope.pipelineAggOptions = queryDef.getPipelineAggOptions($scope.target);
|
||||
};
|
||||
|
||||
$rootScope.onAppEvent('elastic-query-updated', function() {
|
||||
$scope.index = _.indexOf(metricAggs, $scope.agg);
|
||||
$scope.updatePipelineAggOptions();
|
||||
$scope.validateModel();
|
||||
}, $scope);
|
||||
|
||||
@@ -30,7 +37,18 @@ function (angular, _, queryDef) {
|
||||
$scope.settingsLinkText = '';
|
||||
$scope.aggDef = _.findWhere($scope.metricAggTypes, {value: $scope.agg.type});
|
||||
|
||||
if (!$scope.agg.field) {
|
||||
if (queryDef.isPipelineAgg($scope.agg.type)) {
|
||||
$scope.agg.pipelineAgg = $scope.agg.pipelineAgg || 'select metric';
|
||||
$scope.agg.field = $scope.agg.pipelineAgg;
|
||||
|
||||
var pipelineOptions = queryDef.getPipelineOptions($scope.agg);
|
||||
if (pipelineOptions.length > 0) {
|
||||
_.each(pipelineOptions, function(opt) {
|
||||
$scope.agg.settings[opt.text] = $scope.agg.settings[opt.text] || opt.default;
|
||||
});
|
||||
$scope.settingsLinkText = 'Options';
|
||||
}
|
||||
} else if (!$scope.agg.field) {
|
||||
$scope.agg.field = 'select field';
|
||||
}
|
||||
|
||||
@@ -65,12 +83,18 @@ function (angular, _, queryDef) {
|
||||
|
||||
$scope.toggleOptions = function() {
|
||||
$scope.showOptions = !$scope.showOptions;
|
||||
$scope.updatePipelineAggOptions();
|
||||
};
|
||||
|
||||
$scope.onChangeInternal = function() {
|
||||
$scope.onChange();
|
||||
};
|
||||
|
||||
$scope.onTypeChange = function() {
|
||||
$scope.agg.settings = {};
|
||||
$scope.agg.meta = {};
|
||||
$scope.showOptions = false;
|
||||
$scope.updatePipelineAggOptions();
|
||||
$scope.onChange();
|
||||
};
|
||||
|
||||
@@ -94,6 +118,14 @@ function (angular, _, queryDef) {
|
||||
$scope.onChange();
|
||||
};
|
||||
|
||||
$scope.toggleShowMetric = function() {
|
||||
$scope.agg.hide = !$scope.agg.hide;
|
||||
if (!$scope.agg.hide) {
|
||||
delete $scope.agg.hide;
|
||||
}
|
||||
$scope.onChange();
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
|
||||
});
|
||||
|
||||
@@ -1,16 +1,27 @@
|
||||
<div class="tight-form">
|
||||
<div class="tight-form" ng-class="{'tight-form-disabled': agg.hide}">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item query-keyword tight-form-align" style="width: 75px;">
|
||||
Metric
|
||||
<a ng-click="toggleShowMetric()" bs-tooltip="Click to toggle show/hide metric">
|
||||
<i class="fa fa-eye" ng-hide="agg.hide"></i>
|
||||
<i class="fa fa-eye-slash" ng-show="agg.hide"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<metric-segment-model property="agg.type" options="metricAggTypes" on-change="onTypeChange()" custom="false" css-class="tight-form-item-large"></metric-segment-model>
|
||||
</li>
|
||||
<li ng-if="aggDef.requiresField">
|
||||
<metric-segment-model property="agg.field" get-options="getFieldsInternal()" on-change="onChange()" css-class="tight-form-item-xxlarge"></metric-segment>
|
||||
<metric-segment-model property="agg.field" get-options="getFieldsInternal()" on-change="onChange()" css-class="tight-form-item-xxlarge"></metric-segment-model>
|
||||
</li>
|
||||
<li ng-if="aggDef.isPipelineAgg">
|
||||
<metric-segment-model property="agg.pipelineAgg" options="pipelineAggOptions" on-change="onChangeInternal()" custom="false" css-class="tight-form-item-xxlarge"></metric-segment-model>
|
||||
</li>
|
||||
<li class="tight-form-item last" ng-if="settingsLinkText">
|
||||
<a ng-click="toggleOptions()">{{settingsLinkText}}</a>
|
||||
<a ng-click="toggleOptions()">
|
||||
<i class="fa fa-caret-down" ng-show="showOptions"></i>
|
||||
<i class="fa fa-caret-right" ng-hide="showOptions"></i>
|
||||
{{settingsLinkText}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -26,7 +37,29 @@
|
||||
</div>
|
||||
|
||||
<div class="tight-form" ng-if="showOptions">
|
||||
<div class="tight-form-inner-box">
|
||||
<div class="tight-form-inner-box tight-form-container">
|
||||
<div class="tight-form" ng-if="agg.type === 'moving_avg'">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="width: 75px;">
|
||||
Window
|
||||
</li>
|
||||
<li>
|
||||
<input type="number" class="input-medium tight-form-input last" ng-model="agg.settings.window" ng-blur="onChangeInternal()" spellcheck='false'>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="tight-form" ng-if="agg.type === 'moving_avg'">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="width: 75px;">
|
||||
Model
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" class="input-medium tight-form-input last" ng-change="onChangeInternal()" ng-model="agg.settings.model" blur="onChange()" spellcheck='false'>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="tight-form last" ng-if="agg.type === 'percentiles'">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
define([
|
||||
'./query_def'
|
||||
],
|
||||
function () {
|
||||
function (queryDef) {
|
||||
'use strict';
|
||||
|
||||
function ElasticQueryBuilder(options) {
|
||||
@@ -167,14 +168,25 @@ function () {
|
||||
continue;
|
||||
}
|
||||
|
||||
var metricAgg = {field: metric.field};
|
||||
var aggField = {};
|
||||
var metricAgg = null;
|
||||
|
||||
if (queryDef.isPipelineAgg(metric.type)) {
|
||||
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 +229,4 @@ function () {
|
||||
};
|
||||
|
||||
return ElasticQueryBuilder;
|
||||
|
||||
});
|
||||
|
||||
@@ -13,6 +13,8 @@ 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, isPipelineAgg: true },
|
||||
{text: "Derivative", value: 'derivative', requiresField: false, isPipelineAgg: true },
|
||||
{text: "Unique Count", value: "cardinality", requiresField: true},
|
||||
{text: "Raw Document", value: "raw_document", requiresField: false}
|
||||
],
|
||||
@@ -66,6 +68,43 @@ function (_) {
|
||||
{text: '1d', value: '1d'},
|
||||
],
|
||||
|
||||
pipelineOptions: {
|
||||
'moving_avg' : [
|
||||
{text: 'window', default: 5},
|
||||
{text: 'model', default: 'simple'}
|
||||
],
|
||||
'derivative': []
|
||||
},
|
||||
|
||||
getPipelineOptions: function(metric) {
|
||||
if (!this.isPipelineAgg(metric.type)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.pipelineOptions[metric.type];
|
||||
},
|
||||
|
||||
isPipelineAgg: function(metricType) {
|
||||
if (metricType) {
|
||||
var po = this.pipelineOptions[metricType];
|
||||
return po !== null && po !== undefined;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
getPipelineAggOptions: function(targets) {
|
||||
var self = this;
|
||||
var result = [];
|
||||
_.each(targets.metrics, function(metric) {
|
||||
if (!self.isPipelineAgg(metric.type)) {
|
||||
result.push({text: self.describeMetric(metric), value: metric.id });
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
getOrderByOptions: function(target) {
|
||||
var self = this;
|
||||
var metricRefs = [];
|
||||
|
||||
@@ -155,4 +155,89 @@ 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',
|
||||
pipelineAgg: '3'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
type: 'moving_avg',
|
||||
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);
|
||||
});
|
||||
|
||||
it('with derivative', function() {
|
||||
var query = builder.build({
|
||||
metrics: [
|
||||
{
|
||||
id: '3',
|
||||
type: 'sum',
|
||||
field: '@value'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'derivative',
|
||||
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"].derivative).not.to.be(undefined);
|
||||
expect(firstLevel.aggs["2"].derivative.buckets_path).to.be("3");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
///<amd-dependency path="../query_def" name="QueryDef" />
|
||||
///<amd-dependency path="test/specs/helpers" name="helpers" />
|
||||
|
||||
import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common';
|
||||
|
||||
declare var helpers: any;
|
||||
declare var QueryDef: any;
|
||||
|
||||
describe('ElasticQueryDef', function() {
|
||||
|
||||
describe('getPipelineAggOptions', function() {
|
||||
describe('with zero targets', function() {
|
||||
var response = QueryDef.getPipelineAggOptions([]);
|
||||
|
||||
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.getPipelineAggOptions(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.getPipelineAggOptions(targets);
|
||||
|
||||
it('should return one', function() {
|
||||
expect(response.length).to.be(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with derivatives targets', function() {
|
||||
var targets = {
|
||||
metrics: [
|
||||
{ type: 'derivative', field: '@value' }
|
||||
]
|
||||
};
|
||||
|
||||
var response = QueryDef.getPipelineAggOptions(targets);
|
||||
|
||||
it('should return zero', function() {
|
||||
expect(response.length).to.be(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('isPipelineMetric', function() {
|
||||
describe('moving_avg', function() {
|
||||
var result = QueryDef.isPipelineAgg('moving_avg');
|
||||
|
||||
it('is pipe line metric', function() {
|
||||
expect(result).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('count', function() {
|
||||
var result = QueryDef.isPipelineAgg('count');
|
||||
|
||||
it('is not pipe line metric', function() {
|
||||
expect(result).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user