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] 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() {