From d3bbc245c9fb8d8dd7d0e28005490c928663cf8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 10 May 2016 14:32:25 +0200 Subject: [PATCH 1/9] feat(query_part): began query part refactor to be able to reuse it in prometheus query editor --- .../core/components/query_part/query_part.ts | 123 +++++++++++ .../query_part/query_part_editor.ts | 0 .../plugins/datasource/influxdb/query_part.ts | 198 +++++------------- 3 files changed, 171 insertions(+), 150 deletions(-) create mode 100644 public/app/core/components/query_part/query_part.ts create mode 100644 public/app/core/components/query_part/query_part_editor.ts diff --git a/public/app/core/components/query_part/query_part.ts b/public/app/core/components/query_part/query_part.ts new file mode 100644 index 00000000000..90724f65d2d --- /dev/null +++ b/public/app/core/components/query_part/query_part.ts @@ -0,0 +1,123 @@ +/// + +import _ from 'lodash'; + +export class QueryPartDef { + type: string; + params: any[]; + defaultParams: any[]; + renderer: any; + category: any; + addStrategy: any; + + constructor(options: any) { + this.type = options.type; + this.params = options.params; + this.defaultParams = options.defaultParams; + this.renderer = options.renderer; + this.category = options.category; + this.addStrategy = options.addStrategy; + } +} + +export class QueryPart { + part: any; + def: QueryPartDef; + params: any[]; + text: string; + + constructor(part: any, def: any) { + this.part = part; + this.def = def; + if (!this.def) { + throw {message: 'Could not find query part ' + part.type}; + } + + part.params = part.params || _.clone(this.def.defaultParams); + this.params = part.params; + this.updateText(); + } + + render(innerExpr: string) { + return this.def.renderer(this, innerExpr); + } + + hasMultipleParamsInString (strValue, index) { + if (strValue.indexOf(',') === -1) { + return false; + } + + return this.def.params[index + 1] && this.def.params[index + 1].optional; + } + + updateParam (strValue, index) { + // handle optional parameters + // if string contains ',' and next param is optional, split and update both + if (this.hasMultipleParamsInString(strValue, index)) { + _.each(strValue.split(','), function(partVal: string, idx) { + this.updateParam(partVal.trim(), idx); + }, this); + return; + } + + if (strValue === '' && this.def.params[index].optional) { + this.params.splice(index, 1); + } else { + this.params[index] = strValue; + } + + this.part.params = this.params; + this.updateText(); + } + + updateText() { + if (this.params.length === 0) { + this.text = this.def.type + '()'; + return; + } + + var text = this.def.type + '('; + text += this.params.join(', '); + text += ')'; + this.text = text; + } +} + +export function functionRenderer(part, innerExpr) { + var str = part.def.type + '('; + var parameters = _.map(part.params, (value, index) => { + var paramType = part.def.params[index]; + if (paramType.type === 'time') { + if (value === 'auto') { + value = '$interval'; + } + } + if (paramType.quote === 'single') { + return "'" + value + "'"; + } else if (paramType.quote === 'double') { + return '"' + value + '"'; + } + + return value; + }); + + if (innerExpr) { + parameters.unshift(innerExpr); + } + return str + parameters.join(', ') + ')'; +} + + +export function suffixRenderer(part, innerExpr) { + return innerExpr + ' ' + part.params[0]; +} + +export function identityRenderer(part, innerExpr) { + return part.params[0]; +} + +export function quotedIdentityRenderer(part, innerExpr) { + return '"' + part.params[0] + '"'; +} + + diff --git a/public/app/core/components/query_part/query_part_editor.ts b/public/app/core/components/query_part/query_part_editor.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/public/app/plugins/datasource/influxdb/query_part.ts b/public/app/plugins/datasource/influxdb/query_part.ts index 63d70be5653..f22713a8057 100644 --- a/public/app/plugins/datasource/influxdb/query_part.ts +++ b/public/app/plugins/datasource/influxdb/query_part.ts @@ -1,6 +1,14 @@ /// import _ from 'lodash'; +import { + QueryPartDef, + QueryPart, + functionRenderer, + suffixRenderer, + identityRenderer, + quotedIdentityRenderer, +} from 'app/core/components/query_part/query_part'; var index = []; var categories = { @@ -12,71 +20,26 @@ var categories = { Fields: [], }; +function createPart(part): any { + var def = index[part.type]; + if (!def) { + throw {message: 'Could not find query part ' + part.type}; + } + + return new QueryPart(part, def); +}; + +function register(options: any) { + index[options.type] = new QueryPartDef(options); + options.category.push(index[options.type]); +} + var groupByTimeFunctions = []; -class QueryPartDef { - type: string; - params: any[]; - defaultParams: any[]; - renderer: any; - category: any; - addStrategy: any; - - constructor(options: any) { - this.type = options.type; - this.params = options.params; - this.defaultParams = options.defaultParams; - this.renderer = options.renderer; - this.category = options.category; - this.addStrategy = options.addStrategy; - } - - static register(options: any) { - index[options.type] = new QueryPartDef(options); - options.category.push(index[options.type]); - } -} - -function functionRenderer(part, innerExpr) { - var str = part.def.type + '('; - var parameters = _.map(part.params, (value, index) => { - var paramType = part.def.params[index]; - if (paramType.type === 'time') { - if (value === 'auto') { - value = '$interval'; - } - } - if (paramType.quote === 'single') { - return "'" + value + "'"; - } else if (paramType.quote === 'double') { - return '"' + value + '"'; - } - - return value; - }); - - if (innerExpr) { - parameters.unshift(innerExpr); - } - return str + parameters.join(', ') + ')'; -} - function aliasRenderer(part, innerExpr) { return innerExpr + ' AS ' + '"' + part.params[0] + '"'; } -function suffixRenderer(part, innerExpr) { - return innerExpr + ' ' + part.params[0]; -} - -function identityRenderer(part, innerExpr) { - return part.params[0]; -} - -function quotedIdentityRenderer(part, innerExpr) { - return '"' + part.params[0] + '"'; -} - function fieldRenderer(part, innerExpr) { if (part.params[0] === '*') { return '*'; @@ -149,13 +112,13 @@ function addAliasStrategy(selectParts, partModel) { function addFieldStrategy(selectParts, partModel, query) { // copy all parts var parts = _.map(selectParts, function(part: any) { - return new QueryPart({type: part.def.type, params: _.clone(part.params)}); + return createPart({type: part.def.type, params: _.clone(part.params)}); }); query.selectModels.push(parts); } -QueryPartDef.register({ +register({ type: 'field', addStrategy: addFieldStrategy, category: categories.Fields, @@ -165,7 +128,7 @@ QueryPartDef.register({ }); // Aggregations -QueryPartDef.register({ +register({ type: 'count', addStrategy: replaceAggregationAddStrategy, category: categories.Aggregations, @@ -174,7 +137,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'distinct', addStrategy: replaceAggregationAddStrategy, category: categories.Aggregations, @@ -183,7 +146,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'integral', addStrategy: replaceAggregationAddStrategy, category: categories.Aggregations, @@ -192,7 +155,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'mean', addStrategy: replaceAggregationAddStrategy, category: categories.Aggregations, @@ -201,7 +164,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'median', addStrategy: replaceAggregationAddStrategy, category: categories.Aggregations, @@ -210,7 +173,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'sum', addStrategy: replaceAggregationAddStrategy, category: categories.Aggregations, @@ -221,7 +184,7 @@ QueryPartDef.register({ // transformations -QueryPartDef.register({ +register({ type: 'derivative', addStrategy: addTransformationStrategy, category: categories.Transformations, @@ -230,7 +193,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'non_negative_derivative', addStrategy: addTransformationStrategy, category: categories.Transformations, @@ -239,7 +202,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'difference', addStrategy: addTransformationStrategy, category: categories.Transformations, @@ -248,7 +211,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'moving_average', addStrategy: addTransformationStrategy, category: categories.Transformations, @@ -257,7 +220,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'stddev', addStrategy: addTransformationStrategy, category: categories.Transformations, @@ -266,7 +229,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'time', category: groupByTimeFunctions, params: [{ name: "interval", type: "time", options: ['auto', '1s', '10s', '1m', '5m', '10m', '15m', '1h'] }], @@ -274,7 +237,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'fill', category: groupByTimeFunctions, params: [{ name: "fill", type: "string", options: ['none', 'null', '0', 'previous'] }], @@ -283,7 +246,7 @@ QueryPartDef.register({ }); // Selectors -QueryPartDef.register({ +register({ type: 'bottom', addStrategy: replaceAggregationAddStrategy, category: categories.Selectors, @@ -292,7 +255,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'first', addStrategy: replaceAggregationAddStrategy, category: categories.Selectors, @@ -301,7 +264,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'last', addStrategy: replaceAggregationAddStrategy, category: categories.Selectors, @@ -310,7 +273,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'max', addStrategy: replaceAggregationAddStrategy, category: categories.Selectors, @@ -319,7 +282,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'min', addStrategy: replaceAggregationAddStrategy, category: categories.Selectors, @@ -328,7 +291,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'percentile', addStrategy: replaceAggregationAddStrategy, category: categories.Selectors, @@ -337,7 +300,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'top', addStrategy: replaceAggregationAddStrategy, category: categories.Selectors, @@ -346,7 +309,7 @@ QueryPartDef.register({ renderer: functionRenderer, }); -QueryPartDef.register({ +register({ type: 'tag', category: groupByTimeFunctions, params: [{name: 'tag', type: 'string', dynamicLookup: true}], @@ -354,7 +317,7 @@ QueryPartDef.register({ renderer: fieldRenderer, }); -QueryPartDef.register({ +register({ type: 'math', addStrategy: addMathStrategy, category: categories.Math, @@ -363,7 +326,7 @@ QueryPartDef.register({ renderer: suffixRenderer, }); -QueryPartDef.register({ +register({ type: 'alias', addStrategy: addAliasStrategy, category: categories.Aliasing, @@ -373,74 +336,9 @@ QueryPartDef.register({ renderer: aliasRenderer, }); -class QueryPart { - part: any; - def: QueryPartDef; - params: any[]; - text: string; - - constructor(part: any) { - this.part = part; - this.def = index[part.type]; - if (!this.def) { - throw {message: 'Could not find query part ' + part.type}; - } - - part.params = part.params || _.clone(this.def.defaultParams); - this.params = part.params; - this.updateText(); - } - - render(innerExpr: string) { - return this.def.renderer(this, innerExpr); - } - - hasMultipleParamsInString (strValue, index) { - if (strValue.indexOf(',') === -1) { - return false; - } - - return this.def.params[index + 1] && this.def.params[index + 1].optional; - } - - updateParam (strValue, index) { - // handle optional parameters - // if string contains ',' and next param is optional, split and update both - if (this.hasMultipleParamsInString(strValue, index)) { - _.each(strValue.split(','), function(partVal: string, idx) { - this.updateParam(partVal.trim(), idx); - }, this); - return; - } - - if (strValue === '' && this.def.params[index].optional) { - this.params.splice(index, 1); - } else { - this.params[index] = strValue; - } - - this.part.params = this.params; - this.updateText(); - } - - updateText() { - if (this.params.length === 0) { - this.text = this.def.type + '()'; - return; - } - - var text = this.def.type + '('; - text += this.params.join(', '); - text += ')'; - this.text = text; - } -} export default { - create: function(part): any { - return new QueryPart(part); - }, - + create: createPart, getCategories: function() { return categories; } From cca37caf331ce62eb3cbdb16b02bbbeb1d9583c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 10 May 2016 15:48:07 +0200 Subject: [PATCH 2/9] feat(prometheus): began work on prometheus query model --- .../datasource/prometheus/prom_query.ts | 79 +++++++++++++++++++ .../prometheus/specs/query_specs.ts | 23 ++++++ 2 files changed, 102 insertions(+) create mode 100644 public/app/plugins/datasource/prometheus/prom_query.ts create mode 100644 public/app/plugins/datasource/prometheus/specs/query_specs.ts diff --git a/public/app/plugins/datasource/prometheus/prom_query.ts b/public/app/plugins/datasource/prometheus/prom_query.ts new file mode 100644 index 00000000000..e9ef6ddf40d --- /dev/null +++ b/public/app/plugins/datasource/prometheus/prom_query.ts @@ -0,0 +1,79 @@ +import { + QueryPartDef, + QueryPart, + functionRenderer, + suffixRenderer, + identityRenderer, + quotedIdentityRenderer, +} from 'app/core/components/query_part/query_part'; + +var index = []; +var categories = { + Functions: [], +}; + +export class PromQuery { + target: any; + metric: string; + range: string; + filters: any[]; + functions: any[]; + templateSrv: any; + scopedVars: any; + + constructor(target, templateSrv?, scopedVars?) { + this.target = target; + this.templateSrv = templateSrv; + this.scopedVars = scopedVars; + } + + render() { + var query = this.target.metric; + if (this.target.range) { + query += '[' + this.target.range + ']'; + } + + for (let funcModel of this.target.functions) { + var partDef = index[funcModel.type]; + if (!partDef) { + continue; + } + + var part = new QueryPart(funcModel, partDef); + query = part.render(query); + } + + return query; + } +} + +export function createPart(part): any { + var def = index[part.type]; + if (!def) { + throw {message: 'Could not find query part ' + part.type}; + } + + return new QueryPart(part, def); +} + +function register(options: any) { + index[options.type] = new QueryPartDef(options); + options.category.push(index[options.type]); +} + +function addFunctionStrategy(model, partModel) { + model.functions.push(partModel); +} + +register({ + type: 'rate', + addStrategy: addFunctionStrategy, + category: categories.Functions, + params: [], + defaultParams: [], + renderer: functionRenderer, +}); + +export function getCategories() { + return categories; +} diff --git a/public/app/plugins/datasource/prometheus/specs/query_specs.ts b/public/app/plugins/datasource/prometheus/specs/query_specs.ts new file mode 100644 index 00000000000..5bb164c306e --- /dev/null +++ b/public/app/plugins/datasource/prometheus/specs/query_specs.ts @@ -0,0 +1,23 @@ +import {describe, beforeEach, it, sinon, expect} from 'test/lib/common'; + +import {PromQuery} from '../prom_query'; + +describe.only('PromQuery', function() { + var templateSrv = {replace: val => val}; + + describe('render series with mesurement only', function() { + it('should generate correct query', function() { + var query = new PromQuery({ + metric: 'cpu', + range: '5m', + functions: [ + {type: 'rate', params: []} + ] + }, templateSrv, {}); + + var queryText = query.render(); + expect(queryText).to.be('rate(cpu[5m])'); + }); + }); + +}); From b170b6ec8b91485370a3fc7f3f8c7b947a4a7cc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 11 May 2016 14:52:44 +0200 Subject: [PATCH 3/9] feat(query part): moved query part editor from influxdb to core --- .../query_part/query_part_editor.ts | 183 ++++++++++++++++++ public/app/core/core.ts | 2 + .../influxdb/partials/query.editor.html | 8 +- .../influxdb/partials/query_part.html | 5 - .../plugins/datasource/influxdb/query_ctrl.ts | 3 - .../datasource/influxdb/query_part_editor.js | 178 ----------------- .../prometheus/partials/query.editor.html | 30 ++- .../datasource/prometheus/prom_query.ts | 33 +++- .../datasource/prometheus/query_ctrl.ts | 60 ++++-- 9 files changed, 277 insertions(+), 225 deletions(-) delete mode 100644 public/app/plugins/datasource/influxdb/partials/query_part.html delete mode 100644 public/app/plugins/datasource/influxdb/query_part_editor.js diff --git a/public/app/core/components/query_part/query_part_editor.ts b/public/app/core/components/query_part/query_part_editor.ts index e69de29bb2d..f9122ee283b 100644 --- a/public/app/core/components/query_part/query_part_editor.ts +++ b/public/app/core/components/query_part/query_part_editor.ts @@ -0,0 +1,183 @@ +/// + +import _ from 'lodash'; +import $ from 'jquery'; +import coreModule from 'app/core/core_module'; + +var template = ` +
+ +
+ +{{part.def.type}} +() +`; + + /** @ngInject */ +export function queryPartEditorDirective($compile, templateSrv) { + + var paramTemplate = ''; + return { + restrict: 'E', + template: template, + scope: { + part: "=", + removeAction: "&", + partUpdated: "&", + getOptions: "&", + }, + link: function postLink($scope, elem) { + var part = $scope.part; + var partDef = part.def; + var $paramsContainer = elem.find('.query-part-parameters'); + var $controlsContainer = elem.find('.tight-form-func-controls'); + + function clickFuncParam(paramIndex) { + /*jshint validthis:true */ + var $link = $(this); + var $input = $link.next(); + + $input.val(part.params[paramIndex]); + $input.css('width', ($link.width() + 16) + 'px'); + + $link.hide(); + $input.show(); + $input.focus(); + $input.select(); + + var typeahead = $input.data('typeahead'); + if (typeahead) { + $input.val(''); + typeahead.lookup(); + } + } + + function inputBlur(paramIndex) { + /*jshint validthis:true */ + var $input = $(this); + var $link = $input.prev(); + var newValue = $input.val(); + + if (newValue !== '' || part.def.params[paramIndex].optional) { + $link.html(templateSrv.highlightVariablesAsHtml(newValue)); + + part.updateParam($input.val(), paramIndex); + $scope.$apply($scope.partUpdated); + } + + $input.hide(); + $link.show(); + } + + function inputKeyPress(paramIndex, e) { + /*jshint validthis:true */ + if (e.which === 13) { + inputBlur.call(this, paramIndex); + } + } + + function inputKeyDown() { + /*jshint validthis:true */ + this.style.width = (3 + this.value.length) * 8 + 'px'; + } + + function addTypeahead($input, param, paramIndex) { + if (!param.options && !param.dynamicLookup) { + return; + } + + var typeaheadSource = function (query, callback) { + if (param.options) { return param.options; } + + $scope.$apply(function() { + $scope.getOptions().then(function(result) { + var dynamicOptions = _.map(result, function(op) { return op.value; }); + callback(dynamicOptions); + }); + }); + }; + + $input.attr('data-provide', 'typeahead'); + var options = param.options; + if (param.type === 'int') { + options = _.map(options, function(val) { return val.toString(); }); + } + + $input.typeahead({ + source: typeaheadSource, + minLength: 0, + items: 1000, + updater: function (value) { + setTimeout(function() { + inputBlur.call($input[0], paramIndex); + }, 0); + return value; + } + }); + + var typeahead = $input.data('typeahead'); + typeahead.lookup = function () { + this.query = this.$element.val() || ''; + var items = this.source(this.query, $.proxy(this.process, this)); + return items ? this.process(items) : items; + }; + } + + $scope.toggleControls = function() { + var targetDiv = elem.closest('.tight-form'); + + if (elem.hasClass('show-function-controls')) { + elem.removeClass('show-function-controls'); + targetDiv.removeClass('has-open-function'); + $controlsContainer.hide(); + return; + } + + elem.addClass('show-function-controls'); + targetDiv.addClass('has-open-function'); + $controlsContainer.show(); + }; + + $scope.removeActionInternal = function() { + $scope.toggleControls(); + $scope.removeAction(); + }; + + function addElementsAndCompile() { + _.each(partDef.params, function(param, index) { + if (param.optional && part.params.length <= index) { + return; + } + + if (index > 0) { + $(', ').appendTo($paramsContainer); + } + + var paramValue = templateSrv.highlightVariablesAsHtml(part.params[index]); + var $paramLink = $('' + paramValue + ''); + var $input = $(paramTemplate); + + $paramLink.appendTo($paramsContainer); + $input.appendTo($paramsContainer); + + $input.blur(_.partial(inputBlur, index)); + $input.keyup(inputKeyDown); + $input.keypress(_.partial(inputKeyPress, index)); + $paramLink.click(_.partial(clickFuncParam, index)); + + addTypeahead($input, param, index); + }); + } + + function relink() { + $paramsContainer.empty(); + addElementsAndCompile(); + } + + relink(); + } + }; +} + +coreModule.directive('queryPartEditor', queryPartEditorDirective); diff --git a/public/app/core/core.ts b/public/app/core/core.ts index abebb5ce560..9c8ae9cdad2 100644 --- a/public/app/core/core.ts +++ b/public/app/core/core.ts @@ -33,6 +33,7 @@ import {Emitter} from './utils/emitter'; import {layoutSelector} from './components/layout_selector/layout_selector'; import {switchDirective} from './components/switch'; import {dashboardSelector} from './components/dashboard_selector'; +import {queryPartEditorDirective} from './components/query_part/query_part_editor'; import 'app/core/controllers/all'; import 'app/core/services/all'; import 'app/core/routes/routes'; @@ -56,4 +57,5 @@ export { Emitter, appEvents, dashboardSelector, + queryPartEditorDirective, }; diff --git a/public/app/plugins/datasource/influxdb/partials/query.editor.html b/public/app/plugins/datasource/influxdb/partials/query.editor.html index 0d6dd5c5c3b..68b8ee60d98 100644 --- a/public/app/plugins/datasource/influxdb/partials/query.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/query.editor.html @@ -35,13 +35,13 @@
- - +
@@ -62,12 +62,12 @@ GROUP BY - - +
diff --git a/public/app/plugins/datasource/influxdb/partials/query_part.html b/public/app/plugins/datasource/influxdb/partials/query_part.html deleted file mode 100644 index 478edfe5c29..00000000000 --- a/public/app/plugins/datasource/influxdb/partials/query_part.html +++ /dev/null @@ -1,5 +0,0 @@ -
- -
- -{{part.def.type}}() diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.ts b/public/app/plugins/datasource/influxdb/query_ctrl.ts index 8d6a03fc4a1..69895e53c25 100644 --- a/public/app/plugins/datasource/influxdb/query_ctrl.ts +++ b/public/app/plugins/datasource/influxdb/query_ctrl.ts @@ -1,8 +1,5 @@ /// -import './query_part_editor'; -import './query_part_editor'; - import angular from 'angular'; import _ from 'lodash'; import InfluxQueryBuilder from './query_builder'; diff --git a/public/app/plugins/datasource/influxdb/query_part_editor.js b/public/app/plugins/datasource/influxdb/query_part_editor.js deleted file mode 100644 index 4e044eca304..00000000000 --- a/public/app/plugins/datasource/influxdb/query_part_editor.js +++ /dev/null @@ -1,178 +0,0 @@ -define([ - 'angular', - 'lodash', - 'jquery', -], -function (angular, _, $) { - 'use strict'; - - angular - .module('grafana.directives') - .directive('influxQueryPartEditor', function($compile, templateSrv) { - - var paramTemplate = ''; - return { - restrict: 'E', - templateUrl: 'public/app/plugins/datasource/influxdb/partials/query_part.html', - scope: { - part: "=", - removeAction: "&", - partUpdated: "&", - getOptions: "&", - }, - link: function postLink($scope, elem) { - var part = $scope.part; - var partDef = part.def; - var $paramsContainer = elem.find('.query-part-parameters'); - var $controlsContainer = elem.find('.tight-form-func-controls'); - - function clickFuncParam(paramIndex) { - /*jshint validthis:true */ - var $link = $(this); - var $input = $link.next(); - - $input.val(part.params[paramIndex]); - $input.css('width', ($link.width() + 16) + 'px'); - - $link.hide(); - $input.show(); - $input.focus(); - $input.select(); - - var typeahead = $input.data('typeahead'); - if (typeahead) { - $input.val(''); - typeahead.lookup(); - } - } - - function inputBlur(paramIndex) { - /*jshint validthis:true */ - var $input = $(this); - var $link = $input.prev(); - var newValue = $input.val(); - - if (newValue !== '' || part.def.params[paramIndex].optional) { - $link.html(templateSrv.highlightVariablesAsHtml(newValue)); - - part.updateParam($input.val(), paramIndex); - $scope.$apply($scope.partUpdated); - } - - $input.hide(); - $link.show(); - } - - function inputKeyPress(paramIndex, e) { - /*jshint validthis:true */ - if(e.which === 13) { - inputBlur.call(this, paramIndex); - } - } - - function inputKeyDown() { - /*jshint validthis:true */ - this.style.width = (3 + this.value.length) * 8 + 'px'; - } - - function addTypeahead($input, param, paramIndex) { - if (!param.options && !param.dynamicLookup) { - return; - } - - var typeaheadSource = function (query, callback) { - if (param.options) { return param.options; } - - $scope.$apply(function() { - $scope.getOptions().then(function(result) { - var dynamicOptions = _.map(result, function(op) { return op.value; }); - callback(dynamicOptions); - }); - }); - }; - - $input.attr('data-provide', 'typeahead'); - var options = param.options; - if (param.type === 'int') { - options = _.map(options, function(val) { return val.toString(); }); - } - - $input.typeahead({ - source: typeaheadSource, - minLength: 0, - items: 1000, - updater: function (value) { - setTimeout(function() { - inputBlur.call($input[0], paramIndex); - }, 0); - return value; - } - }); - - var typeahead = $input.data('typeahead'); - typeahead.lookup = function () { - this.query = this.$element.val() || ''; - var items = this.source(this.query, $.proxy(this.process, this)); - return items ? this.process(items) : items; - }; - } - - $scope.toggleControls = function() { - var targetDiv = elem.closest('.tight-form'); - - if (elem.hasClass('show-function-controls')) { - elem.removeClass('show-function-controls'); - targetDiv.removeClass('has-open-function'); - $controlsContainer.hide(); - return; - } - - elem.addClass('show-function-controls'); - targetDiv.addClass('has-open-function'); - $controlsContainer.show(); - }; - - $scope.removeActionInternal = function() { - $scope.toggleControls(); - $scope.removeAction(); - }; - - function addElementsAndCompile() { - _.each(partDef.params, function(param, index) { - if (param.optional && part.params.length <= index) { - return; - } - - if (index > 0) { - $(', ').appendTo($paramsContainer); - } - - var paramValue = templateSrv.highlightVariablesAsHtml(part.params[index]); - var $paramLink = $('' + paramValue + ''); - var $input = $(paramTemplate); - - $paramLink.appendTo($paramsContainer); - $input.appendTo($paramsContainer); - - $input.blur(_.partial(inputBlur, index)); - $input.keyup(inputKeyDown); - $input.keypress(_.partial(inputKeyPress, index)); - $paramLink.click(_.partial(clickFuncParam, index)); - - addTypeahead($input, param, index); - }); - } - - function relink() { - $paramsContainer.empty(); - addElementsAndCompile(); - } - - relink(); - } - }; - - }); - -}); diff --git a/public/app/plugins/datasource/prometheus/partials/query.editor.html b/public/app/plugins/datasource/prometheus/partials/query.editor.html index 37c27ace58a..a67e3e9b188 100644 --- a/public/app/plugins/datasource/prometheus/partials/query.editor.html +++ b/public/app/plugins/datasource/prometheus/partials/query.editor.html @@ -1,18 +1,34 @@
-
- - +
+ +
-
- - + +
+ + +
+ +
+ +
+ +
+
- + diff --git a/public/app/plugins/datasource/prometheus/prom_query.ts b/public/app/plugins/datasource/prometheus/prom_query.ts index e9ef6ddf40d..83ac4f1615f 100644 --- a/public/app/plugins/datasource/prometheus/prom_query.ts +++ b/public/app/plugins/datasource/prometheus/prom_query.ts @@ -7,6 +7,8 @@ import { quotedIdentityRenderer, } from 'app/core/components/query_part/query_part'; +import _ from 'lodash'; + var index = []; var categories = { Functions: [], @@ -23,8 +25,21 @@ export class PromQuery { constructor(target, templateSrv?, scopedVars?) { this.target = target; + + this.target.expr = this.target.expr || ''; + this.target.intervalFactor = this.target.intervalFactor || 2; + this.target.functions = this.target.functions || []; + this.templateSrv = templateSrv; this.scopedVars = scopedVars; + + this.updateProjection(); + } + + updateProjection() { + this.functions = _.map(this.target.functions, function(func: any) { + return createPart(func); + }); } render() { @@ -33,18 +48,17 @@ export class PromQuery { query += '[' + this.target.range + ']'; } - for (let funcModel of this.target.functions) { - var partDef = index[funcModel.type]; - if (!partDef) { - continue; - } - - var part = new QueryPart(funcModel, partDef); - query = part.render(query); + for (let func of this.functions) { + query = func.render(query); } return query; } + + addQueryPart(category, item) { + var partModel = createPart({type: item.text}); + partModel.def.addStrategy(this, partModel); + } } export function createPart(part): any { @@ -63,6 +77,7 @@ function register(options: any) { function addFunctionStrategy(model, partModel) { model.functions.push(partModel); + model.target.functions.push(partModel.part); } register({ @@ -74,6 +89,6 @@ register({ renderer: functionRenderer, }); -export function getCategories() { +export function getQueryPartCategories() { return categories; } diff --git a/public/app/plugins/datasource/prometheus/query_ctrl.ts b/public/app/plugins/datasource/prometheus/query_ctrl.ts index 8284268ef39..25a803b7bb3 100644 --- a/public/app/plugins/datasource/prometheus/query_ctrl.ts +++ b/public/app/plugins/datasource/prometheus/query_ctrl.ts @@ -6,46 +6,68 @@ import moment from 'moment'; import * as dateMath from 'app/core/utils/datemath'; import {QueryCtrl} from 'app/plugins/sdk'; +import {PromQuery, getQueryPartCategories} from './prom_query'; class PrometheusQueryCtrl extends QueryCtrl { static templateUrl = 'partials/query.editor.html'; - metric: any; + query: any; + metricSegment: any; + addQueryPartMenu: any[]; resolutions: any; oldTarget: any; suggestMetrics: any; linkToPrometheus: any; /** @ngInject */ - constructor($scope, $injector, private templateSrv) { + constructor($scope, $injector, private templateSrv, private uiSegmentSrv) { super($scope, $injector); - var target = this.target; - target.expr = target.expr || ''; - target.intervalFactor = target.intervalFactor || 2; + this.query = new PromQuery(this.target, templateSrv); + + if (this.target.metric) { + this.metricSegment = uiSegmentSrv.newSegment(this.target.metric); + } else { + this.metricSegment = uiSegmentSrv.newSegment({value: 'select metric', fake: true}); + } - this.metric = ''; this.resolutions = _.map([1,2,3,4,5,10], function(f) { return {factor: f, label: '1/' + f}; }); - $scope.$on('typeahead-updated', () => { - this.$scope.$apply(() => { + this.updateLink(); + this.buildQueryPartMenu(); + } - this.target.expr += this.target.metric; - this.metric = ''; - this.refreshMetricData(); + buildQueryPartMenu() { + var categories = getQueryPartCategories(); + this.addQueryPartMenu = _.reduce(categories, function(memo, cat, key) { + var menu = { + text: key, + submenu: cat.map(item => { + return {text: item.type, value: item.type}; + }), + }; + memo.push(menu); + return memo; + }, []); + } + + addQueryPart(item, subItem) { + this.query.addQueryPart(item, subItem); + this.panelCtrl.refresh(); + } + + getMetricOptions() { + return this.datasource.performSuggestQuery('').then(res => { + return _.map(res, metric => { + return this.uiSegmentSrv.newSegment(metric); }); }); + } - // called from typeahead so need this - // here in order to ensure this ref - this.suggestMetrics = (query, callback) => { - console.log(this); - this.datasource.performSuggestQuery(query).then(callback); - }; - - this.updateLink(); + queryChanged() { + this.target.metric = this.metricSegment.value; } refreshMetricData() { From c77d72b29fb806eb848987f085fa1eebf16e4818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 23 May 2016 18:03:17 +0200 Subject: [PATCH 4/9] feat(prometheus): progress on new prometheus query editor, #5117 --- .../prometheus/metric_find_query.js | 128 +++++++++++------- .../prometheus/partials/query.editor.html | 9 +- .../datasource/prometheus/prom_query.ts | 27 +++- .../datasource/prometheus/query_ctrl.ts | 42 +++++- .../specs/metric_find_query_specs.ts | 37 +++++ .../prometheus/specs/query_specs.ts | 17 ++- 6 files changed, 207 insertions(+), 53 deletions(-) diff --git a/public/app/plugins/datasource/prometheus/metric_find_query.js b/public/app/plugins/datasource/prometheus/metric_find_query.js index f449978be81..d3d5376bc19 100644 --- a/public/app/plugins/datasource/prometheus/metric_find_query.js +++ b/public/app/plugins/datasource/prometheus/metric_find_query.js @@ -11,27 +11,33 @@ function (_) { } PrometheusMetricFindQuery.prototype.process = function() { - var label_values_regex = /^label_values\((?:(.+),\s*)?([a-zA-Z_][a-zA-Z0-9_]+)\)$/; - var metric_names_regex = /^metrics\((.+)\)$/; - var query_result_regex = /^query_result\((.+)\)$/; + var labelValuesRegex = /^label_values\((?:(.+),\s*)?([a-zA-Z_][a-zA-Z0-9_]+)\)$/; + var metricNamesRegex = /^metrics\((.+)\)$/; + var labelsRegex = /^labels\((.+)\)$/; + var queryResultRegex = /^query_result\((.+)\)$/; - var label_values_query = this.query.match(label_values_regex); - if (label_values_query) { - if (label_values_query[1]) { - return this.labelValuesQuery(label_values_query[2], label_values_query[1]); + var labelsQuery = this.query.match(labelsRegex); + if (labelsQuery) { + return this.labelsQuery(labelsQuery[1]); + } + + var labelValuesQuery = this.query.match(labelValuesRegex); + if (labelValuesQuery) { + if (labelValuesQuery[1]) { + return this.labelValuesQuery(labelValuesQuery[2], labelValuesQuery[1]); } else { - return this.labelValuesQuery(label_values_query[2], null); + return this.labelValuesQuery(labelValuesQuery[2], null); } } - var metric_names_query = this.query.match(metric_names_regex); - if (metric_names_query) { - return this.metricNameQuery(metric_names_query[1]); + var metricNamesQuery = this.query.match(metricNamesRegex); + if (metricNamesQuery) { + return this.metricNameQuery(metricNamesQuery[1]); } - var query_result_query = this.query.match(query_result_regex); - if (query_result_query) { - return this.queryResultQuery(query_result_query[1]); + var queryResultQuery = this.query.match(queryResultRegex); + if (queryResultQuery) { + return this.queryResultQuery(queryResultQuery[1]); } // if query contains full metric name, return metric name and label list @@ -67,45 +73,71 @@ function (_) { } }; + PrometheusMetricFindQuery.prototype.labelsQuery = function(metric) { + var url; + + url = '/api/v1/series?match[]=' + encodeURIComponent(metric) + + '&start=' + (this.range.from.valueOf() / 1000) + + '&end=' + (this.range.to.valueOf() / 1000); + + return this.datasource._request('GET', url) + .then(function(result) { + var tags = {}; + _.each(result.data.data, function(metric) { + _.each(metric, function(value, key) { + if (key === "__name__") { + return; + } + + tags[key] = key; + }); + }); + + return _.map(tags, function(value) { + return {text: value, value: value}; + }); + }); + }; + PrometheusMetricFindQuery.prototype.metricNameQuery = function(metricFilterPattern) { var url = '/api/v1/label/__name__/values'; return this.datasource._request('GET', url) - .then(function(result) { - return _.chain(result.data.data) - .filter(function(metricName) { - var r = new RegExp(metricFilterPattern); - return r.test(metricName); - }) - .map(function(matchedMetricName) { - return { - text: matchedMetricName, - expandable: true - }; - }) - .value(); - }); + .then(function(result) { + return _.chain(result.data.data) + .filter(function(metricName) { + var r = new RegExp(metricFilterPattern); + return r.test(metricName); + }) + .map(function(matchedMetricName) { + return { + text: matchedMetricName, + expandable: true + }; + }) + .value(); + }); }; PrometheusMetricFindQuery.prototype.queryResultQuery = function(query) { var url = '/api/v1/query?query=' + encodeURIComponent(query) + '&time=' + (this.range.to.valueOf() / 1000); return this.datasource._request('GET', url) - .then(function(result) { - return _.map(result.data.data.result, function(metricData) { - var text = metricData.metric.__name__ || ''; - delete metricData.metric.__name__; - text += '{' + - _.map(metricData.metric, function(v, k) { return k + '="' + v + '"'; }).join(',') + - '}'; - text += ' ' + metricData.value[1] + ' ' + metricData.value[0] * 1000; + .then(function(result) { + return _.map(result.data.data.result, function(metricData) { + var text = metricData.metric.__name__ || ''; + delete metricData.metric.__name__; + text += '{' + + _.map(metricData.metric, function(v, k) { return k + '="' + v + '"'; }).join(',') + + '}'; + text += ' ' + metricData.value[1] + ' ' + metricData.value[0] * 1000; - return { - text: text, - expandable: true - }; + return { + text: text, + expandable: true + }; + }); }); - }); }; PrometheusMetricFindQuery.prototype.metricNameAndLabelsQuery = function(query) { @@ -115,14 +147,14 @@ function (_) { var self = this; return this.datasource._request('GET', url) - .then(function(result) { - return _.map(result.data.data, function(metric) { - return { - text: self.datasource.getOriginalMetricName(metric), - expandable: true - }; + .then(function(result) { + return _.map(result.data.data, function(metric) { + return { + text: self.datasource.getOriginalMetricName(metric), + expandable: true + }; + }); }); - }); }; return PrometheusMetricFindQuery; diff --git a/public/app/plugins/datasource/prometheus/partials/query.editor.html b/public/app/plugins/datasource/prometheus/partials/query.editor.html index a67e3e9b188..359c366cb0f 100644 --- a/public/app/plugins/datasource/prometheus/partials/query.editor.html +++ b/public/app/plugins/datasource/prometheus/partials/query.editor.html @@ -1,5 +1,10 @@ - -
+ + +
+ +
+ +
diff --git a/public/app/plugins/datasource/prometheus/prom_query.ts b/public/app/plugins/datasource/prometheus/prom_query.ts index 83ac4f1615f..d73a8b1cbcf 100644 --- a/public/app/plugins/datasource/prometheus/prom_query.ts +++ b/public/app/plugins/datasource/prometheus/prom_query.ts @@ -2,7 +2,6 @@ import { QueryPartDef, QueryPart, functionRenderer, - suffixRenderer, identityRenderer, quotedIdentityRenderer, } from 'app/core/components/query_part/query_part'; @@ -12,6 +11,7 @@ import _ from 'lodash'; var index = []; var categories = { Functions: [], + GroupBy: [], }; export class PromQuery { @@ -29,6 +29,7 @@ export class PromQuery { this.target.expr = this.target.expr || ''; this.target.intervalFactor = this.target.intervalFactor || 2; this.target.functions = this.target.functions || []; + this.target.editorMode = this.target.editorMode || true; this.templateSrv = templateSrv; this.scopedVars = scopedVars; @@ -80,6 +81,10 @@ function addFunctionStrategy(model, partModel) { model.target.functions.push(partModel.part); } +function groupByLabelRenderer(part, innerExpr) { + return innerExpr + ' by(' + part.params.join(',') + ')'; +} + register({ type: 'rate', addStrategy: addFunctionStrategy, @@ -89,6 +94,26 @@ register({ renderer: functionRenderer, }); +register({ + type: 'sum', + addStrategy: addFunctionStrategy, + category: categories.Functions, + params: [], + defaultParams: [], + renderer: functionRenderer, +}); + +register({ + type: 'by', + addStrategy: addFunctionStrategy, + category: categories.Functions, + params: [ + {name: "label", type: "string", dynamicLookup: true} + ], + defaultParams: [], + renderer: groupByLabelRenderer, +}); + export function getQueryPartCategories() { return categories; } diff --git a/public/app/plugins/datasource/prometheus/query_ctrl.ts b/public/app/plugins/datasource/prometheus/query_ctrl.ts index 8692d915d30..680b1294df6 100644 --- a/public/app/plugins/datasource/prometheus/query_ctrl.ts +++ b/public/app/plugins/datasource/prometheus/query_ctrl.ts @@ -20,7 +20,7 @@ class PrometheusQueryCtrl extends QueryCtrl { linkToPrometheus: any; /** @ngInject */ - constructor($scope, $injector, private templateSrv, private uiSegmentSrv) { + constructor($scope, $injector, private templateSrv, private uiSegmentSrv, private $rootScope) { super($scope, $injector); this.query = new PromQuery(this.target, templateSrv); @@ -58,6 +58,39 @@ class PrometheusQueryCtrl extends QueryCtrl { this.panelCtrl.refresh(); } + getPartOptions(part) { + if (part.def.type === 'by') { + return this.datasource.metricFindQuery('labels(' + this.target.metric + ')') + .then(this.transformToSegments(true)) + .catch(this.handleQueryError.bind(true)); + } + } + + partUpdated(part) { + this.target.expr = this.query.render(); + this.panelCtrl.refresh(); + } + + handleQueryError(err) { + this.$rootScope.appEvent('alert-error', ['Query failed', err.message]); + } + + transformToSegments(addTemplateVars) { + return (results) => { + var segments = _.map(results, segment => { + return this.uiSegmentSrv.newSegment({ value: segment.text, expandable: segment.expandable }); + }); + + if (addTemplateVars) { + for (let variable of this.templateSrv.variables) { + segments.unshift(this.uiSegmentSrv.newSegment({ type: 'template', value: '/^$' + variable.name + '$/', expandable: true })); + } + } + + return segments; + }; + } + getMetricOptions() { return this.datasource.performSuggestQuery('').then(res => { return _.map(res, metric => { @@ -68,6 +101,13 @@ class PrometheusQueryCtrl extends QueryCtrl { queryChanged() { this.target.metric = this.metricSegment.value; + this.target.expr = this.query.render(); + this.refresh(); + } + + toggleEditorMode() { + this.target.expr = this.query.render(false); + this.target.editorMode = !this.target.editorMode; } refreshMetricData() { diff --git a/public/app/plugins/datasource/prometheus/specs/metric_find_query_specs.ts b/public/app/plugins/datasource/prometheus/specs/metric_find_query_specs.ts index 38b50d2ce79..b393f36402c 100644 --- a/public/app/plugins/datasource/prometheus/specs/metric_find_query_specs.ts +++ b/public/app/plugins/datasource/prometheus/specs/metric_find_query_specs.ts @@ -22,18 +22,22 @@ describe('PrometheusMetricFindQuery', function() { describe('When performing metricFindQuery', function() { var results; var response; + it('label_values(resource) should generate label search query', function() { response = { status: "success", data: ["value1", "value2", "value3"] }; + ctx.$httpBackend.expect('GET', 'proxied/api/v1/label/resource/values').respond(response); var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(resource)', ctx.timeSrv); pm.process().then(function(data) { results = data; }); ctx.$httpBackend.flush(); ctx.$rootScope.$apply(); + expect(results.length).to.be(3); }); + it('label_values(metric, resource) should generate series query', function() { response = { status: "success", @@ -43,13 +47,16 @@ describe('PrometheusMetricFindQuery', function() { {__name__: "metric", resource: "value3"} ] }; + ctx.$httpBackend.expect('GET', /proxied\/api\/v1\/series\?match\[\]=metric&start=.*&end=.*/).respond(response); var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(metric, resource)', ctx.timeSrv); pm.process().then(function(data) { results = data; }); ctx.$httpBackend.flush(); ctx.$rootScope.$apply(); + expect(results.length).to.be(3); }); + it('label_values(metric, resource) should pass correct time', function() { ctx.timeSrv.setTime({ from: moment.utc('2011-01-01'), to: moment.utc('2015-01-01') }); ctx.$httpBackend.expect('GET', @@ -59,6 +66,7 @@ describe('PrometheusMetricFindQuery', function() { ctx.$httpBackend.flush(); ctx.$rootScope.$apply(); }); + it('label_values(metric{label1="foo", label2="bar", label3="baz"}, resource) should generate series query', function() { response = { status: "success", @@ -68,6 +76,7 @@ describe('PrometheusMetricFindQuery', function() { {__name__: "metric", resource: "value3"} ] }; + ctx.$httpBackend.expect('GET', /proxied\/api\/v1\/series\?match\[\]=metric&start=.*&end=.*/).respond(response); var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(metric, resource)', ctx.timeSrv); pm.process().then(function(data) { results = data; }); @@ -75,18 +84,22 @@ describe('PrometheusMetricFindQuery', function() { ctx.$rootScope.$apply(); expect(results.length).to.be(3); }); + it('metrics(metric.*) should generate metric name query', function() { response = { status: "success", data: ["metric1","metric2","metric3","nomatch"] }; + ctx.$httpBackend.expect('GET', 'proxied/api/v1/label/__name__/values').respond(response); var pm = new PrometheusMetricFindQuery(ctx.ds, 'metrics(metric.*)', ctx.timeSrv); pm.process().then(function(data) { results = data; }); ctx.$httpBackend.flush(); ctx.$rootScope.$apply(); + expect(results.length).to.be(3); }); + it('query_result(metric) should generate metric name query', function() { response = { status: "success", @@ -98,6 +111,7 @@ describe('PrometheusMetricFindQuery', function() { }] } }; + ctx.$httpBackend.expect('GET', /proxied\/api\/v1\/query\?query=metric&time=.*/).respond(response); var pm = new PrometheusMetricFindQuery(ctx.ds, 'query_result(metric)', ctx.timeSrv); pm.process().then(function(data) { results = data; }); @@ -106,5 +120,28 @@ describe('PrometheusMetricFindQuery', function() { expect(results.length).to.be(1); expect(results[0].text).to.be('metric{job="testjob"} 3846 1443454528000'); }); + + it('labels(metric) should generate series query', function() { + response = { + status: "success", + data: [ + {__name__: "metric", resource: "value1", app: "app1"}, + {__name__: "metric", resource: "value2", app: "app2"}, + {__name__: "metric", resource: "value3", server: "server1"} + ] + }; + + ctx.$httpBackend.expect('GET', /proxied\/api\/v1\/series\?match\[\]=metric&start=.*&end=.*/).respond(response); + var pm = new PrometheusMetricFindQuery(ctx.ds, 'labels(metric)', ctx.timeSrv); + pm.process().then(function(data) { results = data; }); + ctx.$httpBackend.flush(); + ctx.$rootScope.$apply(); + + expect(results.length).to.be(3); + expect(results[0].text).to.be('resource'); + expect(results[1].text).to.be('app'); + expect(results[2].text).to.be('server'); + }); + }); }); diff --git a/public/app/plugins/datasource/prometheus/specs/query_specs.ts b/public/app/plugins/datasource/prometheus/specs/query_specs.ts index 5bb164c306e..d58e0834f89 100644 --- a/public/app/plugins/datasource/prometheus/specs/query_specs.ts +++ b/public/app/plugins/datasource/prometheus/specs/query_specs.ts @@ -2,7 +2,7 @@ import {describe, beforeEach, it, sinon, expect} from 'test/lib/common'; import {PromQuery} from '../prom_query'; -describe.only('PromQuery', function() { +describe('PromQuery', function() { var templateSrv = {replace: val => val}; describe('render series with mesurement only', function() { @@ -20,4 +20,19 @@ describe.only('PromQuery', function() { }); }); + describe('render series with group by label', function() { + it('should generate correct query', function() { + var query = new PromQuery({ + metric: 'cpu', + functions: [ + {type: 'sum', params: []}, + {type: 'by', params: ['app']}, + ] + }, templateSrv, {}); + + var queryText = query.render(); + expect(queryText).to.be('sum(cpu) by(app)'); + }); + }); + }); From 3d776851082725a4e3b5780cd35bf888348b892f Mon Sep 17 00:00:00 2001 From: Thibault Chataigner Date: Mon, 6 Jun 2016 13:47:45 +0000 Subject: [PATCH 5/9] Enable the "limit" param in /api/search --- pkg/services/search/handlers.go | 1 + pkg/services/search/models.go | 1 + pkg/services/sqlstore/dashboard.go | 8 +++++++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pkg/services/search/handlers.go b/pkg/services/search/handlers.go index a4905d6fa58..a8344ba05a5 100644 --- a/pkg/services/search/handlers.go +++ b/pkg/services/search/handlers.go @@ -44,6 +44,7 @@ func searchHandler(query *Query) error { IsStarred: query.IsStarred, OrgId: query.OrgId, DashboardIds: query.DashboardIds, + Limit: query.Limit, } if err := bus.Dispatch(&dashQuery); err != nil { diff --git a/pkg/services/search/models.go b/pkg/services/search/models.go index 159637013f5..ada3e1ccdfa 100644 --- a/pkg/services/search/models.go +++ b/pkg/services/search/models.go @@ -42,6 +42,7 @@ type FindPersistedDashboardsQuery struct { UserId int64 IsStarred bool DashboardIds []int + Limit int Result HitList } diff --git a/pkg/services/sqlstore/dashboard.go b/pkg/services/sqlstore/dashboard.go index ef36fd75ab6..d71eaeefa6e 100644 --- a/pkg/services/sqlstore/dashboard.go +++ b/pkg/services/sqlstore/dashboard.go @@ -123,6 +123,11 @@ type DashboardSearchProjection struct { } func SearchDashboards(query *search.FindPersistedDashboardsQuery) error { + limit := query.Limit + if limit == 0 { + limit = 1000 + } + var sql bytes.Buffer params := make([]interface{}, 0) @@ -165,7 +170,8 @@ func SearchDashboards(query *search.FindPersistedDashboardsQuery) error { params = append(params, "%"+query.Title+"%") } - sql.WriteString(fmt.Sprintf(" ORDER BY dashboard.title ASC LIMIT 1000")) + sql.WriteString(fmt.Sprintf(" ORDER BY dashboard.title ASC LIMIT ?")) + params = append(params, limit) var res []DashboardSearchProjection From ffac5c12999eca55dbbed36a24cc8f6588dff8fa Mon Sep 17 00:00:00 2001 From: bergquist Date: Wed, 8 Jun 2016 09:35:34 +0200 Subject: [PATCH 6/9] docs(changelog): add note about changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b25f025d1d..eb1cb5c55ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * **InfluxDB**: Add spread function, closes [#5211](https://github.com/grafana/grafana/issues/5211) * **Scripts**: Use restart instead of start for deb package script, closes [#5282](https://github.com/grafana/grafana/pull/5282) * **Logging**: Moved to structured logging lib, and moved to component specific level filters via config file, closes [#4590](https://github.com/grafana/grafana/issues/4590) +* **Search**: Add search limit query parameter, closes [#5292](https://github.com/grafana/grafana/pull/5292) ## Breaking changes * **Logging** : Changed default logging output format (now structured into message, and key value pairs, with logger key acting as component). You can also no change in config to json log ouput. From 755ec552dea491e23cb2d2fc778da32234405b9d Mon Sep 17 00:00:00 2001 From: Mukesh Date: Thu, 9 Jun 2016 04:13:38 +0530 Subject: [PATCH 7/9] Bugfix: org select btn was not working on profile (#5317) --- public/app/features/org/partials/profile.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/org/partials/profile.html b/public/app/features/org/partials/profile.html index 811891ae317..02fa2878a17 100644 --- a/public/app/features/org/partials/profile.html +++ b/public/app/features/org/partials/profile.html @@ -51,7 +51,7 @@ Current - + Select From ced3bafa99fb81687428905b60521f2971ace51e Mon Sep 17 00:00:00 2001 From: Mark Baas Date: Thu, 9 Jun 2016 02:13:31 -0400 Subject: [PATCH 8/9] returns correct index (#5275) --- public/app/plugins/datasource/opentsdb/datasource.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/public/app/plugins/datasource/opentsdb/datasource.js b/public/app/plugins/datasource/opentsdb/datasource.js index b683aa02068..1d3db75658c 100644 --- a/public/app/plugins/datasource/opentsdb/datasource.js +++ b/public/app/plugins/datasource/opentsdb/datasource.js @@ -403,10 +403,7 @@ function (angular, _, dateMath) { } else { return _.findIndex(options.targets, function(target) { if (target.filters && target.filters.length > 0) { - return target.metric === metricData.metric && - _.all(target.filters, function(filter) { - return filter.tagk === interpolatedTagValue === "*"; - }); + return target.metric === metricData.metric; } else { return target.metric === metricData.metric && _.all(target.tags, function(tagV, tagK) { From b8aa8b307997dde9f11c28401436cc841f087726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 9 Jun 2016 10:37:33 +0200 Subject: [PATCH 9/9] feat(prometheus): restore old prometheus query editor, revert this commit in prometheus query editor v2 branch --- .../datasource/prometheus/datasource.ts | 25 ++-- .../prometheus/metric_find_query.js | 128 +++++++----------- .../prometheus/partials/query.editor.html | 39 ++---- .../datasource/prometheus/prom_query.ts | 119 ---------------- .../datasource/prometheus/query_ctrl.ts | 102 +++----------- .../specs/metric_find_query_specs.ts | 37 ----- .../prometheus/specs/query_specs.ts | 38 ------ 7 files changed, 87 insertions(+), 401 deletions(-) delete mode 100644 public/app/plugins/datasource/prometheus/prom_query.ts delete mode 100644 public/app/plugins/datasource/prometheus/specs/query_specs.ts diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index f1c58f6a61d..cbd7b1ba24b 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -256,23 +256,14 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS return this.renderTemplate(options.legendFormat, labelData) || '{}'; }; - this.renderTemplate = function(format, data) { - var originalSettings = _.templateSettings; - _.templateSettings = { - interpolate: /\{\{(.+?)\}\}/g - }; - - var template = _.template(templateSrv.replace(format)); - var result; - try { - result = template(data); - } catch (e) { - result = null; - } - - _.templateSettings = originalSettings; - - return result; + this.renderTemplate = function(aliasPattern, aliasData) { + var aliasRegex = /\{\{\s*(.+?)\s*\}\}/g; + return aliasPattern.replace(aliasRegex, function(match, g1) { + if (aliasData[g1]) { + return aliasData[g1]; + } + return g1; + }); }; this.getOriginalMetricName = function(labelData) { diff --git a/public/app/plugins/datasource/prometheus/metric_find_query.js b/public/app/plugins/datasource/prometheus/metric_find_query.js index d3d5376bc19..f449978be81 100644 --- a/public/app/plugins/datasource/prometheus/metric_find_query.js +++ b/public/app/plugins/datasource/prometheus/metric_find_query.js @@ -11,33 +11,27 @@ function (_) { } PrometheusMetricFindQuery.prototype.process = function() { - var labelValuesRegex = /^label_values\((?:(.+),\s*)?([a-zA-Z_][a-zA-Z0-9_]+)\)$/; - var metricNamesRegex = /^metrics\((.+)\)$/; - var labelsRegex = /^labels\((.+)\)$/; - var queryResultRegex = /^query_result\((.+)\)$/; + var label_values_regex = /^label_values\((?:(.+),\s*)?([a-zA-Z_][a-zA-Z0-9_]+)\)$/; + var metric_names_regex = /^metrics\((.+)\)$/; + var query_result_regex = /^query_result\((.+)\)$/; - var labelsQuery = this.query.match(labelsRegex); - if (labelsQuery) { - return this.labelsQuery(labelsQuery[1]); - } - - var labelValuesQuery = this.query.match(labelValuesRegex); - if (labelValuesQuery) { - if (labelValuesQuery[1]) { - return this.labelValuesQuery(labelValuesQuery[2], labelValuesQuery[1]); + var label_values_query = this.query.match(label_values_regex); + if (label_values_query) { + if (label_values_query[1]) { + return this.labelValuesQuery(label_values_query[2], label_values_query[1]); } else { - return this.labelValuesQuery(labelValuesQuery[2], null); + return this.labelValuesQuery(label_values_query[2], null); } } - var metricNamesQuery = this.query.match(metricNamesRegex); - if (metricNamesQuery) { - return this.metricNameQuery(metricNamesQuery[1]); + var metric_names_query = this.query.match(metric_names_regex); + if (metric_names_query) { + return this.metricNameQuery(metric_names_query[1]); } - var queryResultQuery = this.query.match(queryResultRegex); - if (queryResultQuery) { - return this.queryResultQuery(queryResultQuery[1]); + var query_result_query = this.query.match(query_result_regex); + if (query_result_query) { + return this.queryResultQuery(query_result_query[1]); } // if query contains full metric name, return metric name and label list @@ -73,71 +67,45 @@ function (_) { } }; - PrometheusMetricFindQuery.prototype.labelsQuery = function(metric) { - var url; - - url = '/api/v1/series?match[]=' + encodeURIComponent(metric) - + '&start=' + (this.range.from.valueOf() / 1000) - + '&end=' + (this.range.to.valueOf() / 1000); - - return this.datasource._request('GET', url) - .then(function(result) { - var tags = {}; - _.each(result.data.data, function(metric) { - _.each(metric, function(value, key) { - if (key === "__name__") { - return; - } - - tags[key] = key; - }); - }); - - return _.map(tags, function(value) { - return {text: value, value: value}; - }); - }); - }; - PrometheusMetricFindQuery.prototype.metricNameQuery = function(metricFilterPattern) { var url = '/api/v1/label/__name__/values'; return this.datasource._request('GET', url) - .then(function(result) { - return _.chain(result.data.data) - .filter(function(metricName) { - var r = new RegExp(metricFilterPattern); - return r.test(metricName); - }) - .map(function(matchedMetricName) { - return { - text: matchedMetricName, - expandable: true - }; - }) - .value(); - }); + .then(function(result) { + return _.chain(result.data.data) + .filter(function(metricName) { + var r = new RegExp(metricFilterPattern); + return r.test(metricName); + }) + .map(function(matchedMetricName) { + return { + text: matchedMetricName, + expandable: true + }; + }) + .value(); + }); }; PrometheusMetricFindQuery.prototype.queryResultQuery = function(query) { var url = '/api/v1/query?query=' + encodeURIComponent(query) + '&time=' + (this.range.to.valueOf() / 1000); return this.datasource._request('GET', url) - .then(function(result) { - return _.map(result.data.data.result, function(metricData) { - var text = metricData.metric.__name__ || ''; - delete metricData.metric.__name__; - text += '{' + - _.map(metricData.metric, function(v, k) { return k + '="' + v + '"'; }).join(',') + - '}'; - text += ' ' + metricData.value[1] + ' ' + metricData.value[0] * 1000; + .then(function(result) { + return _.map(result.data.data.result, function(metricData) { + var text = metricData.metric.__name__ || ''; + delete metricData.metric.__name__; + text += '{' + + _.map(metricData.metric, function(v, k) { return k + '="' + v + '"'; }).join(',') + + '}'; + text += ' ' + metricData.value[1] + ' ' + metricData.value[0] * 1000; - return { - text: text, - expandable: true - }; - }); + return { + text: text, + expandable: true + }; }); + }); }; PrometheusMetricFindQuery.prototype.metricNameAndLabelsQuery = function(query) { @@ -147,14 +115,14 @@ function (_) { var self = this; return this.datasource._request('GET', url) - .then(function(result) { - return _.map(result.data.data, function(metric) { - return { - text: self.datasource.getOriginalMetricName(metric), - expandable: true - }; - }); + .then(function(result) { + return _.map(result.data.data, function(metric) { + return { + text: self.datasource.getOriginalMetricName(metric), + expandable: true + }; }); + }); }; return PrometheusMetricFindQuery; diff --git a/public/app/plugins/datasource/prometheus/partials/query.editor.html b/public/app/plugins/datasource/prometheus/partials/query.editor.html index 359c366cb0f..37c27ace58a 100644 --- a/public/app/plugins/datasource/prometheus/partials/query.editor.html +++ b/public/app/plugins/datasource/prometheus/partials/query.editor.html @@ -1,39 +1,18 @@ - - -
- -
- -
-
- - -
- -
- - -
- -
- -
- + +
-
+ + +
+
+ +
- + diff --git a/public/app/plugins/datasource/prometheus/prom_query.ts b/public/app/plugins/datasource/prometheus/prom_query.ts deleted file mode 100644 index d73a8b1cbcf..00000000000 --- a/public/app/plugins/datasource/prometheus/prom_query.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { - QueryPartDef, - QueryPart, - functionRenderer, - identityRenderer, - quotedIdentityRenderer, -} from 'app/core/components/query_part/query_part'; - -import _ from 'lodash'; - -var index = []; -var categories = { - Functions: [], - GroupBy: [], -}; - -export class PromQuery { - target: any; - metric: string; - range: string; - filters: any[]; - functions: any[]; - templateSrv: any; - scopedVars: any; - - constructor(target, templateSrv?, scopedVars?) { - this.target = target; - - this.target.expr = this.target.expr || ''; - this.target.intervalFactor = this.target.intervalFactor || 2; - this.target.functions = this.target.functions || []; - this.target.editorMode = this.target.editorMode || true; - - this.templateSrv = templateSrv; - this.scopedVars = scopedVars; - - this.updateProjection(); - } - - updateProjection() { - this.functions = _.map(this.target.functions, function(func: any) { - return createPart(func); - }); - } - - render() { - var query = this.target.metric; - if (this.target.range) { - query += '[' + this.target.range + ']'; - } - - for (let func of this.functions) { - query = func.render(query); - } - - return query; - } - - addQueryPart(category, item) { - var partModel = createPart({type: item.text}); - partModel.def.addStrategy(this, partModel); - } -} - -export function createPart(part): any { - var def = index[part.type]; - if (!def) { - throw {message: 'Could not find query part ' + part.type}; - } - - return new QueryPart(part, def); -} - -function register(options: any) { - index[options.type] = new QueryPartDef(options); - options.category.push(index[options.type]); -} - -function addFunctionStrategy(model, partModel) { - model.functions.push(partModel); - model.target.functions.push(partModel.part); -} - -function groupByLabelRenderer(part, innerExpr) { - return innerExpr + ' by(' + part.params.join(',') + ')'; -} - -register({ - type: 'rate', - addStrategy: addFunctionStrategy, - category: categories.Functions, - params: [], - defaultParams: [], - renderer: functionRenderer, -}); - -register({ - type: 'sum', - addStrategy: addFunctionStrategy, - category: categories.Functions, - params: [], - defaultParams: [], - renderer: functionRenderer, -}); - -register({ - type: 'by', - addStrategy: addFunctionStrategy, - category: categories.Functions, - params: [ - {name: "label", type: "string", dynamicLookup: true} - ], - defaultParams: [], - renderer: groupByLabelRenderer, -}); - -export function getQueryPartCategories() { - return categories; -} diff --git a/public/app/plugins/datasource/prometheus/query_ctrl.ts b/public/app/plugins/datasource/prometheus/query_ctrl.ts index 680b1294df6..92cec328d87 100644 --- a/public/app/plugins/datasource/prometheus/query_ctrl.ts +++ b/public/app/plugins/datasource/prometheus/query_ctrl.ts @@ -6,108 +6,46 @@ import moment from 'moment'; import * as dateMath from 'app/core/utils/datemath'; import {QueryCtrl} from 'app/plugins/sdk'; -import {PromQuery, getQueryPartCategories} from './prom_query'; class PrometheusQueryCtrl extends QueryCtrl { static templateUrl = 'partials/query.editor.html'; - query: any; - metricSegment: any; - addQueryPartMenu: any[]; + metric: any; resolutions: any; oldTarget: any; suggestMetrics: any; linkToPrometheus: any; /** @ngInject */ - constructor($scope, $injector, private templateSrv, private uiSegmentSrv, private $rootScope) { + constructor($scope, $injector, private templateSrv) { super($scope, $injector); - this.query = new PromQuery(this.target, templateSrv); - - if (this.target.metric) { - this.metricSegment = uiSegmentSrv.newSegment(this.target.metric); - } else { - this.metricSegment = uiSegmentSrv.newSegment({value: 'select metric', fake: true}); - } + var target = this.target; + target.expr = target.expr || ''; + target.intervalFactor = target.intervalFactor || 2; + this.metric = ''; this.resolutions = _.map([1,2,3,4,5,10], function(f) { return {factor: f, label: '1/' + f}; }); - this.updateLink(); - this.buildQueryPartMenu(); - } + $scope.$on('typeahead-updated', () => { + this.$scope.$apply(() => { - buildQueryPartMenu() { - var categories = getQueryPartCategories(); - this.addQueryPartMenu = _.reduce(categories, function(memo, cat, key) { - var menu = { - text: key, - submenu: cat.map(item => { - return {text: item.type, value: item.type}; - }), - }; - memo.push(menu); - return memo; - }, []); - } - - addQueryPart(item, subItem) { - this.query.addQueryPart(item, subItem); - this.panelCtrl.refresh(); - } - - getPartOptions(part) { - if (part.def.type === 'by') { - return this.datasource.metricFindQuery('labels(' + this.target.metric + ')') - .then(this.transformToSegments(true)) - .catch(this.handleQueryError.bind(true)); - } - } - - partUpdated(part) { - this.target.expr = this.query.render(); - this.panelCtrl.refresh(); - } - - handleQueryError(err) { - this.$rootScope.appEvent('alert-error', ['Query failed', err.message]); - } - - transformToSegments(addTemplateVars) { - return (results) => { - var segments = _.map(results, segment => { - return this.uiSegmentSrv.newSegment({ value: segment.text, expandable: segment.expandable }); - }); - - if (addTemplateVars) { - for (let variable of this.templateSrv.variables) { - segments.unshift(this.uiSegmentSrv.newSegment({ type: 'template', value: '/^$' + variable.name + '$/', expandable: true })); - } - } - - return segments; - }; - } - - getMetricOptions() { - return this.datasource.performSuggestQuery('').then(res => { - return _.map(res, metric => { - return this.uiSegmentSrv.newSegment(metric); + this.target.expr += this.target.metric; + this.metric = ''; + this.refreshMetricData(); }); }); - } - queryChanged() { - this.target.metric = this.metricSegment.value; - this.target.expr = this.query.render(); - this.refresh(); - } + // called from typeahead so need this + // here in order to ensure this ref + this.suggestMetrics = (query, callback) => { + console.log(this); + this.datasource.performSuggestQuery(query).then(callback); + }; - toggleEditorMode() { - this.target.expr = this.query.render(false); - this.target.editorMode = !this.target.editorMode; + this.updateLink(); } refreshMetricData() { @@ -120,6 +58,10 @@ class PrometheusQueryCtrl extends QueryCtrl { updateLink() { var range = this.panelCtrl.range; + if (!range) { + return; + } + var rangeDiff = Math.ceil((range.to.valueOf() - range.from.valueOf()) / 1000); var endTime = range.to.utc().format('YYYY-MM-DD HH:mm'); var expr = { diff --git a/public/app/plugins/datasource/prometheus/specs/metric_find_query_specs.ts b/public/app/plugins/datasource/prometheus/specs/metric_find_query_specs.ts index b393f36402c..38b50d2ce79 100644 --- a/public/app/plugins/datasource/prometheus/specs/metric_find_query_specs.ts +++ b/public/app/plugins/datasource/prometheus/specs/metric_find_query_specs.ts @@ -22,22 +22,18 @@ describe('PrometheusMetricFindQuery', function() { describe('When performing metricFindQuery', function() { var results; var response; - it('label_values(resource) should generate label search query', function() { response = { status: "success", data: ["value1", "value2", "value3"] }; - ctx.$httpBackend.expect('GET', 'proxied/api/v1/label/resource/values').respond(response); var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(resource)', ctx.timeSrv); pm.process().then(function(data) { results = data; }); ctx.$httpBackend.flush(); ctx.$rootScope.$apply(); - expect(results.length).to.be(3); }); - it('label_values(metric, resource) should generate series query', function() { response = { status: "success", @@ -47,16 +43,13 @@ describe('PrometheusMetricFindQuery', function() { {__name__: "metric", resource: "value3"} ] }; - ctx.$httpBackend.expect('GET', /proxied\/api\/v1\/series\?match\[\]=metric&start=.*&end=.*/).respond(response); var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(metric, resource)', ctx.timeSrv); pm.process().then(function(data) { results = data; }); ctx.$httpBackend.flush(); ctx.$rootScope.$apply(); - expect(results.length).to.be(3); }); - it('label_values(metric, resource) should pass correct time', function() { ctx.timeSrv.setTime({ from: moment.utc('2011-01-01'), to: moment.utc('2015-01-01') }); ctx.$httpBackend.expect('GET', @@ -66,7 +59,6 @@ describe('PrometheusMetricFindQuery', function() { ctx.$httpBackend.flush(); ctx.$rootScope.$apply(); }); - it('label_values(metric{label1="foo", label2="bar", label3="baz"}, resource) should generate series query', function() { response = { status: "success", @@ -76,7 +68,6 @@ describe('PrometheusMetricFindQuery', function() { {__name__: "metric", resource: "value3"} ] }; - ctx.$httpBackend.expect('GET', /proxied\/api\/v1\/series\?match\[\]=metric&start=.*&end=.*/).respond(response); var pm = new PrometheusMetricFindQuery(ctx.ds, 'label_values(metric, resource)', ctx.timeSrv); pm.process().then(function(data) { results = data; }); @@ -84,22 +75,18 @@ describe('PrometheusMetricFindQuery', function() { ctx.$rootScope.$apply(); expect(results.length).to.be(3); }); - it('metrics(metric.*) should generate metric name query', function() { response = { status: "success", data: ["metric1","metric2","metric3","nomatch"] }; - ctx.$httpBackend.expect('GET', 'proxied/api/v1/label/__name__/values').respond(response); var pm = new PrometheusMetricFindQuery(ctx.ds, 'metrics(metric.*)', ctx.timeSrv); pm.process().then(function(data) { results = data; }); ctx.$httpBackend.flush(); ctx.$rootScope.$apply(); - expect(results.length).to.be(3); }); - it('query_result(metric) should generate metric name query', function() { response = { status: "success", @@ -111,7 +98,6 @@ describe('PrometheusMetricFindQuery', function() { }] } }; - ctx.$httpBackend.expect('GET', /proxied\/api\/v1\/query\?query=metric&time=.*/).respond(response); var pm = new PrometheusMetricFindQuery(ctx.ds, 'query_result(metric)', ctx.timeSrv); pm.process().then(function(data) { results = data; }); @@ -120,28 +106,5 @@ describe('PrometheusMetricFindQuery', function() { expect(results.length).to.be(1); expect(results[0].text).to.be('metric{job="testjob"} 3846 1443454528000'); }); - - it('labels(metric) should generate series query', function() { - response = { - status: "success", - data: [ - {__name__: "metric", resource: "value1", app: "app1"}, - {__name__: "metric", resource: "value2", app: "app2"}, - {__name__: "metric", resource: "value3", server: "server1"} - ] - }; - - ctx.$httpBackend.expect('GET', /proxied\/api\/v1\/series\?match\[\]=metric&start=.*&end=.*/).respond(response); - var pm = new PrometheusMetricFindQuery(ctx.ds, 'labels(metric)', ctx.timeSrv); - pm.process().then(function(data) { results = data; }); - ctx.$httpBackend.flush(); - ctx.$rootScope.$apply(); - - expect(results.length).to.be(3); - expect(results[0].text).to.be('resource'); - expect(results[1].text).to.be('app'); - expect(results[2].text).to.be('server'); - }); - }); }); diff --git a/public/app/plugins/datasource/prometheus/specs/query_specs.ts b/public/app/plugins/datasource/prometheus/specs/query_specs.ts deleted file mode 100644 index d58e0834f89..00000000000 --- a/public/app/plugins/datasource/prometheus/specs/query_specs.ts +++ /dev/null @@ -1,38 +0,0 @@ -import {describe, beforeEach, it, sinon, expect} from 'test/lib/common'; - -import {PromQuery} from '../prom_query'; - -describe('PromQuery', function() { - var templateSrv = {replace: val => val}; - - describe('render series with mesurement only', function() { - it('should generate correct query', function() { - var query = new PromQuery({ - metric: 'cpu', - range: '5m', - functions: [ - {type: 'rate', params: []} - ] - }, templateSrv, {}); - - var queryText = query.render(); - expect(queryText).to.be('rate(cpu[5m])'); - }); - }); - - describe('render series with group by label', function() { - it('should generate correct query', function() { - var query = new PromQuery({ - metric: 'cpu', - functions: [ - {type: 'sum', params: []}, - {type: 'by', params: ['app']}, - ] - }, templateSrv, {}); - - var queryText = query.render(); - expect(queryText).to.be('sum(cpu) by(app)'); - }); - }); - -});