Working on new query editor for influxdb 0.9, #1525

This commit is contained in:
Torkel Ödegaard 2015-05-15 11:38:22 +02:00
parent a258e2e608
commit 11c8e80ea9
9 changed files with 132 additions and 125 deletions

View File

@ -12,7 +12,7 @@ define([
'./bootstrap-tagsinput',
'./bodyClass',
'./variableValueSelect',
'./graphiteSegment',
'./metric.segment',
'./grafanaVersionCheck',
'./dropdown.typeahead',
'./topnav',

View File

@ -9,7 +9,7 @@ function (angular, app, _, $) {
angular
.module('grafana.directives')
.directive('graphiteSegment', function($compile, $sce) {
.directive('metricSegment', function($compile, $sce) {
var inputTemplate = '<input type="text" data-provide="typeahead" ' +
' class="tight-form-clear-input input-medium"' +
' spellcheck="false" style="display:none"></input>';
@ -17,6 +17,12 @@ function (angular, app, _, $) {
var buttonTemplate = '<a class="tight-form-item" tabindex="1" focus-me="segment.focus" ng-bind-html="segment.html"></a>';
return {
scope: {
segment: "=",
getAltSegments: "&",
onValueChanged: "&"
},
link: function($scope, elem) {
var $input = $(inputTemplate);
var $button = $(buttonTemplate);
@ -46,7 +52,7 @@ function (angular, app, _, $) {
segment.expandable = true;
segment.fake = false;
}
$scope.segmentValueChanged(segment, $scope.$index);
$scope.onValueChanged();
});
};
@ -69,7 +75,8 @@ function (angular, app, _, $) {
if (options) { return options; }
$scope.$apply(function() {
$scope.getAltSegments($scope.$index).then(function() {
$scope.getAltSegments().then(function(altSegments) {
$scope.altSegments = altSegments;
options = _.map($scope.altSegments, function(alt) { return alt.value; });
// add custom values

View File

@ -74,7 +74,9 @@
ng-show="showTextEditor" />
<ul class="tight-form-list" role="menu" ng-hide="showTextEditor">
<li ng-repeat="segment in segments" role="menuitem" graphite-segment></li>
<li ng-repeat="segment in segments" role="menuitem">
<metric-segment segment="segment" get-alt-segments="getAltSegments($index)" on-value-changed="segmentValueChanged(segment, $index)"></metric-segment>
</li>
<li ng-repeat="func in functions">
<span graphite-func-editor class="tight-form-item tight-form-func">
</span>

View File

@ -152,23 +152,18 @@ function (angular, _, config, gfunc, Parser) {
}
$scope.getAltSegments = function (index) {
$scope.altSegments = [];
var query = index === 0 ? '*' : getSegmentPathUpTo(index) + '.*';
return $scope.datasource.metricFindQuery(query)
.then(function(segments) {
$scope.altSegments = _.map(segments, function(segment) {
return $scope.datasource.metricFindQuery(query).then(function(segments) {
var altSegments = _.map(segments, function(segment) {
return new MetricSegment({ value: segment.text, expandable: segment.expandable });
});
if ($scope.altSegments.length === 0) {
return;
}
if (altSegments.length === 0) { return altSegments; }
// add template variables
_.each(templateSrv.variables, function(variable) {
$scope.altSegments.unshift(new MetricSegment({
altSegments.unshift(new MetricSegment({
type: 'template',
value: '$' + variable.name,
expandable: true,
@ -176,10 +171,12 @@ function (angular, _, config, gfunc, Parser) {
});
// add wildcard option
$scope.altSegments.unshift(new MetricSegment('*'));
altSegments.unshift(new MetricSegment('*'));
return altSegments;
})
.then(null, function(err) {
$scope.parserError = err.message || 'Failed to issue metric query';
return [];
});
};

View File

@ -108,7 +108,7 @@ function (angular, _, $) {
function addElementsAndCompile() {
$funcLink.appendTo(elem);
var $paramLink = $('<a ng-click="" class="graphite-func-param-link">' + $scope.target.column + '</a>');
var $paramLink = $('<a ng-click="" class="graphite-func-param-link">value</a>');
var $input = $(paramTemplate);
$paramLink.appendTo(elem);

View File

@ -62,23 +62,54 @@
</li>
</ul>
<input type="text"
class="tight-form-clear-input span10"
ng-model="target.query"
focus-me="target.rawQuery"
spellcheck='false'
ng-model-onblur ng-change="targetTextChanged()"
ng-show="target.rawQuery" />
<input type="text" class="tight-form-clear-input span10" ng-model="target.query" focus-me="target.rawQuery"
spellcheck='false' ng-model-onblur ng-change="targetTextChanged()" ng-show="target.rawQuery" />
<ul class="tight-form-list" role="menu" ng-hide="target.rawQuery">
<li ng-repeat="segment in segments" role="menuitem" graphite-segment></li>
<li ng-repeat="func in functions">
<span graphite-func-editor class="tight-form-item tight-form-func">
</span>
</li>
<li class="dropdown" graphite-add-func>
</li>
<li class="tight-form-item query-keyword">
SELECT
</li>
<li class="dropdown tight-form-item">
<a gf-dropdown="functionMenu" class="dropdown-toggle" data-toggle="dropdown">
{{target.function}}<span>(value)</span>
</a>
</li>
<li class="tight-form-item query-keyword">
FROM
</li>
<li>
<metric-segment segment="measurementSegment" get-alt-segments="getMeasurements()" on-value-changed="measurementChanged()"></metric-segment>
</li>
<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>
<li class="tight-form-item">
<span class="query-keyword">GROUP BY</span>
time($interval), <i class="fa fa-plus"></i>
</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">
fill ({{target.fill}})
</span>
<span ng-show="!target.fill">
no fill
</span>
</a>
<ul class="dropdown-menu">
<li><a ng-click="target.fill = ''">no fill</a></li>
<li><a ng-click="target.fill = 'null'">fill (null)</a></li>
<li><a ng-click="target.fill = '0'">fill (0)</a></li>
</ul>
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>

View File

@ -7,19 +7,39 @@ function (angular, _) {
var module = angular.module('grafana.controllers');
module.controller('InfluxQueryCtrl', function($scope, $timeout, $sce, templateSrv, $q) {
module.controller('InfluxQueryCtrl', function($scope, $timeout, $sce, templateSrv) {
$scope.functionList = [
'count', 'mean', 'sum', 'min',
'max', 'mode', 'distinct', 'median',
'derivative', 'stddev', 'first', 'last',
'difference'
];
$scope.functionMenu = _.map($scope.functionList, function(func) {
return { text: func, click: "changeFunction('" + func + "');" };
});
$scope.init = function() {
$scope.segments = $scope.target.segments || [];
var target = $scope.target;
target.function = target.function || 'mean';
$scope.functionsSelect = [
'count', 'mean', 'sum', 'min',
'max', 'mode', 'distinct', 'median',
'derivative', 'stddev', 'first', 'last',
'difference'
];
if (!target.measurement) {
$scope.measurementSegment = MetricSegment.newSelectMeasurement();
} else {
$scope.measurementSegment = new MetricSegment(target.measurement);
}
};
checkOtherSegments(0);
$scope.changeFunction = function(func) {
$scope.target.function = func;
$scope.$parent.get_data();
};
$scope.measurementChanged = function() {
$scope.target.measurement = $scope.measurementSegment.value;
console.log('measurement updated', $scope.target.measurement);
$scope.$parent.get_data();
};
$scope.toggleQueryMode = function () {
@ -35,103 +55,44 @@ function (angular, _) {
$scope.panel.targets.push(clone);
};
$scope.getAltSegments = function (index) {
$scope.altSegments = [];
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);
return $scope.datasource.metricFindQuery(query, queryType).then(function(results) {
$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);
$scope.altSegments = _.map(results, function(segment) {
var measurements = _.map(results, function(segment) {
return new MetricSegment({ value: segment.text, expandable: segment.expandable });
});
_.each(templateSrv.variables, function(variable) {
$scope.altSegments.unshift(new MetricSegment({
measurements.unshift(new MetricSegment({
type: 'template',
value: '$' + variable.name,
expandable: true,
}));
});
return measurements;
}, function(err) {
$scope.parserError = err.message || 'Failed to issue metric query';
return [];
});
};
$scope.segmentValueChanged = function (segment, segmentIndex) {
delete $scope.parserError;
if (segment.expandable) {
return checkOtherSegments(segmentIndex + 1).then(function () {
setSegmentFocus(segmentIndex + 1);
$scope.targetChanged();
});
}
else {
$scope.segments = $scope.segments.splice(0, segmentIndex + 1);
}
setSegmentFocus(segmentIndex + 1);
$scope.targetChanged();
};
$scope.targetChanged = function() {
if ($scope.parserError) {
return;
}
$scope.target.measurement = '';
$scope.target.tags = {};
$scope.target.measurement = $scope.segments[0].value;
for (var i = 1; i+1 < $scope.segments.length; i += 2) {
var key = $scope.segments[i].value;
$scope.target.tags[key] = $scope.segments[i+1].value;
}
$scope.$parent.get_data();
};
function checkOtherSegments(fromIndex) {
if (fromIndex === 0) {
$scope.segments.push(MetricSegment.newSelectMetric());
return;
}
if ($scope.segments.length === 0) {
throw('should always have a scope segment?');
}
if (_.last($scope.segments).fake) {
return $q.when([]);
} else if ($scope.segments.length % 2 === 1) {
$scope.segments.push(MetricSegment.newSelectTag());
return $q.when([]);
} else {
$scope.segments.push(MetricSegment.newSelectTagValue());
return $q.when([]);
}
}
function setSegmentFocus(segmentIndex) {
_.each($scope.segments, function(segment, index) {
segment.focus = segmentIndex === index;
});
}
function MetricSegment(options) {
if (options === '*' || options.value === '*') {
this.value = '*';
@ -153,8 +114,8 @@ function (angular, _) {
this.html = $sce.trustAsHtml(templateSrv.highlightVariablesAsHtml(this.value));
}
MetricSegment.newSelectMetric = function() {
return new MetricSegment({value: 'select metric', fake: true});
MetricSegment.newSelectMeasurement = function() {
return new MetricSegment({value: 'select measurement', fake: true});
};
MetricSegment.newSelectTag = function() {

View File

@ -337,3 +337,10 @@
text-overflow: ellipsis;
}
}
.query-keyword {
font-weight: bold;
color: @blue;
}

View File

@ -141,13 +141,15 @@ define([
ctx.scope.target.target = 'test.count';
ctx.scope.datasource.metricFindQuery.returns(ctx.$q.when([]));
ctx.scope.init();
ctx.scope.getAltSegments(1);
ctx.scope.getAltSegments(1).then(function(results) {
ctx.altSegments = results;
});
ctx.scope.$digest();
ctx.scope.$parent = { get_data: sinon.spy() };
});
it('should have no segments', function() {
expect(ctx.scope.altSegments.length).to.be(0);
expect(ctx.altSegments.length).to.be(0);
});
});