mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Working on new query editor for influxdb 0.9, looking good! #1525
This commit is contained in:
parent
11c8e80ea9
commit
5ca8d590bd
@ -14,7 +14,8 @@ function (angular, app, _, $) {
|
||||
' class="tight-form-clear-input input-medium"' +
|
||||
' spellcheck="false" style="display:none"></input>';
|
||||
|
||||
var buttonTemplate = '<a class="tight-form-item" tabindex="1" focus-me="segment.focus" ng-bind-html="segment.html"></a>';
|
||||
var buttonTemplate = '<a class="tight-form-item" ng-class="segment.cssClass" ' +
|
||||
'tabindex="1" focus-me="segment.focus" ng-bind-html="segment.html"></a>';
|
||||
|
||||
return {
|
||||
scope: {
|
||||
|
@ -83,15 +83,16 @@
|
||||
<li class="tight-form-item query-keyword">
|
||||
WHERE
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" class="input-medium tight-form-input" ng-model="target.condition"
|
||||
bs-tooltip="'Add a where clause'" data-placement="right" spellcheck='false' placeholder="column ~= value" ng-blur="get_data()">
|
||||
<li ng-repeat="segment in tagSegments">
|
||||
<metric-segment segment="segment" get-alt-segments="getTagsOrValues(segment, $index)" on-value-changed="tagSegmentUpdated(segment, $index)"></metric-segment>
|
||||
</li>
|
||||
<li class="tight-form-item">
|
||||
<span class="query-keyword">GROUP BY</span>
|
||||
time($interval), <i class="fa fa-plus"></i>
|
||||
time($interval)
|
||||
</li>
|
||||
<li ng-repeat="segment in groupBySegments">
|
||||
<metric-segment segment="segment" get-alt-segments="getTagsOrValues(segment, 0)" on-value-changed="groupByTagUpdated(segment, $index)"></metric-segment>
|
||||
</li>
|
||||
|
||||
<li class="dropdown">
|
||||
<a class="tight-form-item pointer" data-toggle="dropdown" bs-tooltip="'Insert missing values, important when stacking'" data-placement="right">
|
||||
<span ng-show="target.fill">
|
||||
@ -106,7 +107,7 @@
|
||||
<li><a ng-click="target.fill = 'null'">fill (null)</a></li>
|
||||
<li><a ng-click="target.fill = '0'">fill (0)</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
|
@ -33,8 +33,8 @@ function (_) {
|
||||
|
||||
query += aggregationFunc + '(value)';
|
||||
query += ' FROM ' + measurement + ' WHERE $timeFilter';
|
||||
query += _.map(target.tags, function(value, key) {
|
||||
return ' AND ' + key + '=' + "'" + value + "'";
|
||||
query += _.map(target.tags, function(tag) {
|
||||
return ' AND ' + tag.key + '=' + "'" + tag.value + "'";
|
||||
}).join('');
|
||||
|
||||
query += ' GROUP BY time($interval)';
|
||||
|
@ -7,13 +7,11 @@ function (angular, _) {
|
||||
|
||||
var module = angular.module('grafana.controllers');
|
||||
|
||||
module.controller('InfluxQueryCtrl', function($scope, $timeout, $sce, templateSrv) {
|
||||
module.controller('InfluxQueryCtrl', function($scope, $timeout, $sce, templateSrv, $q) {
|
||||
|
||||
$scope.functionList = [
|
||||
'count', 'mean', 'sum', 'min',
|
||||
'max', 'mode', 'distinct', 'median',
|
||||
'derivative', 'stddev', 'first', 'last',
|
||||
'difference'
|
||||
'count', 'mean', 'sum', 'min', 'max', 'mode', 'distinct', 'median',
|
||||
'derivative', 'stddev', 'first', 'last', 'difference'
|
||||
];
|
||||
|
||||
$scope.functionMenu = _.map($scope.functionList, function(func) {
|
||||
@ -23,12 +21,41 @@ function (angular, _) {
|
||||
$scope.init = function() {
|
||||
var target = $scope.target;
|
||||
target.function = target.function || 'mean';
|
||||
target.tags = target.tags || [];
|
||||
target.groupByTags = target.groupByTags || [];
|
||||
|
||||
if (!target.measurement) {
|
||||
$scope.measurementSegment = MetricSegment.newSelectMeasurement();
|
||||
} else {
|
||||
$scope.measurementSegment = new MetricSegment(target.measurement);
|
||||
}
|
||||
|
||||
$scope.tagSegments = [];
|
||||
_.each(target.tags, function(tag) {
|
||||
if (tag.condition) {
|
||||
$scope.tagSegments.push(MetricSegment.newCondition(tag.condition));
|
||||
}
|
||||
$scope.tagSegments.push(new MetricSegment({value: tag.key, type: 'key' }));
|
||||
$scope.tagSegments.push(new MetricSegment({fake: true, value: "="}));
|
||||
$scope.tagSegments.push(new MetricSegment({value: tag.value, type: 'value'}));
|
||||
});
|
||||
|
||||
if ($scope.tagSegments.length % 3 === 0) {
|
||||
$scope.tagSegments.push(MetricSegment.newPlusButton());
|
||||
}
|
||||
|
||||
$scope.groupBySegments = [];
|
||||
_.each(target.groupByTags, function(tag) {
|
||||
$scope.groupBySegments.push(new MetricSegment(tag));
|
||||
});
|
||||
|
||||
$scope.groupBySegments.push(MetricSegment.newPlusButton());
|
||||
};
|
||||
|
||||
$scope.groupByTagUpdated = function(segment, index) {
|
||||
if (index === $scope.groupBySegments.length-1) {
|
||||
$scope.groupBySegments.push(MetricSegment.newPlusButton());
|
||||
}
|
||||
};
|
||||
|
||||
$scope.changeFunction = function(func) {
|
||||
@ -56,43 +83,107 @@ function (angular, _) {
|
||||
};
|
||||
|
||||
$scope.getMeasurements = function () {
|
||||
// var measurement = $scope.segments[0].value;
|
||||
// var queryType, query;
|
||||
// if (index === 0) {
|
||||
// queryType = 'MEASUREMENTS';
|
||||
// query = 'SHOW MEASUREMENTS';
|
||||
// } else if (index % 2 === 1) {
|
||||
// queryType = 'TAG_KEYS';
|
||||
// query = 'SHOW TAG KEYS FROM "' + measurement + '"';
|
||||
// } else {
|
||||
// queryType = 'TAG_VALUES';
|
||||
// query = 'SHOW TAG VALUES FROM "' + measurement + '" WITH KEY = ' + $scope.segments[$scope.segments.length - 2].value;
|
||||
// }
|
||||
//
|
||||
// console.log('getAltSegments: query' , query);
|
||||
//
|
||||
console.log('get measurements');
|
||||
return $scope.datasource.metricFindQuery('SHOW MEASUREMENTS', 'MEASUREMENTS').then(function(results) {
|
||||
console.log('get alt segments: response', results);
|
||||
var measurements = _.map(results, function(segment) {
|
||||
return new MetricSegment({ value: segment.text, expandable: segment.expandable });
|
||||
});
|
||||
return $scope.datasource.metricFindQuery('SHOW MEASUREMENTS', 'MEASUREMENTS')
|
||||
.then($scope.transformToSegments)
|
||||
.then($scope.addTemplateVariableSegments)
|
||||
.then(null, $scope.handleQueryError);
|
||||
};
|
||||
|
||||
_.each(templateSrv.variables, function(variable) {
|
||||
measurements.unshift(new MetricSegment({
|
||||
type: 'template',
|
||||
value: '$' + variable.name,
|
||||
expandable: true,
|
||||
}));
|
||||
});
|
||||
$scope.handleQueryError = function(err) {
|
||||
$scope.parserError = err.message || 'Failed to issue metric query';
|
||||
return [];
|
||||
};
|
||||
|
||||
return measurements;
|
||||
}, function(err) {
|
||||
$scope.parserError = err.message || 'Failed to issue metric query';
|
||||
return [];
|
||||
$scope.transformToSegments = function(results) {
|
||||
return _.map(results, function(segment) {
|
||||
return new MetricSegment({ value: segment.text, expandable: segment.expandable });
|
||||
});
|
||||
};
|
||||
|
||||
$scope.addTemplateVariableSegments = function(segments) {
|
||||
_.each(templateSrv.variables, function(variable) {
|
||||
segments.unshift(new MetricSegment({ type: 'template', value: '$' + variable.name, expandable: true }));
|
||||
});
|
||||
return segments;
|
||||
};
|
||||
|
||||
$scope.getTagsOrValues = function(segment, index) {
|
||||
var query, queryType;
|
||||
if (segment.type === 'key' || segment.type === 'plus-button') {
|
||||
queryType = 'TAG_KEYS';
|
||||
query = 'SHOW TAG KEYS FROM "' + $scope.target.measurement + '"';
|
||||
} else if (segment.type === 'value') {
|
||||
queryType = 'TAG_VALUES';
|
||||
query = 'SHOW TAG VALUES FROM "' + $scope.target.measurement + '" WITH KEY = ' + $scope.tagSegments[index-2].value;
|
||||
} else if (segment.type === 'condition') {
|
||||
return $q.when([new MetricSegment('AND'), new MetricSegment('OR')]);
|
||||
}
|
||||
else {
|
||||
return $q.when([]);
|
||||
}
|
||||
|
||||
return $scope.datasource.metricFindQuery(query, queryType)
|
||||
.then($scope.transformToSegments)
|
||||
.then($scope.addTemplateVariableSegments)
|
||||
.then(function(results) {
|
||||
if (queryType === 'TAG_KEYS' && segment.type !== 'plus-button') {
|
||||
results.push(new MetricSegment({fake: true, value: 'remove tag filter'}));
|
||||
}
|
||||
return results;
|
||||
})
|
||||
.then(null, $scope.handleQueryError);
|
||||
};
|
||||
|
||||
$scope.tagSegmentUpdated = function(segment, index) {
|
||||
$scope.tagSegments[index] = segment;
|
||||
|
||||
if (segment.value === 'remove tag filter') {
|
||||
$scope.tagSegments.splice(index, 3);
|
||||
if ($scope.tagSegments.length === 0) {
|
||||
$scope.tagSegments.push(MetricSegment.newPlusButton());
|
||||
} else {
|
||||
$scope.tagSegments.splice(index-1, 1);
|
||||
$scope.tagSegments.push(MetricSegment.newPlusButton());
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (segment.type === 'plus-button') {
|
||||
if (index > 2) {
|
||||
$scope.tagSegments.splice(index, 0, MetricSegment.newCondition('AND'));
|
||||
}
|
||||
$scope.tagSegments.push(new MetricSegment({fake: true, value: '=', type: 'operator'}));
|
||||
$scope.tagSegments.push(new MetricSegment({fake: true, value: 'select tag value', type: 'value' }));
|
||||
segment.type = 'key';
|
||||
}
|
||||
|
||||
if ((index+1) === $scope.tagSegments.length) {
|
||||
$scope.tagSegments.push(MetricSegment.newPlusButton());
|
||||
}
|
||||
}
|
||||
|
||||
$scope.rebuildTargetTagConditions();
|
||||
};
|
||||
|
||||
$scope.rebuildTargetTagConditions = function() {
|
||||
var tags = [{}];
|
||||
var tagIndex = 0;
|
||||
_.each($scope.tagSegments, function(segment2) {
|
||||
if (segment2.type === 'key') {
|
||||
tags[tagIndex].key = segment2.value;
|
||||
}
|
||||
else if (segment2.type === 'value') {
|
||||
tags[tagIndex].value = segment2.value;
|
||||
}
|
||||
else if (segment2.type === 'condition') {
|
||||
tags.push({ condition: segment2.value });
|
||||
tagIndex += 1;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.target.tags = tags;
|
||||
$scope.$parent.get_data();
|
||||
};
|
||||
|
||||
function MetricSegment(options) {
|
||||
if (options === '*' || options.value === '*') {
|
||||
this.value = '*';
|
||||
@ -107,19 +198,25 @@ function (angular, _) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.cssClass = options.cssClass;
|
||||
this.type = options.type;
|
||||
this.fake = options.fake;
|
||||
this.value = options.value;
|
||||
this.type = options.type;
|
||||
this.expandable = options.expandable;
|
||||
this.html = $sce.trustAsHtml(templateSrv.highlightVariablesAsHtml(this.value));
|
||||
this.html = options.html || $sce.trustAsHtml(templateSrv.highlightVariablesAsHtml(this.value));
|
||||
}
|
||||
|
||||
MetricSegment.newSelectMeasurement = function() {
|
||||
return new MetricSegment({value: 'select measurement', fake: true});
|
||||
};
|
||||
|
||||
MetricSegment.newSelectTag = function() {
|
||||
return new MetricSegment({value: 'select tag', fake: true});
|
||||
MetricSegment.newCondition = function(condition) {
|
||||
return new MetricSegment({value: condition, type: 'condition', cssClass: 'query-keyword' });
|
||||
};
|
||||
|
||||
MetricSegment.newPlusButton = function() {
|
||||
return new MetricSegment({fake: true, html: '<i class="fa fa-plus"></i>', type: 'plus-button' });
|
||||
};
|
||||
|
||||
MetricSegment.newSelectTagValue = function() {
|
||||
|
@ -21,7 +21,7 @@ define([
|
||||
describe('series with tags only', function() {
|
||||
var builder = new InfluxQueryBuilder({
|
||||
measurement: 'cpu',
|
||||
tags: {'hostname': 'server1'}
|
||||
tags: [{key: 'hostname', value: 'server1'}]
|
||||
});
|
||||
|
||||
var query = builder.build();
|
||||
|
120
public/test/specs/influxdbQueryCtrl-specs.js
Normal file
120
public/test/specs/influxdbQueryCtrl-specs.js
Normal file
@ -0,0 +1,120 @@
|
||||
define([
|
||||
'helpers',
|
||||
'plugins/datasource/influxdb/queryCtrl'
|
||||
], function(helpers) {
|
||||
'use strict';
|
||||
|
||||
describe('InfluxDBQueryCtrl', function() {
|
||||
var ctx = new helpers.ControllerTestContext();
|
||||
|
||||
beforeEach(module('grafana.controllers'));
|
||||
beforeEach(ctx.providePhase());
|
||||
beforeEach(ctx.createControllerPhase('InfluxQueryCtrl'));
|
||||
|
||||
beforeEach(function() {
|
||||
ctx.scope.target = {};
|
||||
ctx.scope.$parent = { get_data: sinon.spy() };
|
||||
|
||||
ctx.scope.datasource = ctx.datasource;
|
||||
ctx.scope.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([]));
|
||||
});
|
||||
|
||||
describe('init', function() {
|
||||
beforeEach(function() {
|
||||
ctx.scope.init();
|
||||
});
|
||||
|
||||
it('should init tagSegments', function() {
|
||||
expect(ctx.scope.tagSegments.length).to.be(1);
|
||||
});
|
||||
|
||||
it('should init measurementSegment', function() {
|
||||
expect(ctx.scope.measurementSegment.value).to.be('select measurement');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when first tag segment is updated', function() {
|
||||
beforeEach(function() {
|
||||
ctx.scope.init();
|
||||
ctx.scope.tagSegmentUpdated({value: 'asd', type: 'plus-button'}, 0);
|
||||
});
|
||||
|
||||
it('should update tag key', function() {
|
||||
expect(ctx.scope.target.tags[0].key).to.be('asd');
|
||||
expect(ctx.scope.tagSegments[0].type).to.be('key');
|
||||
});
|
||||
|
||||
it('should add tagSegments', function() {
|
||||
expect(ctx.scope.tagSegments.length).to.be(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when last tag value segment is updated', function() {
|
||||
beforeEach(function() {
|
||||
ctx.scope.init();
|
||||
ctx.scope.tagSegmentUpdated({value: 'asd', type: 'plus-button'}, 0);
|
||||
ctx.scope.tagSegmentUpdated({value: 'server1', type: 'value'}, 2);
|
||||
});
|
||||
|
||||
it('should update tag value', function() {
|
||||
expect(ctx.scope.target.tags[0].value).to.be('server1');
|
||||
});
|
||||
|
||||
it('should add plus button for another filter', function() {
|
||||
expect(ctx.scope.tagSegments[3].fake).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when second tag key is added', function() {
|
||||
beforeEach(function() {
|
||||
ctx.scope.init();
|
||||
ctx.scope.tagSegmentUpdated({value: 'asd', type: 'plus-button' }, 0);
|
||||
ctx.scope.tagSegmentUpdated({value: 'server1', type: 'value'}, 2);
|
||||
ctx.scope.tagSegmentUpdated({value: 'key2', type: 'plus-button'}, 3);
|
||||
});
|
||||
|
||||
it('should update tag key', function() {
|
||||
expect(ctx.scope.target.tags[1].key).to.be('key2');
|
||||
});
|
||||
|
||||
it('should add AND segment', function() {
|
||||
expect(ctx.scope.tagSegments[3].value).to.be('AND');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when condition is changed', function() {
|
||||
beforeEach(function() {
|
||||
ctx.scope.init();
|
||||
ctx.scope.tagSegmentUpdated({value: 'asd', type: 'plus-button' }, 0);
|
||||
ctx.scope.tagSegmentUpdated({value: 'server1', type: 'value'}, 2);
|
||||
ctx.scope.tagSegmentUpdated({value: 'key2', type: 'plus-button'}, 3);
|
||||
ctx.scope.tagSegmentUpdated({value: 'OR', type: 'condition'}, 3);
|
||||
});
|
||||
|
||||
it('should update tag condition', function() {
|
||||
expect(ctx.scope.target.tags[1].condition).to.be('OR');
|
||||
});
|
||||
|
||||
it('should update AND segment', function() {
|
||||
expect(ctx.scope.tagSegments[3].value).to.be('OR');
|
||||
expect(ctx.scope.tagSegments.length).to.be(7);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when deleting is changed', function() {
|
||||
beforeEach(function() {
|
||||
ctx.scope.init();
|
||||
ctx.scope.tagSegmentUpdated({value: 'asd', type: 'plus-button' }, 0);
|
||||
ctx.scope.tagSegmentUpdated({value: 'server1', type: 'value'}, 2);
|
||||
ctx.scope.tagSegmentUpdated({value: 'key2', type: 'plus-button'}, 3);
|
||||
ctx.scope.tagSegmentUpdated({value: 'remove tag filter', type: 'key'}, 4);
|
||||
});
|
||||
|
||||
it('should remove all segment after 2 and replace with plus button', function() {
|
||||
expect(ctx.scope.tagSegments.length).to.be(4);
|
||||
expect(ctx.scope.tagSegments[3].type).to.be('plus-button');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
@ -128,6 +128,7 @@ require([
|
||||
'specs/influxQueryBuilder-specs',
|
||||
'specs/influx09-querybuilder-specs',
|
||||
'specs/influxdb-datasource-specs',
|
||||
'specs/influxdbQueryCtrl-specs',
|
||||
'specs/graph-ctrl-specs',
|
||||
'specs/graph-specs',
|
||||
'specs/graph-tooltip-specs',
|
||||
|
Loading…
Reference in New Issue
Block a user