From 235bbc9c7ed8fa28294a23e33143a6b288d9a7ad Mon Sep 17 00:00:00 2001 From: "Haneysmith, Nathan" Date: Thu, 20 Aug 2015 11:15:36 -0700 Subject: [PATCH 001/394] custom login hints via config file --- conf/sample.ini | 3 +++ pkg/api/login.go | 1 + pkg/setting/setting.go | 2 ++ public/app/controllers/loginCtrl.js | 1 + public/app/partials/login.html | 2 +- 5 files changed, 8 insertions(+), 1 deletion(-) diff --git a/conf/sample.ini b/conf/sample.ini index 9a8d9aa3908..e8122766e9f 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -130,6 +130,9 @@ # Default role new users will be automatically assigned (if disabled above is set to true) ;auto_assign_org_role = Viewer +# Background text for the user field on the login page +;login_hint = email or username + #################################### Anonymous Auth ########################## [auth.anonymous] # enable anonymous access diff --git a/pkg/api/login.go b/pkg/api/login.go index 8863e1b10c1..d691270ad72 100644 --- a/pkg/api/login.go +++ b/pkg/api/login.go @@ -28,6 +28,7 @@ func LoginView(c *middleware.Context) { settings["googleAuthEnabled"] = setting.OAuthService.Google settings["githubAuthEnabled"] = setting.OAuthService.GitHub settings["disableUserSignUp"] = !setting.AllowUserSignUp + settings["loginHint"] = setting.LoginHint if !tryLoginUsingRememberCookie(c) { c.HTML(200, VIEW_INDEX) diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 3ac4b16db64..907d12479d8 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -79,6 +79,7 @@ var ( AllowUserOrgCreate bool AutoAssignOrg bool AutoAssignOrgRole string + LoginHint string // Http auth AdminUser string @@ -392,6 +393,7 @@ func NewConfigContext(args *CommandLineArgs) { AllowUserOrgCreate = users.Key("allow_org_create").MustBool(true) AutoAssignOrg = users.Key("auto_assign_org").MustBool(true) AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Read Only Editor", "Viewer"}) + LoginHint = users.Key("login_hint").String() // anonymous access AnonymousEnabled = Cfg.Section("auth.anonymous").Key("enabled").MustBool(false) diff --git a/public/app/controllers/loginCtrl.js b/public/app/controllers/loginCtrl.js index 40e8009b399..9d27687c5ba 100644 --- a/public/app/controllers/loginCtrl.js +++ b/public/app/controllers/loginCtrl.js @@ -19,6 +19,7 @@ function (angular, config) { $scope.googleAuthEnabled = config.googleAuthEnabled; $scope.githubAuthEnabled = config.githubAuthEnabled; $scope.disableUserSignUp = config.disableUserSignUp; + $scope.loginHint = config.loginHint; $scope.loginMode = true; $scope.submitBtnText = 'Log in'; diff --git a/public/app/partials/login.html b/public/app/partials/login.html index f311c929b66..aaff2f6fd53 100644 --- a/public/app/partials/login.html +++ b/public/app/partials/login.html @@ -26,7 +26,7 @@ User
  • - +
  • From 74ea26615734776f091e42433204ddb1ff52fc69 Mon Sep 17 00:00:00 2001 From: "Haneysmith, Nathan" Date: Thu, 20 Aug 2015 11:20:40 -0700 Subject: [PATCH 002/394] add login hint to defaults.ini --- conf/defaults.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/conf/defaults.ini b/conf/defaults.ini index 7ca5191c4e7..39899efb9ff 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -131,6 +131,9 @@ auto_assign_org = true # Default role new users will be automatically assigned (if auto_assign_org above is set to true) auto_assign_org_role = Viewer +# Background text for the user field on the login page +login_hint = email or username + #################################### Anonymous Auth ########################## [auth.anonymous] # enable anonymous access From 28ef972c9f877d5093de497e99a6fffe1f83cac1 Mon Sep 17 00:00:00 2001 From: ubhatnagar Date: Wed, 23 Sep 2015 22:13:38 +0530 Subject: [PATCH 003/394] Added duplicate feature for variable. --- public/app/features/templating/editorCtrl.js | 8 ++++++++ public/app/features/templating/partials/editor.html | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/public/app/features/templating/editorCtrl.js b/public/app/features/templating/editorCtrl.js index 74157ac3dd8..bd913e28fb5 100644 --- a/public/app/features/templating/editorCtrl.js +++ b/public/app/features/templating/editorCtrl.js @@ -96,6 +96,14 @@ function (angular, _) { } }; + $scope.duplicate = function(variable) { + $scope.current = angular.copy(variable); + $scope.variables.push($scope.current); + $scope.current = $scope.variables[$scope.variables.length - 1]; + $scope.current.name = 'copy_of_'+variable.name; + $scope.updateSubmenuVisibility(); + }; + $scope.update = function() { if ($scope.isValid()) { $scope.runQuery().then(function() { diff --git a/public/app/features/templating/partials/editor.html b/public/app/features/templating/partials/editor.html index 63ecd00adcf..43e2513e618 100644 --- a/public/app/features/templating/partials/editor.html +++ b/public/app/features/templating/partials/editor.html @@ -59,6 +59,11 @@ Edit + + + Duplicate + + From 9cdf0601eba06d63d6ea16698f257d8a8bb01432 Mon Sep 17 00:00:00 2001 From: ubhatnagar Date: Wed, 23 Sep 2015 22:20:38 +0530 Subject: [PATCH 004/394] Removed unnecessary statement. --- public/app/features/templating/editorCtrl.js | 1 - 1 file changed, 1 deletion(-) diff --git a/public/app/features/templating/editorCtrl.js b/public/app/features/templating/editorCtrl.js index bd913e28fb5..09dc300934e 100644 --- a/public/app/features/templating/editorCtrl.js +++ b/public/app/features/templating/editorCtrl.js @@ -99,7 +99,6 @@ function (angular, _) { $scope.duplicate = function(variable) { $scope.current = angular.copy(variable); $scope.variables.push($scope.current); - $scope.current = $scope.variables[$scope.variables.length - 1]; $scope.current.name = 'copy_of_'+variable.name; $scope.updateSubmenuVisibility(); }; From e70b99a706d10eadfb6ef3fc755a4added45cba6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 29 Sep 2015 18:00:15 +0200 Subject: [PATCH 005/394] poc: influxdb editor v3 test --- .../influxdb/partials/query.editor.html | 83 +++--- .../plugins/datasource/influxdb/query_ctrl.js | 12 +- .../plugins/datasource/influxdb/query_part.js | 161 ++++++++++++ .../datasource/influxdb/query_part_editor.js | 244 ++++++++++++++++++ 4 files changed, 452 insertions(+), 48 deletions(-) create mode 100644 public/app/plugins/datasource/influxdb/query_part.js create mode 100644 public/app/plugins/datasource/influxdb/query_part_editor.js diff --git a/public/app/plugins/datasource/influxdb/partials/query.editor.html b/public/app/plugins/datasource/influxdb/partials/query.editor.html index c9c7203bed7..8263db8e594 100644 --- a/public/app/plugins/datasource/influxdb/partials/query.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/query.editor.html @@ -1,4 +1,4 @@ -
    +
    • @@ -48,26 +48,22 @@
    • +
    • + WHERE +
    • +
    • + +
    +
    -
    -
      -
    • - WHERE -
    • -
    • - -
    • -
    -
    -
      @@ -75,19 +71,11 @@ SELECT
    • - +
    • -
    • - -
    • -
    • - -
    • -
    • - AS -
    • -
    • - +
    • + +
    @@ -108,30 +96,29 @@ GROUP BY
  • - time - - -
  • - -
  • - + +
  • + + + + + + + + + + + + + + + + + + + +
      @@ -146,6 +133,8 @@
    + +
    • diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.js b/public/app/plugins/datasource/influxdb/query_ctrl.js index 75dd972fbaa..061a771c623 100644 --- a/public/app/plugins/datasource/influxdb/query_ctrl.js +++ b/public/app/plugins/datasource/influxdb/query_ctrl.js @@ -2,8 +2,10 @@ define([ 'angular', 'lodash', './query_builder', + './query_part', + './query_part_editor', ], -function (angular, _, InfluxQueryBuilder) { +function (angular, _, InfluxQueryBuilder, queryPart) { 'use strict'; var module = angular.module('grafana.controllers'); @@ -17,6 +19,14 @@ function (angular, _, InfluxQueryBuilder) { target.tags = target.tags || []; target.groupBy = target.groupBy || [{type: 'time', interval: 'auto'}]; target.fields = target.fields || [{name: 'value', func: target.function || 'mean'}]; + target.fields[0].parts = [ + queryPart.create('mean', { withDefaultParams: true }), + queryPart.create('derivate', { withDefaultParams: true }), + queryPart.create('math', { withDefaultParams: true }), + queryPart.create('alias', { withDefaultParams: true }), + ]; + + $scope.func = queryPart.create('time', { withDefaultParams: true }); $scope.queryBuilder = new InfluxQueryBuilder(target); diff --git a/public/app/plugins/datasource/influxdb/query_part.js b/public/app/plugins/datasource/influxdb/query_part.js new file mode 100644 index 00000000000..44394a50cdb --- /dev/null +++ b/public/app/plugins/datasource/influxdb/query_part.js @@ -0,0 +1,161 @@ +define([ + 'lodash', + 'jquery' +], +function (_, $) { + 'use strict'; + + var index = []; + var categories = { + Combine: [], + Transform: [], + Calculate: [], + Filter: [], + Special: [] + }; + + function addFuncDef(funcDef) { + funcDef.params = funcDef.params || []; + funcDef.defaultParams = funcDef.defaultParams || []; + + if (funcDef.category) { + funcDef.category.push(funcDef); + } + index[funcDef.name] = funcDef; + index[funcDef.shortName || funcDef.name] = funcDef; + } + + addFuncDef({ + name: 'mean', + category: categories.Transform, + params: [], + defaultParams: [], + }); + + addFuncDef({ + name: 'derivate', + category: categories.Transform, + params: [{ name: "rate", type: "interval", options: ['1s', '10s', '1m', '5min', '10m', '15m', '1h'] }], + defaultParams: ['10s'], + }); + + addFuncDef({ + name: 'time', + category: categories.Transform, + params: [{ name: "rate", type: "interval", options: ['$interval', '1s', '10s', '1m', '5min', '10m', '15m', '1h'] }], + defaultParams: ['$interval'], + }); + + addFuncDef({ + name: 'math', + category: categories.Transform, + params: [{ name: "expr", type: "string"}], + defaultParams: [' / 100'], + }); + + addFuncDef({ + name: 'alias', + category: categories.Transform, + params: [{ name: "name", type: "string"}], + defaultParams: ['alias'], + }); + + _.each(categories, function(funcList, catName) { + categories[catName] = _.sortBy(funcList, 'name'); + }); + + function FuncInstance(funcDef, options) { + this.def = funcDef; + this.params = []; + + if (options && options.withDefaultParams) { + this.params = funcDef.defaultParams.slice(0); + } + + this.updateText(); + } + + FuncInstance.prototype.render = function(metricExp) { + var str = this.def.name + '('; + var parameters = _.map(this.params, function(value, index) { + + var paramType = this.def.params[index].type; + if (paramType === 'int' || paramType === 'value_or_series' || paramType === 'boolean') { + return value; + } + else if (paramType === 'int_or_interval' && $.isNumeric(value)) { + return value; + } + + return "'" + value + "'"; + + }, this); + + if (metricExp) { + parameters.unshift(metricExp); + } + + return str + parameters.join(', ') + ')'; + }; + + FuncInstance.prototype._hasMultipleParamsInString = function(strValue, index) { + if (strValue.indexOf(',') === -1) { + return false; + } + + return this.def.params[index + 1] && this.def.params[index + 1].optional; + }; + + FuncInstance.prototype.updateParam = function(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, 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.updateText(); + }; + + FuncInstance.prototype.updateText = function () { + if (this.params.length === 0) { + this.text = this.def.name + '()'; + return; + } + + var text = this.def.name + '('; + text += this.params.join(', '); + text += ')'; + this.text = text; + }; + + return { + create: function(funcDef, options) { + if (_.isString(funcDef)) { + if (!index[funcDef]) { + throw { message: 'Method not found ' + name }; + } + funcDef = index[funcDef]; + } + return new FuncInstance(funcDef, options); + }, + + getFuncDef: function(name) { + return index[name]; + }, + + getCategories: function() { + return categories; + } + }; + +}); diff --git a/public/app/plugins/datasource/influxdb/query_part_editor.js b/public/app/plugins/datasource/influxdb/query_part_editor.js new file mode 100644 index 00000000000..6c1f268a9d1 --- /dev/null +++ b/public/app/plugins/datasource/influxdb/query_part_editor.js @@ -0,0 +1,244 @@ +define([ + 'angular', + 'lodash', + 'jquery', +], +function (angular, _, $) { + 'use strict'; + + angular + .module('grafana.directives') + .directive('influxQueryPartEditor', function($compile, templateSrv) { + + var funcSpanTemplate = '{{func.def.name}}('; + var paramTemplate = ''; + + var funcControlsTemplate = + '
      ' + + '' + + '' + + '' + + '' + + '
      '; + + return { + restrict: 'A', + link: function postLink($scope, elem) { + var $funcLink = $(funcSpanTemplate); + var $funcControls = $(funcControlsTemplate); + var func = $scope.func; + var funcDef = func.def; + var scheduledRelink = false; + var paramCountAtLink = 0; + + function clickFuncParam(paramIndex) { + /*jshint validthis:true */ + + var $link = $(this); + var $input = $link.next(); + + $input.val(func.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 scheduledRelinkIfNeeded() { + if (paramCountAtLink === func.params.length) { + return; + } + + if (!scheduledRelink) { + scheduledRelink = true; + setTimeout(function() { + relink(); + scheduledRelink = false; + }, 200); + } + } + + function inputBlur(paramIndex) { + /*jshint validthis:true */ + var $input = $(this); + var $link = $input.prev(); + var newValue = $input.val(); + + if (newValue !== '' || func.def.params[paramIndex].optional) { + $link.html(templateSrv.highlightVariablesAsHtml(newValue)); + + func.updateParam($input.val(), paramIndex); + scheduledRelinkIfNeeded(); + + $scope.$apply($scope.targetChanged); + } + + $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, paramIndex) { + $input.attr('data-provide', 'typeahead'); + + var options = funcDef.params[paramIndex].options; + if (funcDef.params[paramIndex].type === 'int') { + options = _.map(options, function(val) { return val.toString(); }); + } + + $input.typeahead({ + source: options, + minLength: 0, + items: 20, + 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() || ''; + return this.process(this.source); + }; + } + + function toggleFuncControls() { + var targetDiv = elem.closest('.tight-form'); + + if (elem.hasClass('show-function-controls')) { + elem.removeClass('show-function-controls'); + targetDiv.removeClass('has-open-function'); + $funcControls.hide(); + return; + } + + elem.addClass('show-function-controls'); + targetDiv.addClass('has-open-function'); + + $funcControls.show(); + } + + function addElementsAndCompile() { + $funcControls.appendTo(elem); + $funcLink.appendTo(elem); + + _.each(funcDef.params, function(param, index) { + if (param.optional && func.params.length <= index) { + return; + } + + if (index > 0) { + $(', ').appendTo(elem); + } + + var paramValue = templateSrv.highlightVariablesAsHtml(func.params[index]); + var $paramLink = $('' + paramValue + ''); + var $input = $(paramTemplate); + + paramCountAtLink++; + + $paramLink.appendTo(elem); + $input.appendTo(elem); + + $input.blur(_.partial(inputBlur, index)); + $input.keyup(inputKeyDown); + $input.keypress(_.partial(inputKeyPress, index)); + $paramLink.click(_.partial(clickFuncParam, index)); + + if (funcDef.params[index].options) { + addTypeahead($input, index); + } + + }); + + $(')').appendTo(elem); + + $compile(elem.contents())($scope); + } + + function ifJustAddedFocusFistParam() { + if ($scope.func.added) { + $scope.func.added = false; + setTimeout(function() { + elem.find('.graphite-func-param-link').first().click(); + }, 10); + } + } + + function registerFuncControlsToggle() { + $funcLink.click(toggleFuncControls); + } + + function registerFuncControlsActions() { + $funcControls.click(function(e) { + var $target = $(e.target); + if ($target.hasClass('fa-remove')) { + toggleFuncControls(); + $scope.$apply(function() { + $scope.removeFunction($scope.func); + }); + return; + } + + if ($target.hasClass('fa-arrow-left')) { + $scope.$apply(function() { + _.move($scope.functions, $scope.$index, $scope.$index - 1); + $scope.targetChanged(); + }); + return; + } + + if ($target.hasClass('fa-arrow-right')) { + $scope.$apply(function() { + _.move($scope.functions, $scope.$index, $scope.$index + 1); + $scope.targetChanged(); + }); + return; + } + + if ($target.hasClass('fa-question-circle')) { + window.open("http://graphite.readthedocs.org/en/latest/functions.html#graphite.render.functions." + funcDef.name,'_blank'); + return; + } + }); + } + + function relink() { + elem.children().remove(); + + addElementsAndCompile(); + ifJustAddedFocusFistParam(); + registerFuncControlsToggle(); + registerFuncControlsActions(); + } + + relink(); + } + }; + + }); + +}); From 2dc8fcd3be17d03b476c4e994c7288d5a38bf710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 29 Sep 2015 21:32:15 +0200 Subject: [PATCH 006/394] poc(influxdb v3 editor): more testing of new influxdb editor approach --- .../influxdb/partials/query.editor.html | 7 ++---- .../plugins/datasource/influxdb/query_ctrl.js | 23 ++++++++++++++----- .../plugins/datasource/influxdb/query_part.js | 7 ++++++ .../datasource/influxdb/query_part_editor.js | 2 -- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/public/app/plugins/datasource/influxdb/partials/query.editor.html b/public/app/plugins/datasource/influxdb/partials/query.editor.html index 8263db8e594..9fb39ad5abe 100644 --- a/public/app/plugins/datasource/influxdb/partials/query.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/query.editor.html @@ -65,15 +65,12 @@
      -
      +
      • SELECT
      • -
      • - -
      • -
      • +
      • diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.js b/public/app/plugins/datasource/influxdb/query_ctrl.js index 061a771c623..247205ab39d 100644 --- a/public/app/plugins/datasource/influxdb/query_ctrl.js +++ b/public/app/plugins/datasource/influxdb/query_ctrl.js @@ -18,14 +18,25 @@ function (angular, _, InfluxQueryBuilder, queryPart) { var target = $scope.target; target.tags = target.tags || []; target.groupBy = target.groupBy || [{type: 'time', interval: 'auto'}]; - target.fields = target.fields || [{name: 'value', func: target.function || 'mean'}]; - target.fields[0].parts = [ - queryPart.create('mean', { withDefaultParams: true }), - queryPart.create('derivate', { withDefaultParams: true }), - queryPart.create('math', { withDefaultParams: true }), - queryPart.create('alias', { withDefaultParams: true }), + target.fields = target.fields || [{name: 'value'}]; + target.select = target.select || [[{type: 'field', params: ['value']}]]; + target.select[0] = [ + {type: 'field', params: ['value']}, + {type: 'mean', params: []}, + {type: 'derivate', params: ['10s']}, + {type: 'math', params: ['/ 100']}, + {type: 'alias', params: ['google']}, ]; + $scope.select = _.map(target.select, function(parts) { + return _.map(parts, function(part) { + var partModel = queryPart.create(part.type); + partModel.params = part.params; + partModel.updateText(); + return partModel; + }); + }); + $scope.func = queryPart.create('time', { withDefaultParams: true }); $scope.queryBuilder = new InfluxQueryBuilder(target); diff --git a/public/app/plugins/datasource/influxdb/query_part.js b/public/app/plugins/datasource/influxdb/query_part.js index 44394a50cdb..37433d54951 100644 --- a/public/app/plugins/datasource/influxdb/query_part.js +++ b/public/app/plugins/datasource/influxdb/query_part.js @@ -25,6 +25,13 @@ function (_, $) { index[funcDef.shortName || funcDef.name] = funcDef; } + addFuncDef({ + name: 'field', + category: categories.Transform, + params: [{type: 'field'}], + defaultParams: ['value'], + }); + addFuncDef({ name: 'mean', category: categories.Transform, diff --git a/public/app/plugins/datasource/influxdb/query_part_editor.js b/public/app/plugins/datasource/influxdb/query_part_editor.js index 6c1f268a9d1..fd0dc3e1a6b 100644 --- a/public/app/plugins/datasource/influxdb/query_part_editor.js +++ b/public/app/plugins/datasource/influxdb/query_part_editor.js @@ -16,10 +16,8 @@ function (angular, _, $) { var funcControlsTemplate = '
        ' + - '' + '' + '' + - '' + '
        '; return { From f053b416453cf2ed702e7a4b40a769bd9417417d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 30 Sep 2015 14:37:27 +0200 Subject: [PATCH 007/394] feat(influxdb editor): more progress --- .../influxdb/partials/query.editor.html | 13 +- .../influxdb/partials/query_part.html | 6 + .../plugins/datasource/influxdb/query_ctrl.js | 42 ++--- .../plugins/datasource/influxdb/query_part.js | 168 ------------------ .../plugins/datasource/influxdb/query_part.ts | 165 +++++++++++++++++ .../datasource/influxdb/query_part_editor.js | 139 +++------------ 6 files changed, 225 insertions(+), 308 deletions(-) create mode 100644 public/app/plugins/datasource/influxdb/partials/query_part.html delete mode 100644 public/app/plugins/datasource/influxdb/query_part.js create mode 100644 public/app/plugins/datasource/influxdb/query_part.ts diff --git a/public/app/plugins/datasource/influxdb/partials/query.editor.html b/public/app/plugins/datasource/influxdb/partials/query.editor.html index 9fb39ad5abe..af40de12302 100644 --- a/public/app/plugins/datasource/influxdb/partials/query.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/query.editor.html @@ -65,22 +65,20 @@
        -
        +
        • SELECT
        • -
        • - - +
        • +
        -
        • -
        • +
        @@ -93,8 +91,7 @@ GROUP BY
      • - - +
      • diff --git a/public/app/plugins/datasource/influxdb/partials/query_part.html b/public/app/plugins/datasource/influxdb/partials/query_part.html new file mode 100644 index 00000000000..0a28bee11f7 --- /dev/null +++ b/public/app/plugins/datasource/influxdb/partials/query_part.html @@ -0,0 +1,6 @@ +
        + + +
        + +{{part.def.name}}() diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.js b/public/app/plugins/datasource/influxdb/query_ctrl.js index 247205ab39d..d4c4a7f6181 100644 --- a/public/app/plugins/datasource/influxdb/query_ctrl.js +++ b/public/app/plugins/datasource/influxdb/query_ctrl.js @@ -10,7 +10,7 @@ function (angular, _, InfluxQueryBuilder, queryPart) { var module = angular.module('grafana.controllers'); - module.controller('InfluxQueryCtrl', function($scope, $timeout, $sce, templateSrv, $q, uiSegmentSrv) { + module.controller('InfluxQueryCtrl', function($scope, templateSrv, $q, uiSegmentSrv) { $scope.init = function() { if (!$scope.target) { return; } @@ -19,25 +19,14 @@ function (angular, _, InfluxQueryBuilder, queryPart) { target.tags = target.tags || []; target.groupBy = target.groupBy || [{type: 'time', interval: 'auto'}]; target.fields = target.fields || [{name: 'value'}]; - target.select = target.select || [[{type: 'field', params: ['value']}]]; - target.select[0] = [ - {type: 'field', params: ['value']}, - {type: 'mean', params: []}, - {type: 'derivate', params: ['10s']}, - {type: 'math', params: ['/ 100']}, - {type: 'alias', params: ['google']}, - ]; + target.select = target.select || [[ + {name: 'field', params: ['value']}, + {name: 'mean', params: []}, + ]]; - $scope.select = _.map(target.select, function(parts) { - return _.map(parts, function(part) { - var partModel = queryPart.create(part.type); - partModel.params = part.params; - partModel.updateText(); - return partModel; - }); - }); + $scope.updateSelectParts(); - $scope.func = queryPart.create('time', { withDefaultParams: true }); + $scope.groupByParts = queryPart.create({name: 'time', params:['$interval']}); $scope.queryBuilder = new InfluxQueryBuilder(target); @@ -89,14 +78,27 @@ function (angular, _, InfluxQueryBuilder, queryPart) { }; $scope.addSelect = function() { - $scope.target.fields.push({name: "select field", func: 'mean'}); + $scope.target.select.push([ + {name: 'field', params: ['value']}, + {name: 'mean', params: []}, + ]); + $scope.updateSelectParts(); }; $scope.removeSelect = function(index) { - $scope.target.fields.splice(index, 1); + $scope.target.select.splice(index, 1); + $scope.updateSelectParts(); $scope.get_data(); }; + $scope.updateSelectParts = function() { + $scope.selectParts = _.map($scope.target.select, function(parts) { + return _.map(parts, function(part) { + return queryPart.create(part); + }); + }); + }; + $scope.changeFunction = function(func) { $scope.target.function = func; $scope.$parent.get_data(); diff --git a/public/app/plugins/datasource/influxdb/query_part.js b/public/app/plugins/datasource/influxdb/query_part.js deleted file mode 100644 index 37433d54951..00000000000 --- a/public/app/plugins/datasource/influxdb/query_part.js +++ /dev/null @@ -1,168 +0,0 @@ -define([ - 'lodash', - 'jquery' -], -function (_, $) { - 'use strict'; - - var index = []; - var categories = { - Combine: [], - Transform: [], - Calculate: [], - Filter: [], - Special: [] - }; - - function addFuncDef(funcDef) { - funcDef.params = funcDef.params || []; - funcDef.defaultParams = funcDef.defaultParams || []; - - if (funcDef.category) { - funcDef.category.push(funcDef); - } - index[funcDef.name] = funcDef; - index[funcDef.shortName || funcDef.name] = funcDef; - } - - addFuncDef({ - name: 'field', - category: categories.Transform, - params: [{type: 'field'}], - defaultParams: ['value'], - }); - - addFuncDef({ - name: 'mean', - category: categories.Transform, - params: [], - defaultParams: [], - }); - - addFuncDef({ - name: 'derivate', - category: categories.Transform, - params: [{ name: "rate", type: "interval", options: ['1s', '10s', '1m', '5min', '10m', '15m', '1h'] }], - defaultParams: ['10s'], - }); - - addFuncDef({ - name: 'time', - category: categories.Transform, - params: [{ name: "rate", type: "interval", options: ['$interval', '1s', '10s', '1m', '5min', '10m', '15m', '1h'] }], - defaultParams: ['$interval'], - }); - - addFuncDef({ - name: 'math', - category: categories.Transform, - params: [{ name: "expr", type: "string"}], - defaultParams: [' / 100'], - }); - - addFuncDef({ - name: 'alias', - category: categories.Transform, - params: [{ name: "name", type: "string"}], - defaultParams: ['alias'], - }); - - _.each(categories, function(funcList, catName) { - categories[catName] = _.sortBy(funcList, 'name'); - }); - - function FuncInstance(funcDef, options) { - this.def = funcDef; - this.params = []; - - if (options && options.withDefaultParams) { - this.params = funcDef.defaultParams.slice(0); - } - - this.updateText(); - } - - FuncInstance.prototype.render = function(metricExp) { - var str = this.def.name + '('; - var parameters = _.map(this.params, function(value, index) { - - var paramType = this.def.params[index].type; - if (paramType === 'int' || paramType === 'value_or_series' || paramType === 'boolean') { - return value; - } - else if (paramType === 'int_or_interval' && $.isNumeric(value)) { - return value; - } - - return "'" + value + "'"; - - }, this); - - if (metricExp) { - parameters.unshift(metricExp); - } - - return str + parameters.join(', ') + ')'; - }; - - FuncInstance.prototype._hasMultipleParamsInString = function(strValue, index) { - if (strValue.indexOf(',') === -1) { - return false; - } - - return this.def.params[index + 1] && this.def.params[index + 1].optional; - }; - - FuncInstance.prototype.updateParam = function(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, 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.updateText(); - }; - - FuncInstance.prototype.updateText = function () { - if (this.params.length === 0) { - this.text = this.def.name + '()'; - return; - } - - var text = this.def.name + '('; - text += this.params.join(', '); - text += ')'; - this.text = text; - }; - - return { - create: function(funcDef, options) { - if (_.isString(funcDef)) { - if (!index[funcDef]) { - throw { message: 'Method not found ' + name }; - } - funcDef = index[funcDef]; - } - return new FuncInstance(funcDef, options); - }, - - getFuncDef: function(name) { - return index[name]; - }, - - getCategories: function() { - return categories; - } - }; - -}); diff --git a/public/app/plugins/datasource/influxdb/query_part.ts b/public/app/plugins/datasource/influxdb/query_part.ts new file mode 100644 index 00000000000..7e5dfff3277 --- /dev/null +++ b/public/app/plugins/datasource/influxdb/query_part.ts @@ -0,0 +1,165 @@ +/// + +import _ = require('lodash'); + +var index = []; +var categories = { + Combine: [], + Transform: [], + Calculate: [], + Filter: [], + Special: [] +}; + +class QueryPartDef { + name: string; + params: any[]; + defaultParams: any[]; + + constructor(options: any) { + this.name = options.name; + this.params = options.params; + this.defaultParams = options.defaultParams; + } + + static register(options: any) { + index[options.name] = new QueryPartDef(options); + } +} + +QueryPartDef.register({ + name: 'field', + category: categories.Transform, + params: [{type: 'field'}], + defaultParams: ['value'], +}); + +QueryPartDef.register({ + name: 'mean', + category: categories.Transform, + params: [], + defaultParams: [], +}); + +QueryPartDef.register({ + name: 'derivate', + category: categories.Transform, + params: [{ name: "rate", type: "interval", options: ['1s', '10s', '1m', '5min', '10m', '15m', '1h'] }], + defaultParams: ['10s'], +}); + +QueryPartDef.register({ + name: 'time', + category: categories.Transform, + params: [{ name: "rate", type: "interval", options: ['$interval', '1s', '10s', '1m', '5min', '10m', '15m', '1h'] }], + defaultParams: ['$interval'], +}); + +QueryPartDef.register({ + name: 'math', + category: categories.Transform, + params: [{ name: "expr", type: "string"}], + defaultParams: [' / 100'], +}); + +QueryPartDef.register({ + name: 'alias', + category: categories.Transform, + params: [{ name: "name", type: "string"}], + defaultParams: ['alias'], +}); + +class QueryPart { + part: any; + def: QueryPartDef; + params: any[]; + text: string; + + constructor(part: any) { + this.part = part; + this.def = index[part.name]; + if (!this.def) { + throw {message: 'Could not find query part ' + part.name}; + } + + this.params = part.params || _.clone(this.def.defaultParams); + } + + render(innerExpr: string) { + var str = this.def.name + '('; + var parameters = _.map(this.params, (value, index) => { + + var paramType = this.def.params[index].type; + if (paramType === 'int' || paramType === 'value_or_series' || paramType === 'boolean') { + return value; + } + else if (paramType === 'int_or_interval' && _.isNumber(value)) { + return value; + } + + return "'" + value + "'"; + + }); + + if (innerExpr) { + parameters.unshift(innerExpr); + } + + return str + parameters.join(', ') + ')'; + } + + 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.name + '()'; + return; + } + + var text = this.def.name + '('; + text += this.params.join(', '); + text += ')'; + this.text = text; + } +} + +export = { + create: function(part): any { + return new QueryPart(part); + }, + + getFuncDef: function(name) { + return index[name]; + }, + + getCategories: function() { + return categories; + } +}; diff --git a/public/app/plugins/datasource/influxdb/query_part_editor.js b/public/app/plugins/datasource/influxdb/query_part_editor.js index fd0dc3e1a6b..6cfcda5f4e7 100644 --- a/public/app/plugins/datasource/influxdb/query_part_editor.js +++ b/public/app/plugins/datasource/influxdb/query_part_editor.js @@ -10,33 +10,26 @@ function (angular, _, $) { .module('grafana.directives') .directive('influxQueryPartEditor', function($compile, templateSrv) { - var funcSpanTemplate = '{{func.def.name}}('; var paramTemplate = ''; - - var funcControlsTemplate = - '
        ' + - '' + - '' + - '
        '; - return { - restrict: 'A', + restrict: 'E', + templateUrl: 'app/plugins/datasource/influxdb/partials/query_part.html', + scope: { + part: "=" + }, link: function postLink($scope, elem) { - var $funcLink = $(funcSpanTemplate); - var $funcControls = $(funcControlsTemplate); - var func = $scope.func; - var funcDef = func.def; - var scheduledRelink = false; - var paramCountAtLink = 0; + 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(func.params[paramIndex]); + $input.val(part.params[paramIndex]); $input.css('width', ($link.width() + 16) + 'px'); $link.hide(); @@ -51,32 +44,16 @@ function (angular, _, $) { } } - function scheduledRelinkIfNeeded() { - if (paramCountAtLink === func.params.length) { - return; - } - - if (!scheduledRelink) { - scheduledRelink = true; - setTimeout(function() { - relink(); - scheduledRelink = false; - }, 200); - } - } - function inputBlur(paramIndex) { /*jshint validthis:true */ var $input = $(this); var $link = $input.prev(); var newValue = $input.val(); - if (newValue !== '' || func.def.params[paramIndex].optional) { + if (newValue !== '' || part.def.params[paramIndex].optional) { $link.html(templateSrv.highlightVariablesAsHtml(newValue)); - func.updateParam($input.val(), paramIndex); - scheduledRelinkIfNeeded(); - + part.updateParam($input.val(), paramIndex); $scope.$apply($scope.targetChanged); } @@ -99,8 +76,8 @@ function (angular, _, $) { function addTypeahead($input, paramIndex) { $input.attr('data-provide', 'typeahead'); - var options = funcDef.params[paramIndex].options; - if (funcDef.params[paramIndex].type === 'int') { + var options = partDef.params[paramIndex].options; + if (partDef.params[paramIndex].type === 'int') { options = _.map(options, function(val) { return val.toString(); }); } @@ -123,114 +100,52 @@ function (angular, _, $) { }; } - function toggleFuncControls() { + $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'); - $funcControls.hide(); + $controlsContainer.hide(); return; } elem.addClass('show-function-controls'); targetDiv.addClass('has-open-function'); - - $funcControls.show(); - } + $controlsContainer.show(); + }; function addElementsAndCompile() { - $funcControls.appendTo(elem); - $funcLink.appendTo(elem); - - _.each(funcDef.params, function(param, index) { - if (param.optional && func.params.length <= index) { + _.each(partDef.params, function(param, index) { + if (param.optional && part.params.length <= index) { return; } if (index > 0) { - $(', ').appendTo(elem); + $(', ').appendTo($paramsContainer); } - var paramValue = templateSrv.highlightVariablesAsHtml(func.params[index]); - var $paramLink = $('' + paramValue + ''); + var paramValue = templateSrv.highlightVariablesAsHtml(part.params[index]); + var $paramLink = $('' + paramValue + ''); var $input = $(paramTemplate); - paramCountAtLink++; - - $paramLink.appendTo(elem); - $input.appendTo(elem); + $paramLink.appendTo($paramsContainer); + $input.appendTo($paramsContainer); $input.blur(_.partial(inputBlur, index)); $input.keyup(inputKeyDown); $input.keypress(_.partial(inputKeyPress, index)); $paramLink.click(_.partial(clickFuncParam, index)); - if (funcDef.params[index].options) { + if (partDef.params[index].options) { addTypeahead($input, index); } - - }); - - $(')').appendTo(elem); - - $compile(elem.contents())($scope); - } - - function ifJustAddedFocusFistParam() { - if ($scope.func.added) { - $scope.func.added = false; - setTimeout(function() { - elem.find('.graphite-func-param-link').first().click(); - }, 10); - } - } - - function registerFuncControlsToggle() { - $funcLink.click(toggleFuncControls); - } - - function registerFuncControlsActions() { - $funcControls.click(function(e) { - var $target = $(e.target); - if ($target.hasClass('fa-remove')) { - toggleFuncControls(); - $scope.$apply(function() { - $scope.removeFunction($scope.func); - }); - return; - } - - if ($target.hasClass('fa-arrow-left')) { - $scope.$apply(function() { - _.move($scope.functions, $scope.$index, $scope.$index - 1); - $scope.targetChanged(); - }); - return; - } - - if ($target.hasClass('fa-arrow-right')) { - $scope.$apply(function() { - _.move($scope.functions, $scope.$index, $scope.$index + 1); - $scope.targetChanged(); - }); - return; - } - - if ($target.hasClass('fa-question-circle')) { - window.open("http://graphite.readthedocs.org/en/latest/functions.html#graphite.render.functions." + funcDef.name,'_blank'); - return; - } }); } function relink() { - elem.children().remove(); - + $paramsContainer.empty(); addElementsAndCompile(); - ifJustAddedFocusFistParam(); - registerFuncControlsToggle(); - registerFuncControlsActions(); } relink(); From 83052352dc5f310190449153310cdd9bb7863e6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 30 Sep 2015 17:16:34 +0200 Subject: [PATCH 008/394] feat(influxdb editor): lots of work on new editor, #2856 --- .../datasource/influxdb/influx_query.ts | 141 ++++++++++++++++++ .../influxdb/partials/query.editor.html | 8 +- .../datasource/influxdb/query_builder.js | 3 +- .../plugins/datasource/influxdb/query_ctrl.js | 77 ++-------- .../plugins/datasource/influxdb/query_part.ts | 80 ++++++---- .../influxdb/specs/influx_query_specs.ts | 36 +++++ .../influxdb/specs/query_builder_specs.ts | 66 ++++---- .../influxdb/specs/query_part_specs.ts | 41 +++++ 8 files changed, 317 insertions(+), 135 deletions(-) create mode 100644 public/app/plugins/datasource/influxdb/influx_query.ts create mode 100644 public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts create mode 100644 public/app/plugins/datasource/influxdb/specs/query_part_specs.ts diff --git a/public/app/plugins/datasource/influxdb/influx_query.ts b/public/app/plugins/datasource/influxdb/influx_query.ts new file mode 100644 index 00000000000..bdc4ffa8e73 --- /dev/null +++ b/public/app/plugins/datasource/influxdb/influx_query.ts @@ -0,0 +1,141 @@ +/// +/// + +import _ = require('lodash'); +import queryPart = require('./query_part'); + +declare var InfluxQueryBuilder: any; + +class InfluxQuery { + target: any; + selectParts: any[]; + groupByParts: any; + queryBuilder: any; + + constructor(target) { + this.target = target; + + target.tags = target.tags || []; + target.groupBy = target.groupBy || [{type: 'time', interval: 'auto'}]; + target.select = target.select || [[ + {name: 'mean', params: ['value']}, + ]]; + + this.updateSelectParts(); + this.groupByParts = [ + queryPart.create({name: 'time', params: ['$interval']}) + ]; + } + + updateSelectParts() { + this.selectParts = _.map(this.target.select, function(parts: any) { + return _.map(parts, function(part: any) { + return queryPart.create(part); + }); + }); + } + + removeSelect(index: number) { + this.target.select.splice(index, 1); + this.updateSelectParts(); + } + + addSelect() { + this.target.select.push([ + {name: 'mean', params: ['value']}, + ]); + this.updateSelectParts(); + } + + private renderTagCondition(tag, index) { + var str = ""; + var operator = tag.operator; + var value = tag.value; + if (index > 0) { + str = (tag.condition || 'AND') + ' '; + } + + if (!operator) { + if (/^\/.*\/$/.test(tag.value)) { + operator = '=~'; + } else { + operator = '='; + } + } + + // quote value unless regex + if (operator !== '=~' && operator !== '!~') { + value = "'" + value + "'"; + } + + return str + '"' + tag.key + '" ' + operator + ' ' + value; + } + + private getGroupByTimeInterval(interval) { + if (interval === 'auto') { + return '$interval'; + } + return interval; + } + + render() { + var target = this.target; + + if (!target.measurement) { + throw "Metric measurement is missing"; + } + + if (!target.fields) { + target.fields = [{name: 'value', func: target.function || 'mean'}]; + } + + var query = 'SELECT '; + var i, y; + for (i = 0; i < this.selectParts.length; i++) { + let parts = this.selectParts[i]; + var selectText = ""; + for (y = 0; y < parts.length; y++) { + let part = parts[y]; + selectText = part.render(selectText); + } + + if (i > 0) { + query += ', '; + } + query += selectText; + } + + var measurement = target.measurement; + if (!measurement.match('^/.*/') && !measurement.match(/^merge\(.*\)/)) { + measurement = '"' + measurement+ '"'; + } + + query += ' FROM ' + measurement + ' WHERE '; + var conditions = _.map(target.tags, (tag, index) => { + return this.renderTagCondition(tag, index); + }); + + query += conditions.join(' '); + query += (conditions.length > 0 ? ' AND ' : '') + '$timeFilter'; + + query += ' GROUP BY'; + for (i = 0; i < target.groupBy.length; i++) { + var group = target.groupBy[i]; + if (group.type === 'time') { + query += ' time(' + this.getGroupByTimeInterval(group.interval) + ')'; + } else { + query += ', "' + group.key + '"'; + } + } + + if (target.fill) { + query += ' fill(' + target.fill + ')'; + } + + target.query = query; + + return query; + } +} + +export = InfluxQuery; diff --git a/public/app/plugins/datasource/influxdb/partials/query.editor.html b/public/app/plugins/datasource/influxdb/partials/query.editor.html index af40de12302..1993c2d7aa9 100644 --- a/public/app/plugins/datasource/influxdb/partials/query.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/query.editor.html @@ -65,7 +65,7 @@
        -
        +
        • SELECT @@ -85,13 +85,13 @@
        -
        +
        • GROUP BY
        • -
        • - +
        • +
        • diff --git a/public/app/plugins/datasource/influxdb/query_builder.js b/public/app/plugins/datasource/influxdb/query_builder.js index 9ab0953b576..84b8b9c692c 100644 --- a/public/app/plugins/datasource/influxdb/query_builder.js +++ b/public/app/plugins/datasource/influxdb/query_builder.js @@ -4,8 +4,9 @@ define([ function (_) { 'use strict'; - function InfluxQueryBuilder(target) { + function InfluxQueryBuilder(target, queryModel) { this.target = target; + this.model = queryModel; if (target.groupByTags) { target.groupBy = [{type: 'time', interval: 'auto'}]; diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.js b/public/app/plugins/datasource/influxdb/query_ctrl.js index d4c4a7f6181..924c7ab77fe 100644 --- a/public/app/plugins/datasource/influxdb/query_ctrl.js +++ b/public/app/plugins/datasource/influxdb/query_ctrl.js @@ -2,10 +2,10 @@ define([ 'angular', 'lodash', './query_builder', - './query_part', + './influx_query', './query_part_editor', ], -function (angular, _, InfluxQueryBuilder, queryPart) { +function (angular, _, InfluxQueryBuilder, InfluxQuery) { 'use strict'; var module = angular.module('grafana.controllers'); @@ -15,29 +15,18 @@ function (angular, _, InfluxQueryBuilder, queryPart) { $scope.init = function() { if (!$scope.target) { return; } - var target = $scope.target; - target.tags = target.tags || []; - target.groupBy = target.groupBy || [{type: 'time', interval: 'auto'}]; - target.fields = target.fields || [{name: 'value'}]; - target.select = target.select || [[ - {name: 'field', params: ['value']}, - {name: 'mean', params: []}, - ]]; + $scope.target = $scope.target; + $scope.queryModel = new InfluxQuery($scope.target); + $scope.queryBuilder = new InfluxQueryBuilder($scope.target); - $scope.updateSelectParts(); - - $scope.groupByParts = queryPart.create({name: 'time', params:['$interval']}); - - $scope.queryBuilder = new InfluxQueryBuilder(target); - - if (!target.measurement) { + if (!$scope.target.measurement) { $scope.measurementSegment = uiSegmentSrv.newSelectMeasurement(); } else { - $scope.measurementSegment = uiSegmentSrv.newSegment(target.measurement); + $scope.measurementSegment = uiSegmentSrv.newSegment($scope.target.measurement); } $scope.tagSegments = []; - _.each(target.tags, function(tag) { + _.each($scope.target.tags, function(tag) { if (!tag.operator) { if (/^\/.*\/$/.test(tag.value)) { tag.operator = "=~"; @@ -78,32 +67,14 @@ function (angular, _, InfluxQueryBuilder, queryPart) { }; $scope.addSelect = function() { - $scope.target.select.push([ - {name: 'field', params: ['value']}, - {name: 'mean', params: []}, - ]); - $scope.updateSelectParts(); + $scope.queryModel.addSelect(); }; $scope.removeSelect = function(index) { - $scope.target.select.splice(index, 1); - $scope.updateSelectParts(); + $scope.queryModel.removeSelect(index); $scope.get_data(); }; - $scope.updateSelectParts = function() { - $scope.selectParts = _.map($scope.target.select, function(parts) { - return _.map(parts, function(part) { - return queryPart.create(part); - }); - }); - }; - - $scope.changeFunction = function(func) { - $scope.target.function = func; - $scope.$parent.get_data(); - }; - $scope.measurementChanged = function() { $scope.target.measurement = $scope.measurementSegment.value; $scope.$parent.get_data(); @@ -125,22 +96,6 @@ function (angular, _, InfluxQueryBuilder, queryPart) { .then($scope.transformToSegments(true), $scope.handleQueryError); }; - $scope.getFunctions = function () { - var functionList = ['count', 'mean', 'sum', 'min', 'max', 'mode', 'distinct', 'median', - 'stddev', 'first', 'last' - ]; - return $q.when(_.map(functionList, function(func) { - return uiSegmentSrv.newSegment(func); - })); - }; - - $scope.getGroupByTimeIntervals = function () { - var times = ['auto', '1s', '10s', '1m', '2m', '5m', '10m', '30m', '1h', '1d']; - return $q.when(_.map(times, function(func) { - return uiSegmentSrv.newSegment(func); - })); - }; - $scope.handleQueryError = function(err) { $scope.parserError = err.message || 'Failed to issue metric query'; return []; @@ -202,18 +157,6 @@ function (angular, _, InfluxQueryBuilder, queryPart) { .then(null, $scope.handleQueryError); }; - $scope.addField = function() { - $scope.target.fields.push({name: $scope.addFieldSegment.value, func: 'mean'}); - _.extend($scope.addFieldSegment, uiSegmentSrv.newPlusButton()); - }; - - $scope.fieldChanged = function(field) { - if (field.name === '-- remove from select --') { - $scope.target.fields = _.without($scope.target.fields, field); - } - $scope.get_data(); - }; - $scope.getTagOptions = function() { var query = $scope.queryBuilder.buildExploreQuery('TAG_KEYS'); diff --git a/public/app/plugins/datasource/influxdb/query_part.ts b/public/app/plugins/datasource/influxdb/query_part.ts index 7e5dfff3277..9d6d92f569c 100644 --- a/public/app/plugins/datasource/influxdb/query_part.ts +++ b/public/app/plugins/datasource/influxdb/query_part.ts @@ -15,11 +15,13 @@ class QueryPartDef { name: string; params: any[]; defaultParams: any[]; + renderer: any; constructor(options: any) { this.name = options.name; this.params = options.params; this.defaultParams = options.defaultParams; + this.renderer = options.renderer; } static register(options: any) { @@ -27,25 +29,55 @@ class QueryPartDef { } } -QueryPartDef.register({ - name: 'field', - category: categories.Transform, - params: [{type: 'field'}], - defaultParams: ['value'], -}); +function functionRenderer(part, innerExpr) { + var str = part.def.name + '('; + var parameters = _.map(part.params, (value, index) => { + var paramType = part.def.params[index]; + 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] + '"'; +} QueryPartDef.register({ name: 'mean', category: categories.Transform, - params: [], - defaultParams: [], + params: [{type: 'field', quote: 'double'}], + defaultParams: ['value'], + renderer: functionRenderer, }); QueryPartDef.register({ - name: 'derivate', + name: 'derivative', category: categories.Transform, - params: [{ name: "rate", type: "interval", options: ['1s', '10s', '1m', '5min', '10m', '15m', '1h'] }], + params: [{ name: "duration", type: "interval", options: ['1s', '10s', '1m', '5min', '10m', '15m', '1h']}], defaultParams: ['10s'], + renderer: functionRenderer, }); QueryPartDef.register({ @@ -53,6 +85,7 @@ QueryPartDef.register({ category: categories.Transform, params: [{ name: "rate", type: "interval", options: ['$interval', '1s', '10s', '1m', '5min', '10m', '15m', '1h'] }], defaultParams: ['$interval'], + renderer: functionRenderer, }); QueryPartDef.register({ @@ -60,13 +93,16 @@ QueryPartDef.register({ category: categories.Transform, params: [{ name: "expr", type: "string"}], defaultParams: [' / 100'], + renderer: suffixRenderer, }); QueryPartDef.register({ name: 'alias', category: categories.Transform, - params: [{ name: "name", type: "string"}], + params: [{ name: "name", type: "string", quote: 'double'}], defaultParams: ['alias'], + renderMode: 'suffix', + renderer: aliasRenderer, }); class QueryPart { @@ -83,29 +119,11 @@ class QueryPart { } this.params = part.params || _.clone(this.def.defaultParams); + this.updateText(); } render(innerExpr: string) { - var str = this.def.name + '('; - var parameters = _.map(this.params, (value, index) => { - - var paramType = this.def.params[index].type; - if (paramType === 'int' || paramType === 'value_or_series' || paramType === 'boolean') { - return value; - } - else if (paramType === 'int_or_interval' && _.isNumber(value)) { - return value; - } - - return "'" + value + "'"; - - }); - - if (innerExpr) { - parameters.unshift(innerExpr); - } - - return str + parameters.join(', ') + ')'; + return this.def.renderer(this, innerExpr); } hasMultipleParamsInString (strValue, index) { diff --git a/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts b/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts new file mode 100644 index 00000000000..9634e327c67 --- /dev/null +++ b/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts @@ -0,0 +1,36 @@ +import {describe, beforeEach, it, sinon, expect} from 'test/lib/common'; + +import InfluxQuery = require('../influx_query'); + +describe.only('InfluxQuery', function() { + + describe('series with mesurement only', function() { + it('should generate correct query', function() { + var query = new InfluxQuery({ + measurement: 'cpu', + }); + + var queryText = query.render(); + expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE $timeFilter GROUP BY time($interval)'); + }); + }); + + describe('series with math and alias', function() { + it('should generate correct query', function() { + var query = new InfluxQuery({ + measurement: 'cpu', + select: [ + [ + {name: 'mean', params: ['value']}, + {name: 'math', params: ['/100']}, + {name: 'alias', params: ['text']}, + ] + ] + }); + + var queryText = query.render(); + expect(queryText).to.be('SELECT mean("value") /100 AS "text" FROM "cpu" WHERE $timeFilter GROUP BY time($interval)'); + }); + }); + +}); diff --git a/public/app/plugins/datasource/influxdb/specs/query_builder_specs.ts b/public/app/plugins/datasource/influxdb/specs/query_builder_specs.ts index 65a2f453385..5e06ef77db4 100644 --- a/public/app/plugins/datasource/influxdb/specs/query_builder_specs.ts +++ b/public/app/plugins/datasource/influxdb/specs/query_builder_specs.ts @@ -9,8 +9,8 @@ describe('InfluxQueryBuilder', function() { describe('series with mesurement only', function() { it('should generate correct query', function() { var builder = new InfluxQueryBuilder({ - measurement: 'cpu', - groupBy: [{type: 'time', interval: 'auto'}] + measurement: 'cpu', + groupBy: [{type: 'time', interval: 'auto'}] }); var query = builder.build(); @@ -22,9 +22,9 @@ describe('InfluxQueryBuilder', function() { describe('series with math expr and as expr', function() { it('should generate correct query', function() { var builder = new InfluxQueryBuilder({ - measurement: 'cpu', - fields: [{name: 'test', func: 'max', mathExpr: '*2', asExpr: 'new_name'}], - groupBy: [{type: 'time', interval: 'auto'}] + measurement: 'cpu', + fields: [{name: 'test', func: 'max', mathExpr: '*2', asExpr: 'new_name'}], + groupBy: [{type: 'time', interval: 'auto'}] }); var query = builder.build(); @@ -36,22 +36,22 @@ describe('InfluxQueryBuilder', function() { describe('series with single tag only', function() { it('should generate correct query', function() { var builder = new InfluxQueryBuilder({ - measurement: 'cpu', - groupBy: [{type: 'time', interval: 'auto'}], - tags: [{key: 'hostname', value: 'server1'}] + measurement: 'cpu', + groupBy: [{type: 'time', interval: 'auto'}], + tags: [{key: 'hostname', value: 'server1'}] }); var query = builder.build(); expect(query).to.be('SELECT mean("value") AS "value" FROM "cpu" WHERE "hostname" = \'server1\' AND $timeFilter' - + ' GROUP BY time($interval)'); + + ' GROUP BY time($interval)'); }); it('should switch regex operator with tag value is regex', function() { var builder = new InfluxQueryBuilder({ - measurement: 'cpu', - groupBy: [{type: 'time', interval: 'auto'}], - tags: [{key: 'app', value: '/e.*/'}] + measurement: 'cpu', + groupBy: [{type: 'time', interval: 'auto'}], + tags: [{key: 'app', value: '/e.*/'}] }); var query = builder.build(); @@ -62,57 +62,57 @@ describe('InfluxQueryBuilder', function() { describe('series with multiple fields', function() { it('should generate correct query', function() { var builder = new InfluxQueryBuilder({ - measurement: 'cpu', - tags: [], - groupBy: [{type: 'time', interval: 'auto'}], - fields: [{ name: 'tx_in', func: 'sum' }, { name: 'tx_out', func: 'mean' }] + measurement: 'cpu', + tags: [], + groupBy: [{type: 'time', interval: 'auto'}], + fields: [{ name: 'tx_in', func: 'sum' }, { name: 'tx_out', func: 'mean' }] }); var query = builder.build(); expect(query).to.be('SELECT sum("tx_in") AS "tx_in", mean("tx_out") AS "tx_out" ' + - 'FROM "cpu" WHERE $timeFilter GROUP BY time($interval)'); + 'FROM "cpu" WHERE $timeFilter GROUP BY time($interval)'); }); }); describe('series with multiple tags only', function() { it('should generate correct query', function() { var builder = new InfluxQueryBuilder({ - measurement: 'cpu', - groupBy: [{type: 'time', interval: 'auto'}], - tags: [{key: 'hostname', value: 'server1'}, {key: 'app', value: 'email', condition: "AND"}] + measurement: 'cpu', + groupBy: [{type: 'time', interval: 'auto'}], + tags: [{key: 'hostname', value: 'server1'}, {key: 'app', value: 'email', condition: "AND"}] }); var query = builder.build(); expect(query).to.be('SELECT mean("value") AS "value" FROM "cpu" WHERE "hostname" = \'server1\' AND "app" = \'email\' AND ' + - '$timeFilter GROUP BY time($interval)'); + '$timeFilter GROUP BY time($interval)'); }); }); describe('series with tags OR condition', function() { it('should generate correct query', function() { var builder = new InfluxQueryBuilder({ - measurement: 'cpu', - groupBy: [{type: 'time', interval: 'auto'}], - tags: [{key: 'hostname', value: 'server1'}, {key: 'hostname', value: 'server2', condition: "OR"}] + measurement: 'cpu', + groupBy: [{type: 'time', interval: 'auto'}], + tags: [{key: 'hostname', value: 'server1'}, {key: 'hostname', value: 'server2', condition: "OR"}] }); var query = builder.build(); expect(query).to.be('SELECT mean("value") AS "value" FROM "cpu" WHERE "hostname" = \'server1\' OR "hostname" = \'server2\' AND ' + - '$timeFilter GROUP BY time($interval)'); + '$timeFilter GROUP BY time($interval)'); }); }); describe('series with groupByTag', function() { it('should generate correct query', function() { var builder = new InfluxQueryBuilder({ - measurement: 'cpu', - tags: [], - groupBy: [{type: 'time', interval: 'auto'}, {type: 'tag', key: 'host'}], + measurement: 'cpu', + tags: [], + groupBy: [{type: 'time', interval: 'auto'}, {type: 'tag', key: 'host'}], }); var query = builder.build(); expect(query).to.be('SELECT mean("value") AS "value" FROM "cpu" WHERE $timeFilter ' + - 'GROUP BY time($interval), "host"'); + 'GROUP BY time($interval), "host"'); }); }); @@ -126,8 +126,7 @@ describe('InfluxQueryBuilder', function() { it('should handle regex measurement in tag keys query', function() { var builder = new InfluxQueryBuilder({ - measurement: '/.*/', - tags: [] + measurement: '/.*/', tags: [] }); var query = builder.buildExploreQuery('TAG_KEYS'); expect(query).to.be('SHOW TAG KEYS FROM /.*/'); @@ -170,7 +169,10 @@ describe('InfluxQueryBuilder', function() { }); it('should switch to regex operator in tag condition', function() { - var builder = new InfluxQueryBuilder({measurement: 'cpu', tags: [{key: 'host', value: '/server.*/'}]}); + var builder = new InfluxQueryBuilder({ + measurement: 'cpu', + tags: [{key: 'host', value: '/server.*/'}] + }); var query = builder.buildExploreQuery('TAG_VALUES', 'app'); expect(query).to.be('SHOW TAG VALUES FROM "cpu" WITH KEY = "app" WHERE "host" =~ /server.*/'); }); diff --git a/public/app/plugins/datasource/influxdb/specs/query_part_specs.ts b/public/app/plugins/datasource/influxdb/specs/query_part_specs.ts new file mode 100644 index 00000000000..ea354bf1439 --- /dev/null +++ b/public/app/plugins/datasource/influxdb/specs/query_part_specs.ts @@ -0,0 +1,41 @@ + +import {describe, beforeEach, it, sinon, expect} from 'test/lib/common'; + +import queryPart = require('../query_part'); + +describe('InfluxQueryBuilder', () => { + + describe('series with mesurement only', () => { + it('should handle nested function parts', () => { + var part = queryPart.create({ + name: 'derivative', + params: ['10s'], + }); + + expect(part.text).to.be('derivative(10s)'); + expect(part.render('mean(value)')).to.be('derivative(mean(value), 10s)'); + }); + + it('should handle suffirx parts', () => { + var part = queryPart.create({ + name: 'math', + params: ['/ 100'], + }); + + expect(part.text).to.be('math(/ 100)'); + expect(part.render('mean(value)')).to.be('mean(value) / 100'); + }); + + it('should handle alias parts', () => { + var part = queryPart.create({ + name: 'alias', + params: ['test'], + }); + + expect(part.text).to.be('alias(test)'); + expect(part.render('mean(value)')).to.be('mean(value) AS "test"'); + }); + + }); + +}); From ef2094f817d82218924ee0a0316707312f352e55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 1 Oct 2015 15:48:45 +0200 Subject: [PATCH 009/394] feat(influxdb): minor progress on #2802 --- .../influxdb/partials/query.editor.html | 8 +----- .../plugins/datasource/influxdb/query_ctrl.js | 20 ++++++++++++- .../plugins/datasource/influxdb/query_part.ts | 28 ++++++++++++------- .../datasource/influxdb/query_part_editor.js | 3 +- 4 files changed, 40 insertions(+), 19 deletions(-) diff --git a/public/app/plugins/datasource/influxdb/partials/query.editor.html b/public/app/plugins/datasource/influxdb/partials/query.editor.html index 1993c2d7aa9..98499303cc8 100644 --- a/public/app/plugins/datasource/influxdb/partials/query.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/query.editor.html @@ -73,13 +73,7 @@
        • -
        -
          -
        • - -
        • -
        • - +
        diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.js b/public/app/plugins/datasource/influxdb/query_ctrl.js index 924c7ab77fe..1eba65c7c87 100644 --- a/public/app/plugins/datasource/influxdb/query_ctrl.js +++ b/public/app/plugins/datasource/influxdb/query_ctrl.js @@ -3,9 +3,10 @@ define([ 'lodash', './query_builder', './influx_query', + './query_part', './query_part_editor', ], -function (angular, _, InfluxQueryBuilder, InfluxQuery) { +function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) { 'use strict'; var module = angular.module('grafana.controllers'); @@ -45,9 +46,26 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery) { }); $scope.fixTagSegments(); + $scope.buildSelectMenu(); $scope.removeTagFilterSegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove tag filter --'}); }; + $scope.buildSelectMenu = function() { + var categories = queryPart.getCategories(); + $scope.selectMenu = _.reduce(categories, function(memo, cat, key) { + var menu = {text: key}; + menu.submenu = _.map(cat, function(item) { + return {text: item.name, value: item.name}; + }); + memo.push(menu); + return memo; + }, []); + }; + + $scope.selectMenuAction = function(selectParts, cat, subitem) { + selectParts.push(queryPart.create({name: subitem.value })); + }; + $scope.fixTagSegments = function() { var count = $scope.tagSegments.length; var lastSegment = $scope.tagSegments[Math.max(count-1, 0)]; diff --git a/public/app/plugins/datasource/influxdb/query_part.ts b/public/app/plugins/datasource/influxdb/query_part.ts index 9d6d92f569c..84dab84806a 100644 --- a/public/app/plugins/datasource/influxdb/query_part.ts +++ b/public/app/plugins/datasource/influxdb/query_part.ts @@ -4,11 +4,10 @@ import _ = require('lodash'); var index = []; var categories = { - Combine: [], - Transform: [], - Calculate: [], - Filter: [], - Special: [] + Aggregations: [], + Transformations: [], + Math: [], + Aliasing: [], }; class QueryPartDef { @@ -26,6 +25,7 @@ class QueryPartDef { static register(options: any) { index[options.name] = new QueryPartDef(options); + options.category.push(index[options.name]); } } @@ -66,7 +66,15 @@ function quotedIdentityRenderer(part, innerExpr) { QueryPartDef.register({ name: 'mean', - category: categories.Transform, + category: categories.Aggregations, + params: [{type: 'field', quote: 'double'}], + defaultParams: ['value'], + renderer: functionRenderer, +}); + +QueryPartDef.register({ + name: 'sum', + category: categories.Aggregations, params: [{type: 'field', quote: 'double'}], defaultParams: ['value'], renderer: functionRenderer, @@ -74,7 +82,7 @@ QueryPartDef.register({ QueryPartDef.register({ name: 'derivative', - category: categories.Transform, + category: categories.Transformations, params: [{ name: "duration", type: "interval", options: ['1s', '10s', '1m', '5min', '10m', '15m', '1h']}], defaultParams: ['10s'], renderer: functionRenderer, @@ -82,7 +90,7 @@ QueryPartDef.register({ QueryPartDef.register({ name: 'time', - category: categories.Transform, + category: categories.Transformations, params: [{ name: "rate", type: "interval", options: ['$interval', '1s', '10s', '1m', '5min', '10m', '15m', '1h'] }], defaultParams: ['$interval'], renderer: functionRenderer, @@ -90,7 +98,7 @@ QueryPartDef.register({ QueryPartDef.register({ name: 'math', - category: categories.Transform, + category: categories.Math, params: [{ name: "expr", type: "string"}], defaultParams: [' / 100'], renderer: suffixRenderer, @@ -98,7 +106,7 @@ QueryPartDef.register({ QueryPartDef.register({ name: 'alias', - category: categories.Transform, + category: categories.Aliasing, params: [{ name: "name", type: "string", quote: 'double'}], defaultParams: ['alias'], renderMode: 'suffix', diff --git a/public/app/plugins/datasource/influxdb/query_part_editor.js b/public/app/plugins/datasource/influxdb/query_part_editor.js index 6cfcda5f4e7..6adf41f9c3f 100644 --- a/public/app/plugins/datasource/influxdb/query_part_editor.js +++ b/public/app/plugins/datasource/influxdb/query_part_editor.js @@ -16,7 +16,8 @@ function (angular, _, $) { restrict: 'E', templateUrl: 'app/plugins/datasource/influxdb/partials/query_part.html', scope: { - part: "=" + part: "=", + deleteAction: "&", }, link: function postLink($scope, elem) { var part = $scope.part; From 0dc0e03c4c54663d1be58d40caeb68ebb6c3340d Mon Sep 17 00:00:00 2001 From: Ivan Babrou Date: Sun, 4 Oct 2015 12:54:43 +0100 Subject: [PATCH 010/394] Add suggest_tagk() and suggest_tagv() to OpenTSDB datasource (#2840) --- docs/sources/datasources/opentsdb.md | 6 ++++-- .../plugins/datasource/opentsdb/datasource.js | 20 +++++++++++++++---- public/test/specs/opentsdbDatasource-specs.js | 20 ++++++++++++++++++- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/docs/sources/datasources/opentsdb.md b/docs/sources/datasources/opentsdb.md index 3368c39a0ae..bcaeed9056c 100644 --- a/docs/sources/datasources/opentsdb.md +++ b/docs/sources/datasources/opentsdb.md @@ -40,8 +40,10 @@ Grafana's OpenTSDB data source now supports template variable values queries. Th When using OpenTSDB with a template variable of `query` type you can use following syntax for lookup. - metrics() // returns metric names + metrics(prefix) // returns metric names with specific prefix (can be empty) tag_names(cpu) // return tag names (i.e. keys) for a specific cpu metric tag_values(cpu, hostname) // return tag values for metric cpu and tag key hostname + suggest_tagk(prefix) // return tag names (i.e. keys) for all metrics with specific prefix (can be empty) + suggest_tagv(prefix) // return tag values for all metrics with specific prefix (can be empty) -For details on opentsdb metric queries checkout the official [OpenTSDB documentation](http://opentsdb.net/docs/build/html/index.html) \ No newline at end of file +For details on opentsdb metric queries checkout the official [OpenTSDB documentation](http://opentsdb.net/docs/build/html/index.html) diff --git a/public/app/plugins/datasource/opentsdb/datasource.js b/public/app/plugins/datasource/opentsdb/datasource.js index 124c6597a00..c46b451a64c 100644 --- a/public/app/plugins/datasource/opentsdb/datasource.js +++ b/public/app/plugins/datasource/opentsdb/datasource.js @@ -80,8 +80,8 @@ function (angular, _, dateMath) { return backendSrv.datasourceRequest(options); }; - OpenTSDBDatasource.prototype._performSuggestQuery = function(query) { - return this._get('/api/suggest', {type: 'metrics', q: query, max: 1000}).then(function(result) { + OpenTSDBDatasource.prototype._performSuggestQuery = function(query, type) { + return this._get('/api/suggest', {type: type, q: query, max: 1000}).then(function(result) { return result.data; }); }; @@ -150,10 +150,12 @@ function (angular, _, dateMath) { var metrics_regex = /metrics\((.*)\)/; var tag_names_regex = /tag_names\((.*)\)/; var tag_values_regex = /tag_values\((.*),\s?(.*)\)/; + var tag_names_suggest_regex = /suggest_tagk\((.*)\)/; + var tag_values_suggest_regex = /suggest_tagv\((.*)\)/; var metrics_query = interpolated.match(metrics_regex); if (metrics_query) { - return this._performSuggestQuery(metrics_query[1]).then(responseTransform); + return this._performSuggestQuery(metrics_query[1], 'metrics').then(responseTransform); } var tag_names_query = interpolated.match(tag_names_regex); @@ -166,7 +168,17 @@ function (angular, _, dateMath) { return this._performMetricKeyValueLookup(tag_values_query[1], tag_values_query[2]).then(responseTransform); } - return $q.when([]); + var tag_names_suggest_query = interpolated.match(tag_names_suggest_regex); + if (tag_names_suggest_query) { + return this._performSuggestQuery(tag_names_suggest_query[1], 'tagk').then(responseTransform); + } + + var tag_values_suggest_query = interpolated.match(tag_values_suggest_regex); + if (tag_values_suggest_query) { + return this._performSuggestQuery(tag_values_suggest_query[1], 'tagv').then(responseTransform); + } + + return $q.when([{text: "wtf"}]); }; OpenTSDBDatasource.prototype.testDatasource = function() { diff --git a/public/test/specs/opentsdbDatasource-specs.js b/public/test/specs/opentsdbDatasource-specs.js index 57818fdc4fa..ecd2ca598e6 100644 --- a/public/test/specs/opentsdbDatasource-specs.js +++ b/public/test/specs/opentsdbDatasource-specs.js @@ -27,9 +27,11 @@ define([ }); it('metrics() should generate api suggest query', function() { - ctx.ds.metricFindQuery('metrics()').then(function(data) { results = data; }); + ctx.ds.metricFindQuery('metrics(pew)').then(function(data) { results = data; }); ctx.$rootScope.$apply(); expect(requestOptions.url).to.be('/api/suggest'); + expect(requestOptions.params.type).to.be('metrics'); + expect(requestOptions.params.q).to.be('pew'); }); it('tag_names(cpu) should generate looku query', function() { @@ -46,6 +48,22 @@ define([ expect(requestOptions.params.m).to.be('cpu{hostname=*}'); }); + it('suggest_tagk() should generate api suggest query', function() { + ctx.ds.metricFindQuery('suggest_tagk(foo)').then(function(data) { results = data; }); + ctx.$rootScope.$apply(); + expect(requestOptions.url).to.be('/api/suggest'); + expect(requestOptions.params.type).to.be('tagk'); + expect(requestOptions.params.q).to.be('foo'); + }); + + it('suggest_tagv() should generate api suggest query', function() { + ctx.ds.metricFindQuery('suggest_tagv(bar)').then(function(data) { results = data; }); + ctx.$rootScope.$apply(); + expect(requestOptions.url).to.be('/api/suggest'); + expect(requestOptions.params.type).to.be('tagv'); + expect(requestOptions.params.q).to.be('bar'); + }); + }); }); }); From 4bb656b7043cdf156bdf47ded10efafcf87f6237 Mon Sep 17 00:00:00 2001 From: Julien Maitrehenry Date: Thu, 8 Oct 2015 00:22:09 -0400 Subject: [PATCH 011/394] #2834 - follow symlink --- pkg/plugins/plugins.go | 4 +- pkg/util/filepath.go | 98 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 pkg/util/filepath.go diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index 2f7e5264e53..a5767c7a70b 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -5,10 +5,10 @@ import ( "errors" "os" "path" - "path/filepath" "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/util" ) type PluginMeta struct { @@ -36,7 +36,7 @@ func scan(pluginDir string) error { pluginPath: pluginDir, } - if err := filepath.Walk(pluginDir, scanner.walker); err != nil { + if err := util.Walk(pluginDir, true, true, scanner.walker); err != nil { return err } diff --git a/pkg/util/filepath.go b/pkg/util/filepath.go new file mode 100644 index 00000000000..d0e27926956 --- /dev/null +++ b/pkg/util/filepath.go @@ -0,0 +1,98 @@ +package util + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" +) + +//WalkSkipDir is the Error returned when we want to skip descending into a directory +var WalkSkipDir = errors.New("skip this directory") + +//WalkFunc is a callback function called for each path as a directory is walked +//If resolvedPath != "", then we are following symbolic links. +type WalkFunc func(resolvedPath string, info os.FileInfo, err error) error + +//Walk walks a path, optionally following symbolic links, and for each path, +//it calls the walkFn passed. +// +//It is similar to filepath.Walk, except that it supports symbolic links and +//can detect infinite loops while following sym links. +//It solves the issue where your WalkFunc needs a path relative to the symbolic link +//(resolving links within walkfunc loses the path to the symbolic link for each traversal). +func Walk(path string, followSymlinks bool, detectSymlinkInfiniteLoop bool, walkFn WalkFunc) error { + info, err := os.Lstat(path) + if err != nil { + return err + } + var symlinkPathsFollowed map[string]bool + var resolvedPath string + if followSymlinks { + resolvedPath = path + if detectSymlinkInfiniteLoop { + symlinkPathsFollowed = make(map[string]bool, 8) + } + } + return walk(path, info, resolvedPath, symlinkPathsFollowed, walkFn) +} + +//walk walks the path. It is a helper/sibling function to Walk. +//It takes a resolvedPath into consideration. This way, paths being walked are +//always relative to the path argument, even if symbolic links were resolved). +// +//If resolvedPath is "", then we are not following symbolic links. +//If symlinkPathsFollowed is not nil, then we need to detect infinite loop. +func walk(path string, info os.FileInfo, resolvedPath string, + symlinkPathsFollowed map[string]bool, walkFn WalkFunc) error { + if info == nil { + return errors.New("Walk: Nil FileInfo passed") + } + err := walkFn(resolvedPath, info, nil) + if err != nil { + if info.IsDir() && err == WalkSkipDir { + err = nil + } + return err + } + if resolvedPath != "" && info.Mode()&os.ModeSymlink == os.ModeSymlink { + path2, err := os.Readlink(resolvedPath) + if err != nil { + return err + } + //vout("SymLink Path: %v, links to: %v", resolvedPath, path2) + if symlinkPathsFollowed != nil { + if _, ok := symlinkPathsFollowed[path2]; ok { + errMsg := "Potential SymLink Infinite Loop. Path: %v, Link To: %v" + return fmt.Errorf(errMsg, resolvedPath, path2) + } else { + symlinkPathsFollowed[path2] = true + } + } + info2, err := os.Lstat(path2) + if err != nil { + return err + } + return walk(path, info2, path2, symlinkPathsFollowed, walkFn) + } + if info.IsDir() { + list, err := ioutil.ReadDir(path) + if err != nil { + return walkFn(resolvedPath, info, err) + } + for _, fileInfo := range list { + path2 := filepath.Join(path, fileInfo.Name()) + var resolvedPath2 string + if resolvedPath != "" { + resolvedPath2 = filepath.Join(resolvedPath, fileInfo.Name()) + } + err = walk(path2, fileInfo, resolvedPath2, symlinkPathsFollowed, walkFn) + if err != nil { + return err + } + } + return nil + } + return nil +} From 7e2f653bc7131d09b91d620caa2fed8aa30809dd Mon Sep 17 00:00:00 2001 From: Nick Christus Date: Sat, 10 Oct 2015 14:17:07 -0400 Subject: [PATCH 012/394] added alerting tab stub and styles --- public/app/features/panel/partials/panel.html | 5 + public/app/panels/graph/alerting.html | 233 ++++++++++++++++++ public/app/panels/graph/module.js | 1 + public/img/CopyQuery.png | Bin 0 -> 144 bytes public/img/critical.svg | 15 ++ public/img/envelope.png | Bin 0 -> 187 bytes public/img/online.svg | 12 + public/img/warn-tiny.svg | 16 ++ public/img/warn.svg | 22 ++ public/less/alerting.less | 42 ++++ public/less/gfbox.less | 7 + public/less/grafana.less | 1 + public/less/overrides.less | 9 + public/less/tightform.less | 6 + 14 files changed, 369 insertions(+) create mode 100644 public/app/panels/graph/alerting.html create mode 100644 public/img/CopyQuery.png create mode 100644 public/img/critical.svg create mode 100644 public/img/envelope.png create mode 100644 public/img/online.svg create mode 100644 public/img/warn-tiny.svg create mode 100644 public/img/warn.svg create mode 100644 public/less/alerting.less diff --git a/public/app/features/panel/partials/panel.html b/public/app/features/panel/partials/panel.html index a7fef0f5d67..65f482161f8 100644 --- a/public/app/features/panel/partials/panel.html +++ b/public/app/features/panel/partials/panel.html @@ -35,6 +35,11 @@ + +
        + There are unsaved changes. + +
        diff --git a/public/app/panels/graph/alerting.html b/public/app/panels/graph/alerting.html new file mode 100644 index 00000000000..7230c6ef488 --- /dev/null +++ b/public/app/panels/graph/alerting.html @@ -0,0 +1,233 @@ +
        + Last updated by Grafana October 4, 2015 12:15:04 by $username +
        +
        General Alerting Options
        +
        +
          +
        • + Alert Title +
        • +
        • + +
        • +
        • + Alerting Backend +
        • +
        • + +
        • +
        • + + + +
        • +
        +
        +
        +
        +
        +
        +
        Choose your query:
        +

        Select an exising query to alert on:

        +
        +
        +
          +
        • +
        • None
        • +
        +
        +
        +
        +
        +
        +
        +
          +
        • +
        • A
        • +
        • apps
        • +
        • +
        • fakesite
        • +
        • counters
        • +
        • requests
        • +
        • count
        • +
        • scaleToSeconds(1)
        • +
        • aliasByNode(2)
        • +
        • +
        +
        +
        +
        +
        +
        +
          +
        • +
        • B
        • +
        • Metric: us-west-2 AWS/EC2 CPUUtilization Stats: Minimum Maximum Dimensions InstanceIS = i-b0e8a447 Alias {{stat}} Period 60
        • +
        • +
        +
        +
        +
        +
        +
        +
          +
        • +
        • C
        • +
        • Query: avg(counters_logins) by(server) Legend Format: {{app}} - {{server}} Step: 1s Resolution: 1/2
        • +
        • +
        +
        +
        +
        +
        +
        +
          +
        • +
        • D
        • +
        • SELECT mean(value) FROM logins.count WHERE hostname = /$Hostname$/ GROUP BY time($internal) hostname
        • +
        • +
        +
        +
        +
        +
        +
        +
          +
        • +
        • E
        • +
        • Metric: apps.backend.backend_01.counters.requests.count Alias: Bristow Aggregator: Sum Downsample: 1m Aggregator Sum Tags host = test
        • +
        • +
        +
        +
        +
        +
        +
        +

        Or write a new custom alerting query:

        +
        +
        +
          +
        • +
        • + + + +
        • +
        • + select metric +
        • +
        • + +
        • +
        +
        +
        +
        +
        +
        +
        +
        Define Your States
        +
        +
          +
        • + by +
        • +
        • + +
        • +
        • + the values in the query over the last +
        • +
        • + +
        • +
        +
        +
        +
        +
        +
        +
        +
        +
          +
        • + Warn +
        • +
        • + +
        • +
        • + +
        • +
        • + .notify +
        • +
        • + +
        • +
        • + + + +
        • +
        +
        +
        +
        +
          +
        • + Critical +
        • +
        • + +
        • +
        • + +
        • +
        • + .notify +
        • +
        • + +
        • +
        • + + + +
        • +
        +
        +
        +
        +
        +
        +
        +
        What to Say Variables | Preview
        +
        +
          +
        • + Summary +
        • +
        • + +
        • +
        +
        +
        +
        +
          +
        • + Description +
        • +
        • + +
        • +
        +
        +
        +
        +
        diff --git a/public/app/panels/graph/module.js b/public/app/panels/graph/module.js index 5cdeab799de..a1e56f8c5eb 100644 --- a/public/app/panels/graph/module.js +++ b/public/app/panels/graph/module.js @@ -34,6 +34,7 @@ function (angular, $, _, kbn, moment, TimeSeries, PanelMeta) { $scope.panelMeta.addEditorTab('Axes & Grid', 'app/panels/graph/axisEditor.html'); $scope.panelMeta.addEditorTab('Display Styles', 'app/panels/graph/styleEditor.html'); $scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html'); + $scope.panelMeta.addEditorTab('Alerting', 'app/panels/graph/alerting.html'); $scope.panelMeta.addExtendedMenuItem('Export CSV', '', 'exportCsv()'); $scope.panelMeta.addExtendedMenuItem('Toggle legend', '', 'toggleLegend()'); diff --git a/public/img/CopyQuery.png b/public/img/CopyQuery.png new file mode 100644 index 0000000000000000000000000000000000000000..b9829c23b2ff6146760e6c5f5264a713aceb7e99 GIT binary patch literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^d_XL~!2%?E=C*AFQsJI1jv*C{$u>XU+bc6mCH%=h z&$5jFPx*lfES-Y-l^f + + + + + + + diff --git a/public/img/envelope.png b/public/img/envelope.png new file mode 100644 index 0000000000000000000000000000000000000000..59ef8a38aba65a69c72ab8a97b1f3421e35d8cac GIT binary patch literal 187 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh0wlLOK8*rWO`a}}Ar*{sFC1NX$Uvsyqkh?Y zwi2b3NhwY2ITLh(Vprr`DCRAH{+*PM@abWfebchTzZ`Iu^9tMWYgE%|E;Ht-+X6|`CL7_(nktyYo+`**QehTO=4 lFAt}D-Cvh6m;2rg2C+2#McIiyOhDH#c)I$ztaD0e0s!QaM^gX* literal 0 HcmV?d00001 diff --git a/public/img/online.svg b/public/img/online.svg new file mode 100644 index 00000000000..279fbec3a90 --- /dev/null +++ b/public/img/online.svg @@ -0,0 +1,12 @@ + + + + + + diff --git a/public/img/warn-tiny.svg b/public/img/warn-tiny.svg new file mode 100644 index 00000000000..e8e91f4452c --- /dev/null +++ b/public/img/warn-tiny.svg @@ -0,0 +1,16 @@ + + + + + + + diff --git a/public/img/warn.svg b/public/img/warn.svg new file mode 100644 index 00000000000..8caee82c3f5 --- /dev/null +++ b/public/img/warn.svg @@ -0,0 +1,22 @@ + + + + + + + diff --git a/public/less/alerting.less b/public/less/alerting.less new file mode 100644 index 00000000000..9054584e23e --- /dev/null +++ b/public/less/alerting.less @@ -0,0 +1,42 @@ +.copy-query { + display: block; + width: 30px; + height: 36px; + margin: 0; + padding: 0; + border: 0; + background: transparent url(/img/CopyQuery.png) 50% 50% no-repeat; + cursor: pointer; +} + +.alert-state { + display: inline-block; + padding-left: 30px; + background: 0 50% no-repeat; + background-size: 20px auto; +} + +.alert-state-online { + background-image: url('/img/online.svg'); +} + +.alert-state-warning { + background-image: url('/img/warn-tiny.svg'); +} + +.alert-state-critical { + background-image: url('/img/critical.svg'); +} + +.alert-notify-emails { + width: 400px; + border-right: 1px solid @black; +} + +.alert-notify-emails .bootstrap-tagsinput { + width: 394px; // offset for 8px left padding and border width +} + +.alert-notify-emails .bootstrap-tagsinput input { + border: 0; +} diff --git a/public/less/gfbox.less b/public/less/gfbox.less index eb386f740d7..48d0b6a7ca8 100644 --- a/public/less/gfbox.less +++ b/public/less/gfbox.less @@ -25,6 +25,13 @@ } } +.gf-box-header-save-btn { + padding: 7px 0; + float: right; + color: @grayLight; + font-style: italic; +} + .gf-box-body { padding: 20px; min-height: 150px; diff --git a/public/less/grafana.less b/public/less/grafana.less index 6e23faa214d..0d4e5f44dc0 100644 --- a/public/less/grafana.less +++ b/public/less/grafana.less @@ -18,6 +18,7 @@ @import "fonts.less"; @import "tabs.less"; @import "timepicker.less"; +@import "alerting.less"; .row-control-inner { padding:0px; diff --git a/public/less/overrides.less b/public/less/overrides.less index fb6544cd99c..229627c0896 100644 --- a/public/less/overrides.less +++ b/public/less/overrides.less @@ -560,6 +560,15 @@ div.flot-text { background-color: darken(@purple, 10%); } +.label-tag-email { + padding-left: 25px; + background: @black url(/img/envelope.png) 5px 50% no-repeat !important; + border-color: @black !important; + font-size: 12px; + font-weight: normal; + border-radius: 5px; +} + // inspector .inspector-request-table { diff --git a/public/less/tightform.less b/public/less/tightform.less index 494497653ed..f65a991613a 100644 --- a/public/less/tightform.less +++ b/public/less/tightform.less @@ -156,6 +156,12 @@ input[type=checkbox].tight-form-checkbox { margin: 0; } +.tight-form-textarea { + height: 200px; + margin: 0; + box-sizing: border-box; +} + select.tight-form-input { border: none; border-right: 1px solid @grafanaTargetSegmentBorder; From 23404decead89577dad4b23471aa1bf6645dc263 Mon Sep 17 00:00:00 2001 From: Nick Christus Date: Sun, 11 Oct 2015 16:59:40 -0400 Subject: [PATCH 013/394] added global alerts list stub and styles --- public/app/core/routes/all.js | 3 + .../dashboard/partials/globalAlerts.html | 282 ++++++++++++++++++ public/app/panels/graph/alerting.html | 119 ++++---- public/less/filter-list.less | 167 +++++++++++ public/less/gfbox.less | 4 + public/less/grafana.less | 1 + public/less/variables.dark.less | 6 + public/less/variables.light.less | 6 + 8 files changed, 522 insertions(+), 66 deletions(-) create mode 100644 public/app/features/dashboard/partials/globalAlerts.html create mode 100644 public/less/filter-list.less diff --git a/public/app/core/routes/all.js b/public/app/core/routes/all.js index a7e36a0e228..7a912621ba5 100644 --- a/public/app/core/routes/all.js +++ b/public/app/core/routes/all.js @@ -131,6 +131,9 @@ define([ templateUrl: 'app/partials/reset_password.html', controller : 'ResetPasswordCtrl', }) + .when('/global-alerts', { + templateUrl: 'app/features/dashboard/partials/globalAlerts.html', + }) .otherwise({ templateUrl: 'app/partials/error.html', controller: 'ErrorCtrl' diff --git a/public/app/features/dashboard/partials/globalAlerts.html b/public/app/features/dashboard/partials/globalAlerts.html new file mode 100644 index 00000000000..d66c7e98d8c --- /dev/null +++ b/public/app/features/dashboard/partials/globalAlerts.html @@ -0,0 +1,282 @@ + + + + +
        +
        +

        Global alerts

        + +
        +
        +
          +
        • Filters:
        • +
        • Alert State
        • +
        • +
        • Dashboards
        • +
        • +
        • + + + +
        • +
        +
        +
        +
        +
          +
        • + +
        • +
        • + +
        • +
        • + +
        • +
        • + 2 selected, showing 6 of 6 total +
        • +
        +
          +
        • + +
          +
          Alert query configure alerting
          +
          +
            +
          • A
          • +
          • apps
          • +
          • +
          • fakesite
          • +
          • counters
          • +
          • requests
          • +
          • count
          • +
          • scaleToSeconds(1)
          • +
          • aliasByNode(2)
          • +
          +
          +
          +
          +
        • +
        • + +
          +
          Alert query configure alerting
          +
          +
            +
          • A
          • +
          • apps
          • +
          • +
          • fakesite
          • +
          • counters
          • +
          • requests
          • +
          • count
          • +
          • scaleToSeconds(1)
          • +
          • aliasByNode(2)
          • +
          +
          +
          +
          +
        • +
        • + +
          +
          Alert query configure alerting
          +
          +
            +
          • A
          • +
          • apps
          • +
          • +
          • fakesite
          • +
          • counters
          • +
          • requests
          • +
          • count
          • +
          • scaleToSeconds(1)
          • +
          • aliasByNode(2)
          • +
          +
          +
          +
          +
        • +
        • + +
          +
          Alert query configure alerting
          +
          +
            +
          • A
          • +
          • apps
          • +
          • +
          • fakesite
          • +
          • counters
          • +
          • requests
          • +
          • count
          • +
          • scaleToSeconds(1)
          • +
          • aliasByNode(2)
          • +
          +
          +
          +
          +
        • +
        • + +
          +
          Alert query configure alerting
          +
          +
            +
          • A
          • +
          • apps
          • +
          • +
          • fakesite
          • +
          • counters
          • +
          • requests
          • +
          • count
          • +
          • scaleToSeconds(1)
          • +
          • aliasByNode(2)
          • +
          +
          +
          +
          +
        • +
        +
        +
        diff --git a/public/app/panels/graph/alerting.html b/public/app/panels/graph/alerting.html index 7230c6ef488..9e08fc4f6cb 100644 --- a/public/app/panels/graph/alerting.html +++ b/public/app/panels/graph/alerting.html @@ -31,77 +31,64 @@
        Choose your query:

        Select an exising query to alert on:

        -
        -
        -
          -
        • -
        • None
        • -
        -
        -
        +
        +
          +
        • +
        • None
        • +
        +
        -
        -
        -
        -
          -
        • -
        • A
        • -
        • apps
        • -
        • -
        • fakesite
        • -
        • counters
        • -
        • requests
        • -
        • count
        • -
        • scaleToSeconds(1)
        • -
        • aliasByNode(2)
        • -
        • -
        -
        -
        +
        +
          +
        • +
        • A
        • +
        • apps
        • +
        • +
        • fakesite
        • +
        • counters
        • +
        • requests
        • +
        • count
        • +
        • scaleToSeconds(1)
        • +
        • aliasByNode(2)
        • +
        • +
        +
        -
        -
        -
          -
        • -
        • B
        • -
        • Metric: us-west-2 AWS/EC2 CPUUtilization Stats: Minimum Maximum Dimensions InstanceIS = i-b0e8a447 Alias {{stat}} Period 60
        • -
        • -
        -
        -
        +
        +
          +
        • +
        • B
        • +
        • Metric: us-west-2 AWS/EC2 CPUUtilization Stats: Minimum Maximum Dimensions InstanceIS = i-b0e8a447 Alias {{stat}} Period 60
        • +
        • +
        +
        -
        -
        -
          -
        • -
        • C
        • -
        • Query: avg(counters_logins) by(server) Legend Format: {{app}} - {{server}} Step: 1s Resolution: 1/2
        • -
        • -
        -
        -
        +
        +
          +
        • +
        • C
        • +
        • Query: avg(counters_logins) by(server) Legend Format: {{app}} - {{server}} Step: 1s Resolution: 1/2
        • +
        • +
        +
        -
        -
        -
          -
        • -
        • D
        • -
        • SELECT mean(value) FROM logins.count WHERE hostname = /$Hostname$/ GROUP BY time($internal) hostname
        • -
        • -
        -
        -
        +
        +
          +
        • +
        • D
        • +
        • SELECT mean(value) FROM logins.count WHERE hostname = /$Hostname$/ GROUP BY time($internal) hostname
        • +
        • +
        +
        -
        -
        -
          -
        • -
        • E
        • -
        • Metric: apps.backend.backend_01.counters.requests.count Alias: Bristow Aggregator: Sum Downsample: 1m Aggregator Sum Tags host = test
        • -
        • -
        -
        -
        +
        +
          +
        • +
        • E
        • +
        • Metric: apps.backend.backend_01.counters.requests.count Alias: Bristow Aggregator: Sum Downsample: 1m Aggregator Sum Tags host = test
        • +
        • +
        +
        diff --git a/public/less/filter-list.less b/public/less/filter-list.less new file mode 100644 index 00000000000..50da5864db0 --- /dev/null +++ b/public/less/filter-list.less @@ -0,0 +1,167 @@ +// ========================================================================== +// FILTER LIST +// ========================================================================== + + + +// List +// -------------------------------------------------------------------------- + +.filter-list { + max-width: 1000px; + margin: 0; + padding: 0; + list-style: none; +} + +.filter-list > li { + padding: 10px; + margin-bottom: 2px; + background: @grafanaPanelBackground; + + &:last-child { + margin: 0; + } +} + + + +// Card +// -------------------------------------------------------------------------- + +.filter-list-card { + display: table; + width: 100%; + margin: 0; + padding: 0; + list-style: none; +} + +.filter-list-card > li { + display: table-cell; + vertical-align: top; +} + +.filter-list-card-select { + width: 23px; + padding-right: 5px; +} + +.filter-list-card-title { + display: block; + font-size: 16px; + font-weight: normal; +} + +.filter-list-card-status { + color: #777; + font-size: 12px; +} + +.filter-list-card-state { + display: inline-block; + padding: 5px 0 0 28px; + background: 0 bottom no-repeat; + background-size: 24px auto; + font-size: 14px; + text-transform: uppercase; + + &.online { + background-image: url('/img/online.svg'); + color: @online; + } + + &.warn { + background-image: url('/img/warn-tiny.svg'); + color: @warn; + } + + &.critical { + background-image: url('/img/critical.svg'); + color: @critical; + } +} + +.filter-list-card-controls { + float: right; +} + +.filter-list-card-links, +.filter-list-card-config, +.filter-list-card-expand { + display: inline-block; + vertical-align: middle; +} + +.filter-list-card-link { + display: block; + color: #777; + text-align: right; + + > a { + color: #777; + } +} + +.filter-list-card-config { + padding: 8px 8px 8px 16px; + color: #777; + font-size: 25px; + + > a { + color: #777; + } +} + +.filter-list-card-expand { + width: 20px; + padding: 8px 0 8px 8px; + color: #aaa; + font-size: 30px; + text-align: center; + cursor: pointer; +} + +.filter-list-card-details { + padding: 20px 0 0 30px; +} + +.filter-list-card-details-heading { + font-weight: normal; + + > a { + float: right; + color: @blue; + font-size: 12px; + } +} + + + +// Filters +// -------------------------------------------------------------------------- + +.filter-list-filters { + display: inline-block; + margin-bottom: 40px; +} + + + +// Actions +// -------------------------------------------------------------------------- + +.filter-list-actions { + margin: 0 0 10px; + padding: 0; + list-style: none; +} + +.filter-list-actions > li { + display: inline-block; + margin-right: 10px; +} + +.filter-list-actions-selected { + text-transform: uppercase; +} diff --git a/public/less/gfbox.less b/public/less/gfbox.less index 48d0b6a7ca8..d173ace1787 100644 --- a/public/less/gfbox.less +++ b/public/less/gfbox.less @@ -80,6 +80,10 @@ max-width: 653px; } +.page-wide { + max-width: none; +} + .admin-page { max-width: 800px; margin-left: 10px; diff --git a/public/less/grafana.less b/public/less/grafana.less index 0d4e5f44dc0..aa58f749b82 100644 --- a/public/less/grafana.less +++ b/public/less/grafana.less @@ -19,6 +19,7 @@ @import "tabs.less"; @import "timepicker.less"; @import "alerting.less"; +@import "filter-list.less"; .row-control-inner { padding:0px; diff --git a/public/less/variables.dark.less b/public/less/variables.dark.less index 3324c3f4b86..af8f3cdec75 100644 --- a/public/less/variables.dark.less +++ b/public/less/variables.dark.less @@ -25,6 +25,12 @@ @purple: #9933CC; @variable: #32D1DF; +// Status colors +// ------------------------- +@online: #10a345; +@warn: #ffc03c; +@critical: #ed2e18; + // grafana Variables // ------------------------- @grafanaPanelBackground: @grayDarker; diff --git a/public/less/variables.light.less b/public/less/variables.light.less index 27dcf8575f4..67a8dfd7257 100644 --- a/public/less/variables.light.less +++ b/public/less/variables.light.less @@ -31,6 +31,12 @@ @purple: #9954BB; @variable: #2AB2E4; +// Status colors +// ------------------------- +@online: #10a345; +@warn: #ffc03c; +@critical: #ed2e18; + // grafana Variables // ------------------------- @grafanaPanelBackground: @grayLighter; From cf89b565a63c6aa869293e6e770c6501f418e780 Mon Sep 17 00:00:00 2001 From: Anthony Woods Date: Tue, 6 Oct 2015 17:20:50 +0800 Subject: [PATCH 014/394] initial import of thirdParty route support --- pkg/api/api.go | 2 + pkg/api/index.go | 20 +++++ pkg/api/thirdparty.go | 75 +++++++++++++++++++ pkg/models/third_party.go | 31 ++++++++ pkg/plugins/plugins.go | 47 +++++++++++- public/app/app.js | 4 +- public/app/controllers/sidemenuCtrl.js | 10 +++ public/app/core/routes/all.js | 2 +- .../raintank/plugin.json | 40 ++++++++++ public/views/index.html | 13 +++- 10 files changed, 238 insertions(+), 6 deletions(-) create mode 100644 pkg/api/thirdparty.go create mode 100644 pkg/models/third_party.go create mode 100644 public/app/plugins/thirdPartyIntegration/raintank/plugin.json diff --git a/pkg/api/api.go b/pkg/api/api.go index 27eb3c749db..e9d85dc0c8d 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -185,5 +185,7 @@ func Register(r *macaron.Macaron) { // rendering r.Get("/render/*", reqSignedIn, RenderToPng) + InitThirdPartyRoutes(r) + r.NotFound(NotFoundHandler) } diff --git a/pkg/api/index.go b/pkg/api/index.go index 556db006b2f..072878a2cfd 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -3,6 +3,7 @@ package api import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/middleware" + "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/setting" ) @@ -51,6 +52,25 @@ func setIndexViewData(c *middleware.Context) error { if setting.GoogleTagManagerId != "" { c.Data["GoogleTagManagerId"] = setting.GoogleTagManagerId } + // This can be loaded from the DB/file to allow 3rdParty integration + thirdPartyJs := make([]string, 0) + thirdPartyCss := make([]string, 0) + thirdPartyMenu := make([]*plugins.ThirdPartyMenuItem, 0) + for _, integration := range plugins.Integrations { + for _, js := range integration.Js { + thirdPartyJs = append(thirdPartyJs, js.Src) + } + for _, css := range integration.Css { + thirdPartyCss = append(thirdPartyCss, css.Href) + } + for _, item := range integration.MenuItems { + thirdPartyMenu = append(thirdPartyMenu, item) + } + + } + c.Data["ThirdPartyJs"] = thirdPartyJs + c.Data["ThirdPartyCss"] = thirdPartyCss + c.Data["ThirdPartyMenu"] = thirdPartyMenu return nil } diff --git a/pkg/api/thirdparty.go b/pkg/api/thirdparty.go new file mode 100644 index 00000000000..588c3b40ce7 --- /dev/null +++ b/pkg/api/thirdparty.go @@ -0,0 +1,75 @@ +package api + +import ( + "encoding/json" + "github.com/Unknwon/macaron" + "github.com/grafana/grafana/pkg/middleware" + "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/util" + "log" + "net/http" + "net/http/httputil" + "net/url" +) + +func InitThirdPartyRoutes(r *macaron.Macaron) { + /* + // Handle Auth and role requirements + if route.ReqSignedIn { + c.Invoke(middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true})) + } + if route.ReqGrafanaAdmin { + c.Invoke(middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true, ReqGrafanaAdmin: true})) + } + if route.ReqRole != nil { + if *route.ReqRole == m.ROLE_EDITOR { + c.Invoke(middleware.RoleAuth(m.ROLE_EDITOR, m.ROLE_ADMIN)) + } + if *route.ReqRole == m.ROLE_ADMIN { + c.Invoke(middleware.RoleAuth(m.ROLE_ADMIN)) + } + } + */ + for _, integration := range plugins.Integrations { + log.Printf("adding routes for integration") + for _, route := range integration.Routes { + log.Printf("adding route %s %s", route.Method, route.Path) + r.Route(util.JoinUrlFragments("/thirdparty/", route.Path), route.Method, ThirdParty(route.Url)) + } + } +} + +func ThirdParty(routeUrl string) macaron.Handler { + return func(c *middleware.Context) { + path := c.Params("*") + + //Create a HTTP header with the context in it. + ctx, err := json.Marshal(c.SignedInUser) + if err != nil { + c.JsonApiErr(500, "Not found", err) + return + } + log.Printf(string(ctx)) + targetUrl, _ := url.Parse(routeUrl) + proxy := NewThirdPartyProxy(string(ctx), path, targetUrl) + proxy.Transport = dataProxyTransport + proxy.ServeHTTP(c.RW(), c.Req.Request) + } +} + +func NewThirdPartyProxy(ctx string, proxyPath string, targetUrl *url.URL) *httputil.ReverseProxy { + director := func(req *http.Request) { + req.URL.Scheme = targetUrl.Scheme + req.URL.Host = targetUrl.Host + req.Host = targetUrl.Host + + req.URL.Path = util.JoinUrlFragments(targetUrl.Path, proxyPath) + + // clear cookie headers + req.Header.Del("Cookie") + req.Header.Del("Set-Cookie") + req.Header.Add("Grafana-Context", ctx) + } + + return &httputil.ReverseProxy{Director: director} +} diff --git a/pkg/models/third_party.go b/pkg/models/third_party.go new file mode 100644 index 00000000000..0bce0d86f8f --- /dev/null +++ b/pkg/models/third_party.go @@ -0,0 +1,31 @@ +package models + +type ThirdPartyRoute struct { + Path string `json:"path"` + Method string `json:"method"` + ReqSignedIn bool `json:"req_signed_in"` + ReqGrafanaAdmin bool `json:"req_grafana_admin"` + ReqRole RoleType `json:"req_role"` + Url string `json:"url"` +} + +type ThirdPartyJs struct { + src string `json:"src"` +} + +type ThirdPartyMenuItem struct { + Text string `json:"text"` + Icon string `json:"icon"` + Href string `json:"href"` +} + +type ThirdPartyCss struct { + Href string `json:"href"` +} + +type ThirdPartyIntegration struct { + Routes []*ThirdPartyRoute `json:"routes"` + Js []*ThirdPartyJs `json:"js"` + Css []*ThirdPartyCss `json:"css"` + MenuItems []*ThirdPartyMenuItem `json:"menu_items"` +} diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index 2f7e5264e53..c411a47a977 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -16,8 +16,44 @@ type PluginMeta struct { Name string `json:"name"` } +type ThirdPartyRoute struct { + Path string `json:"path"` + Method string `json:"method"` + ReqSignedIn bool `json:"req_signed_in"` + ReqGrafanaAdmin bool `json:"req_grafana_admin"` + ReqRole string `json:"req_role"` + Url string `json:"url"` +} + +type ThirdPartyJs struct { + Src string `json:"src"` +} + +type ThirdPartyMenuItem struct { + Text string `json:"text"` + Icon string `json:"icon"` + Href string `json:"href"` +} + +type ThirdPartyCss struct { + Href string `json:"href"` +} + +type ThirdPartyIntegration struct { + Routes []*ThirdPartyRoute `json:"routes"` + Js []*ThirdPartyJs `json:"js"` + Css []*ThirdPartyCss `json:"css"` + MenuItems []*ThirdPartyMenuItem `json:"menu_items"` +} + +type ThirdPartyPlugin struct { + PluginType string `json:"pluginType"` + Integration ThirdPartyIntegration `json:"integration"` +} + var ( - DataSources map[string]interface{} + DataSources map[string]interface{} + Integrations []ThirdPartyIntegration ) type PluginScanner struct { @@ -31,6 +67,7 @@ func Init() { func scan(pluginDir string) error { DataSources = make(map[string]interface{}) + Integrations = make([]ThirdPartyIntegration, 0) scanner := &PluginScanner{ pluginPath: pluginDir, @@ -94,6 +131,14 @@ func (scanner *PluginScanner) loadPluginJson(path string) error { DataSources[datasourceType.(string)] = pluginJson } + if pluginType == "thirdPartyIntegration" { + p := ThirdPartyPlugin{} + reader.Seek(0, 0) + if err := jsonParser.Decode(&p); err != nil { + return err + } + Integrations = append(Integrations, p.Integration) + } return nil } diff --git a/public/app/app.js b/public/app/app.js index 4f30df34d89..16ce8a454d8 100644 --- a/public/app/app.js +++ b/public/app/app.js @@ -37,6 +37,8 @@ function (angular, $, _, appLevelRequire) { } else { _.extend(module, register_fns); } + // push it into the apps dependencies + apps_deps.push(module.name); return module; }; @@ -66,8 +68,6 @@ function (angular, $, _, appLevelRequire) { var module_name = 'grafana.'+type; // create the module app.useModule(angular.module(module_name, [])); - // push it into the apps dependencies - apps_deps.push(module_name); }); var preBootRequires = [ diff --git a/public/app/controllers/sidemenuCtrl.js b/public/app/controllers/sidemenuCtrl.js index b7ba32f0d35..bd6538b15cb 100644 --- a/public/app/controllers/sidemenuCtrl.js +++ b/public/app/controllers/sidemenuCtrl.js @@ -29,6 +29,16 @@ function (angular, _, $, config) { href: $scope.getUrl("/datasources"), }); } + + if (_.isArray(window.thirdParty.MainLinks)) { + _.forEach(window.thirdParty.MainLinks, function(item) { + $scope.mainLinks.push({ + text: item.text, + icon: item.icon, + href: $scope.getUrl(item.href) + }); + }); + } }; $scope.loadOrgs = function() { diff --git a/public/app/core/routes/all.js b/public/app/core/routes/all.js index a7e36a0e228..b0e41bc956d 100644 --- a/public/app/core/routes/all.js +++ b/public/app/core/routes/all.js @@ -10,7 +10,7 @@ define([ $locationProvider.html5Mode(true); var loadOrgBundle = new BundleLoader.BundleLoader('app/features/org/all'); - + console.log("adding grafana routes"); $routeProvider .when('/', { templateUrl: 'app/partials/dashboard.html', diff --git a/public/app/plugins/thirdPartyIntegration/raintank/plugin.json b/public/app/plugins/thirdPartyIntegration/raintank/plugin.json new file mode 100644 index 00000000000..7c1dcb0dcee --- /dev/null +++ b/public/app/plugins/thirdPartyIntegration/raintank/plugin.json @@ -0,0 +1,40 @@ +{ + "pluginType": "thirdPartyIntegration", + "integration": { + "routes": [ + { + "path": "/raintank/public/*", + "method": "*", + "req_signed_in": false, + "req_grafana_admin": false, + "req_role": "Admin", + "url": "http://localhost:3001/public" + }, + { + "path": "/raintank/api/*", + "method": "*", + "req_signed_in": true, + "req_grafana_admin": false, + "req_role": "Admin", + "url": "http://localhost:3001/api" + } + ], + "css": [ + { + "href": "/path/to/file.css" + } + ], + "js": [ + { + "src": "/raintank/public/app.js" + } + ], + "menu_items": [ + { + "text": "Menu Text", + "icon": "fa fa-fw fa-database", + "href": "/raintank/test" + } + ] + } +} diff --git a/public/views/index.html b/public/views/index.html index 600e18fb954..9998b0d2b68 100644 --- a/public/views/index.html +++ b/public/views/index.html @@ -13,6 +13,9 @@ [[else]] [[end]] + [[ range $css := .ThirdPartyCss ]] + + [[ end ]] @@ -53,11 +56,17 @@ settings: [[.Settings]], }; + window.thirdParty = { + MainLinks: [[.ThirdPartyMenu]] + }; + require(['app/app'], function (app) { - app.boot(); + app.boot(); }) - + [[ range $js := .ThirdPartyJs]] + + [[ end ]] [[if .GoogleAnalyticsId]] - + diff --git a/tasks/options/requirejs.js b/tasks/options/requirejs.js index a1636af98e5..d61aec067c2 100644 --- a/tasks/options/requirejs.js +++ b/tasks/options/requirejs.js @@ -6,7 +6,7 @@ module.exports = function(config,grunt) { var options = { appDir: '<%= genDir %>', dir: '<%= tempDir %>', - mainConfigFile: '<%= genDir %>/app/components/require.config.js', + mainConfigFile: '<%= genDir %>/app/require_config.js', baseUrl: './', waitSeconds: 0, @@ -41,16 +41,12 @@ module.exports = function(config,grunt) { // main/common module name: 'app/app', include: [ - 'kbn', 'text', 'jquery', - 'angular', - 'settings', 'bootstrap', 'modernizr', 'timepicker', 'datepicker', - 'lodash', 'jquery.flot', 'angular-strap', 'angular-dragdrop', @@ -58,7 +54,6 @@ module.exports = function(config,grunt) { 'app/services/all', 'app/features/all', 'app/controllers/all', - 'app/components/partials', // bundle the datasources 'app/plugins/datasource/grafana/datasource', 'app/plugins/datasource/graphite/datasource', From 152b484eb5e97a5f966c38cc36390f6a4c85b8dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 30 Oct 2015 15:16:05 +0100 Subject: [PATCH 059/394] refactoring: moved app/controllers -> app/core/controllers --- public/app/app.js | 1 - public/app/controllers/metricKeys.js | 186 ------------------ public/app/{ => core}/controllers/all.js | 0 .../app/{ => core}/controllers/errorCtrl.js | 8 +- .../app/{ => core}/controllers/grafanaCtrl.js | 7 +- .../app/{ => core}/controllers/inspectCtrl.js | 7 +- .../app/{ => core}/controllers/invitedCtrl.js | 9 +- .../{ => core}/controllers/jsonEditorCtrl.js | 8 +- .../app/{ => core}/controllers/loginCtrl.js | 7 +- .../controllers/resetPasswordCtrl.js | 8 +- public/app/{ => core}/controllers/search.js | 7 +- .../{ => core}/controllers/sidemenuCtrl.js | 7 +- .../app/{ => core}/controllers/signupCtrl.ts | 7 +- public/app/core/core.ts | 1 + 14 files changed, 31 insertions(+), 232 deletions(-) delete mode 100644 public/app/controllers/metricKeys.js rename public/app/{ => core}/controllers/all.js (100%) rename public/app/{ => core}/controllers/errorCtrl.js (60%) rename public/app/{ => core}/controllers/grafanaCtrl.js (95%) rename public/app/{ => core}/controllers/inspectCtrl.js (92%) rename public/app/{ => core}/controllers/invitedCtrl.js (82%) rename public/app/{ => core}/controllers/jsonEditorCtrl.js (68%) rename public/app/{ => core}/controllers/loginCtrl.js (92%) rename public/app/{ => core}/controllers/resetPasswordCtrl.js (84%) rename public/app/{ => core}/controllers/search.js (95%) rename public/app/{ => core}/controllers/sidemenuCtrl.js (94%) rename public/app/{ => core}/controllers/signupCtrl.ts (89%) diff --git a/public/app/app.js b/public/app/app.js index b15bb43ddf5..6ffc95cb4f0 100644 --- a/public/app/app.js +++ b/public/app/app.js @@ -71,7 +71,6 @@ function (angular, $, _, appLevelRequire) { var preBootRequires = [ 'app/services/all', 'app/features/all', - 'app/controllers/all', ]; app.boot = function() { diff --git a/public/app/controllers/metricKeys.js b/public/app/controllers/metricKeys.js deleted file mode 100644 index ac8dffdcdf7..00000000000 --- a/public/app/controllers/metricKeys.js +++ /dev/null @@ -1,186 +0,0 @@ -define([ - 'angular', - 'lodash', - 'app/core/config' -], -function (angular, _, config) { - 'use strict'; - - var module = angular.module('grafana.controllers'); - - module.controller('MetricKeysCtrl', function($scope, $http, $q) { - var elasticSearchUrlForMetricIndex = config.elasticsearch + '/' + config.grafana_metrics_index + '/'; - var httpOptions = {}; - if (config.elasticsearchBasicAuth) { - httpOptions.withCredentials = true; - httpOptions.headers = { - "Authorization": "Basic " + config.elasticsearchBasicAuth - }; - } - $scope.init = function () { - $scope.metricPath = "prod.apps.api.boobarella.*"; - $scope.metricCounter = 0; - }; - - $scope.createIndex = function () { - $scope.errorText = null; - $scope.infoText = null; - - deleteIndex() - .then(createIndex) - .then(function () { - $scope.infoText = "Index created!"; - }) - .then(null, function (err) { - $scope.errorText = angular.toJson(err); - }); - }; - - $scope.loadMetricsFromPath = function() { - $scope.errorText = null; - $scope.infoText = null; - $scope.metricCounter = 0; - - return loadMetricsRecursive($scope.metricPath) - .then(function() { - $scope.infoText = "Indexing completed!"; - }, function(err) { - $scope.errorText = "Error: " + err; - }); - }; - - $scope.loadAll = function() { - $scope.infoText = "Fetching all metrics from graphite..."; - - getFromEachGraphite('/metrics/index.json', saveMetricsArray) - .then(function() { - $scope.infoText = "Indexing complete!"; - }).then(null, function(err) { - $scope.errorText = err; - }); - }; - - function getFromEachGraphite(request, data_callback, error_callback) { - return $q.all(_.map(config.datasources, function(datasource) { - if (datasource.type = 'graphite') { - return $http.get(datasource.url + request) - .then(data_callback, error_callback); - } - })); - } - - function saveMetricsArray(data, currentIndex) { - if (!data && !data.data && data.data.length === 0) { - return $q.reject('No metrics from graphite'); - } - - if (data.data.length === currentIndex) { - return $q.when('done'); - } - - currentIndex = currentIndex || 0; - - return saveMetricKey(data.data[currentIndex]) - .then(function() { - return saveMetricsArray(data, currentIndex + 1); - }); - } - - function deleteIndex() - { - var deferred = $q.defer(); - $http.delete(elasticSearchUrlForMetricIndex, httpOptions) - .success(function() { - deferred.resolve('ok'); - }) - .error(function(data, status) { - if (status === 404) { - deferred.resolve('ok'); - } - else { - deferred.reject('elastic search returned unexpected error'); - } - }); - - return deferred.promise; - } - - function createIndex() - { - return $http.put(elasticSearchUrlForMetricIndex, { - settings: { - analysis: { - analyzer: { - metric_path_ngram : { tokenizer : "my_ngram_tokenizer" } - }, - tokenizer: { - my_ngram_tokenizer : { - type : "nGram", - min_gram : "3", - max_gram : "8", - token_chars: ["letter", "digit", "punctuation", "symbol"] - } - } - } - }, - mappings: { - metricKey: { - properties: { - metricPath: { - type: "multi_field", - fields: { - "metricPath": { type: "string", index: "analyzed", index_analyzer: "standard" }, - "metricPath_ng": { type: "string", index: "analyzed", index_analyzer: "metric_path_ngram" } - } - } - } - } - } - }, httpOptions); - } - - function receiveMetric(result) { - var data = result.data; - if (!data || data.length === 0) { - console.log('no data'); - return; - } - - var funcs = _.map(data, function(metric) { - if (metric.expandable) { - return loadMetricsRecursive(metric.id + ".*"); - } - if (metric.leaf) { - return saveMetricKey(metric.id); - } - }); - - return $q.all(funcs); - } - - function saveMetricKey(metricId) { - - // Create request with id as title. Rethink this. - var request = $scope.ejs.Document(config.grafana_metrics_index, 'metricKey', metricId).source({ - metricPath: metricId - }); - - return request.doIndex( - function() { - $scope.infoText = "Indexing " + metricId; - $scope.metricCounter = $scope.metricCounter + 1; - }, - function() { - $scope.errorText = "failed to save metric " + metricId; - } - ); - } - - function loadMetricsRecursive(metricPath) - { - return getFromEachGraphite('/metrics/find/?query=' + metricPath, receiveMetric); - } - - }); - -}); diff --git a/public/app/controllers/all.js b/public/app/core/controllers/all.js similarity index 100% rename from public/app/controllers/all.js rename to public/app/core/controllers/all.js diff --git a/public/app/controllers/errorCtrl.js b/public/app/core/controllers/errorCtrl.js similarity index 60% rename from public/app/controllers/errorCtrl.js rename to public/app/core/controllers/errorCtrl.js index 13b64584226..9816928fa6c 100644 --- a/public/app/controllers/errorCtrl.js +++ b/public/app/core/controllers/errorCtrl.js @@ -1,13 +1,11 @@ define([ 'angular', - 'lodash' + '../core_module', ], -function (angular) { +function (angular, coreModule) { 'use strict'; - var module = angular.module('grafana.controllers'); - - module.controller('ErrorCtrl', function($scope, contextSrv) { + coreModule.controller('ErrorCtrl', function($scope, contextSrv) { var showSideMenu = contextSrv.sidemenu; contextSrv.sidemenu = false; diff --git a/public/app/controllers/grafanaCtrl.js b/public/app/core/controllers/grafanaCtrl.js similarity index 95% rename from public/app/controllers/grafanaCtrl.js rename to public/app/core/controllers/grafanaCtrl.js index 42a67064e29..baafd15938e 100644 --- a/public/app/controllers/grafanaCtrl.js +++ b/public/app/core/controllers/grafanaCtrl.js @@ -2,15 +2,14 @@ define([ 'angular', 'lodash', 'jquery', + '../core_module', 'app/core/config', 'app/core/store', ], -function (angular, _, $, config, store) { +function (angular, _, $, coreModule, config, store) { "use strict"; - var module = angular.module('grafana.controllers'); - - module.controller('GrafanaCtrl', function($scope, alertSrv, utilSrv, $rootScope, $controller, contextSrv) { + coreModule.controller('GrafanaCtrl', function($scope, alertSrv, utilSrv, $rootScope, $controller, contextSrv) { $scope.init = function() { $scope.contextSrv = contextSrv; diff --git a/public/app/controllers/inspectCtrl.js b/public/app/core/controllers/inspectCtrl.js similarity index 92% rename from public/app/controllers/inspectCtrl.js rename to public/app/core/controllers/inspectCtrl.js index e70a88323e9..81cfaf64a85 100644 --- a/public/app/controllers/inspectCtrl.js +++ b/public/app/core/controllers/inspectCtrl.js @@ -2,13 +2,12 @@ define([ 'angular', 'lodash', 'jquery', + '../core_module', ], -function (angular, _, $) { +function (angular, _, $, coreModule) { 'use strict'; - var module = angular.module('grafana.controllers'); - - module.controller('InspectCtrl', function($scope) { + coreModule.controller('InspectCtrl', function($scope) { var model = $scope.inspector; function getParametersFromQueryString(queryString) { diff --git a/public/app/controllers/invitedCtrl.js b/public/app/core/controllers/invitedCtrl.js similarity index 82% rename from public/app/controllers/invitedCtrl.js rename to public/app/core/controllers/invitedCtrl.js index 42443c4370f..540cb01ca1c 100644 --- a/public/app/controllers/invitedCtrl.js +++ b/public/app/core/controllers/invitedCtrl.js @@ -1,16 +1,13 @@ define([ 'angular', + '../core_module', 'app/core/config', ], -function (angular, config) { +function (angular, coreModule, config) { 'use strict'; - var module = angular.module('grafana.controllers'); - - module.controller('InvitedCtrl', function($scope, $routeParams, contextSrv, backendSrv) { - + coreModule.controller('InvitedCtrl', function($scope, $routeParams, contextSrv, backendSrv) { contextSrv.sidemenu = false; - $scope.formModel = {}; $scope.init = function() { diff --git a/public/app/controllers/jsonEditorCtrl.js b/public/app/core/controllers/jsonEditorCtrl.js similarity index 68% rename from public/app/controllers/jsonEditorCtrl.js rename to public/app/core/controllers/jsonEditorCtrl.js index 60bda8514b7..0bfd5fcfb05 100644 --- a/public/app/controllers/jsonEditorCtrl.js +++ b/public/app/core/controllers/jsonEditorCtrl.js @@ -1,13 +1,11 @@ define([ 'angular', - 'lodash' + '../core_module', ], -function (angular) { +function (angular, coreModule) { 'use strict'; - var module = angular.module('grafana.controllers'); - - module.controller('JsonEditorCtrl', function($scope) { + coreModule.controller('JsonEditorCtrl', function($scope) { $scope.json = angular.toJson($scope.object, true); $scope.canUpdate = $scope.updateHandler !== void 0; diff --git a/public/app/controllers/loginCtrl.js b/public/app/core/controllers/loginCtrl.js similarity index 92% rename from public/app/controllers/loginCtrl.js rename to public/app/core/controllers/loginCtrl.js index 7ec2c4353fd..22cb2c6f04b 100644 --- a/public/app/controllers/loginCtrl.js +++ b/public/app/core/controllers/loginCtrl.js @@ -1,13 +1,12 @@ define([ 'angular', + '../core_module', 'app/core/config', ], -function (angular, config) { +function (angular, coreModule, config) { 'use strict'; - var module = angular.module('grafana.controllers'); - - module.controller('LoginCtrl', function($scope, backendSrv, contextSrv, $location) { + coreModule.controller('LoginCtrl', function($scope, backendSrv, contextSrv, $location) { $scope.formModel = { user: '', email: '', diff --git a/public/app/controllers/resetPasswordCtrl.js b/public/app/core/controllers/resetPasswordCtrl.js similarity index 84% rename from public/app/controllers/resetPasswordCtrl.js rename to public/app/core/controllers/resetPasswordCtrl.js index ed693f0d45a..d414b059458 100644 --- a/public/app/controllers/resetPasswordCtrl.js +++ b/public/app/core/controllers/resetPasswordCtrl.js @@ -1,13 +1,11 @@ define([ 'angular', + '../core_module', ], -function (angular) { +function (angular, coreModule) { 'use strict'; - var module = angular.module('grafana.controllers'); - - module.controller('ResetPasswordCtrl', function($scope, contextSrv, backendSrv, $location) { - + coreModule.controller('ResetPasswordCtrl', function($scope, contextSrv, backendSrv, $location) { contextSrv.sidemenu = false; $scope.formModel = {}; $scope.mode = 'send'; diff --git a/public/app/controllers/search.js b/public/app/core/controllers/search.js similarity index 95% rename from public/app/controllers/search.js rename to public/app/core/controllers/search.js index cbae6366244..bbf869b9f38 100644 --- a/public/app/controllers/search.js +++ b/public/app/core/controllers/search.js @@ -1,14 +1,13 @@ define([ 'angular', 'lodash', + '../core_module', 'app/core/config', ], -function (angular, _, config) { +function (angular, _, coreModule, config) { 'use strict'; - var module = angular.module('grafana.controllers'); - - module.controller('SearchCtrl', function($scope, $location, $timeout, backendSrv) { + coreModule.controller('SearchCtrl', function($scope, $location, $timeout, backendSrv) { $scope.init = function() { $scope.giveSearchFocus = 0; diff --git a/public/app/controllers/sidemenuCtrl.js b/public/app/core/controllers/sidemenuCtrl.js similarity index 94% rename from public/app/controllers/sidemenuCtrl.js rename to public/app/core/controllers/sidemenuCtrl.js index 7fe8a5036b4..c2ee868323f 100644 --- a/public/app/controllers/sidemenuCtrl.js +++ b/public/app/core/controllers/sidemenuCtrl.js @@ -2,14 +2,13 @@ define([ 'angular', 'lodash', 'jquery', + '../core_module', 'app/core/config', ], -function (angular, _, $, config) { +function (angular, _, $, coreModule, config) { 'use strict'; - var module = angular.module('grafana.controllers'); - - module.controller('SideMenuCtrl', function($scope, $location, contextSrv, backendSrv) { + coreModule.controller('SideMenuCtrl', function($scope, $location, contextSrv, backendSrv) { $scope.getUrl = function(url) { return config.appSubUrl + url; diff --git a/public/app/controllers/signupCtrl.ts b/public/app/core/controllers/signupCtrl.ts similarity index 89% rename from public/app/controllers/signupCtrl.ts rename to public/app/core/controllers/signupCtrl.ts index bda332f664d..9c18b121612 100644 --- a/public/app/controllers/signupCtrl.ts +++ b/public/app/core/controllers/signupCtrl.ts @@ -1,9 +1,8 @@ -/// +/// import angular = require('angular'); import config = require('app/core/config'); - -var module = angular.module('grafana.controllers'); +import coreModule = require('../core_module'); export class SignUpCtrl { @@ -48,5 +47,5 @@ export class SignUpCtrl { }; } -module.controller('SignUpCtrl', SignUpCtrl); +coreModule.controller('SignUpCtrl', SignUpCtrl); diff --git a/public/app/core/core.ts b/public/app/core/core.ts index 6c9d0cea958..1d4683b0ce7 100644 --- a/public/app/core/core.ts +++ b/public/app/core/core.ts @@ -16,6 +16,7 @@ /// /// +/// /// /// From 97697b93edd5cffe3c383bc7b6184132d856c4ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 30 Oct 2015 15:58:20 +0100 Subject: [PATCH 060/394] refactoring: moving and renaming things --- public/app/app.js | 1 - public/app/core/core.ts | 1 + public/app/{ => core}/services/alertSrv.js | 9 ++++----- public/app/{ => core}/services/all.js | 0 public/app/{ => core}/services/analytics.js | 7 +++---- public/app/{ => core}/services/backendSrv.js | 7 +++---- public/app/{ => core}/services/contextSrv.js | 7 +++---- public/app/{ => core}/services/datasourceSrv.js | 7 +++---- public/app/{ => core}/services/keyboardManager.js | 9 ++++----- public/app/{ => core}/services/popoverSrv.js | 7 +++---- public/app/{ => core}/services/timer.js | 11 +++++------ public/app/{ => core}/services/uiSegmentSrv.js | 7 +++---- public/app/{ => core}/services/utilSrv.js | 7 +++---- .../datasource/cloudwatch/specs/datasource_specs.ts | 3 +++ .../elasticsearch/specs/datasource_specs.ts | 1 + .../elasticsearch/specs/query_ctrl_specs.ts | 2 +- .../datasource/graphite/specs/datasource_specs.ts | 4 +++- .../datasource/graphite/specs/query_ctrl_specs.ts | 4 +++- .../datasource/influxdb/specs/query_ctrl_specs.ts | 3 ++- .../datasource/influxdb_08/specs/datasource-specs.ts | 5 +++-- .../datasource/prometheus/specs/datasource_specs.ts | 3 +++ public/test/{specs => }/core/time_series_specs.js | 0 public/test/{specs => }/core/utils/datemath_specs.ts | 0 public/test/{specs => }/core/utils/rangeutil_specs.ts | 0 public/test/specs/kairosdb-datasource-specs.js | 3 +++ public/test/specs/linkSrv-specs.js | 1 + public/test/specs/opentsdbDatasource-specs.js | 1 + .../specs/{timeSrv-specs.js => time_srv_specs.js} | 7 ++++--- tasks/options/requirejs.js | 2 -- 29 files changed, 63 insertions(+), 56 deletions(-) rename public/app/{ => core}/services/alertSrv.js (92%) rename public/app/{ => core}/services/all.js (100%) rename public/app/{ => core}/services/analytics.js (75%) rename public/app/{ => core}/services/backendSrv.js (95%) rename public/app/{ => core}/services/contextSrv.js (91%) rename public/app/{ => core}/services/datasourceSrv.js (92%) rename public/app/{ => core}/services/keyboardManager.js (97%) rename public/app/{ => core}/services/popoverSrv.js (88%) rename public/app/{ => core}/services/timer.js (80%) rename public/app/{ => core}/services/uiSegmentSrv.js (94%) rename public/app/{ => core}/services/utilSrv.js (78%) rename public/test/{specs => }/core/time_series_specs.js (100%) rename public/test/{specs => }/core/utils/datemath_specs.ts (100%) rename public/test/{specs => }/core/utils/rangeutil_specs.ts (100%) rename public/test/specs/{timeSrv-specs.js => time_srv_specs.js} (96%) diff --git a/public/app/app.js b/public/app/app.js index 6ffc95cb4f0..0637b3aeea7 100644 --- a/public/app/app.js +++ b/public/app/app.js @@ -69,7 +69,6 @@ function (angular, $, _, appLevelRequire) { }); var preBootRequires = [ - 'app/services/all', 'app/features/all', ]; diff --git a/public/app/core/core.ts b/public/app/core/core.ts index 1d4683b0ce7..9b726de271a 100644 --- a/public/app/core/core.ts +++ b/public/app/core/core.ts @@ -17,6 +17,7 @@ /// /// +/// /// /// diff --git a/public/app/services/alertSrv.js b/public/app/core/services/alertSrv.js similarity index 92% rename from public/app/services/alertSrv.js rename to public/app/core/services/alertSrv.js index 3d0f9e66ff2..f3b5329cd60 100644 --- a/public/app/services/alertSrv.js +++ b/public/app/core/services/alertSrv.js @@ -1,13 +1,12 @@ define([ 'angular', - 'lodash' + 'lodash', + '../core_module', ], -function (angular, _) { +function (angular, _, coreModule) { 'use strict'; - var module = angular.module('grafana.services'); - - module.service('alertSrv', function($timeout, $sce, $rootScope, $modal, $q) { + coreModule.service('alertSrv', function($timeout, $sce, $rootScope, $modal, $q) { var self = this; this.init = function() { diff --git a/public/app/services/all.js b/public/app/core/services/all.js similarity index 100% rename from public/app/services/all.js rename to public/app/core/services/all.js diff --git a/public/app/services/analytics.js b/public/app/core/services/analytics.js similarity index 75% rename from public/app/services/analytics.js rename to public/app/core/services/analytics.js index 4bb7f0c79db..e09ee6c9b77 100644 --- a/public/app/services/analytics.js +++ b/public/app/core/services/analytics.js @@ -1,12 +1,11 @@ define([ 'angular', + '../core_module', ], -function(angular) { +function(angular, coreModule) { 'use strict'; - var module = angular.module('grafana.services'); - module.service('googleAnalyticsSrv', function($rootScope, $location) { - + coreModule.service('googleAnalyticsSrv', function($rootScope, $location) { var first = true; this.init = function() { diff --git a/public/app/services/backendSrv.js b/public/app/core/services/backendSrv.js similarity index 95% rename from public/app/services/backendSrv.js rename to public/app/core/services/backendSrv.js index 4043e66a44b..43b5193e66e 100644 --- a/public/app/services/backendSrv.js +++ b/public/app/core/services/backendSrv.js @@ -1,14 +1,13 @@ define([ 'angular', 'lodash', + '../core_module', 'app/core/config', ], -function (angular, _, config) { +function (angular, _, coreModule, config) { 'use strict'; - var module = angular.module('grafana.services'); - - module.service('backendSrv', function($http, alertSrv, $timeout) { + coreModule.service('backendSrv', function($http, alertSrv, $timeout) { var self = this; this.get = function(url, params) { diff --git a/public/app/services/contextSrv.js b/public/app/core/services/contextSrv.js similarity index 91% rename from public/app/services/contextSrv.js rename to public/app/core/services/contextSrv.js index 8bef719e198..77f10fdf16a 100644 --- a/public/app/services/contextSrv.js +++ b/public/app/core/services/contextSrv.js @@ -1,15 +1,14 @@ define([ 'angular', 'lodash', + '../core_module', 'app/core/store', 'app/core/config', ], -function (angular, _, store, config) { +function (angular, _, coreModule, store, config) { 'use strict'; - var module = angular.module('grafana.services'); - - module.service('contextSrv', function($rootScope, $timeout) { + coreModule.service('contextSrv', function($rootScope, $timeout) { var self = this; function User() { diff --git a/public/app/services/datasourceSrv.js b/public/app/core/services/datasourceSrv.js similarity index 92% rename from public/app/services/datasourceSrv.js rename to public/app/core/services/datasourceSrv.js index e4dd08e3c81..9709bb88cdf 100644 --- a/public/app/services/datasourceSrv.js +++ b/public/app/core/services/datasourceSrv.js @@ -1,14 +1,13 @@ define([ 'angular', 'lodash', + '../core_module', 'app/core/config', ], -function (angular, _, config) { +function (angular, _, coreModule, config) { 'use strict'; - var module = angular.module('grafana.services'); - - module.service('datasourceSrv', function($q, $injector, $rootScope) { + coreModule.service('datasourceSrv', function($q, $injector, $rootScope) { var self = this; this.init = function() { diff --git a/public/app/services/keyboardManager.js b/public/app/core/services/keyboardManager.js similarity index 97% rename from public/app/services/keyboardManager.js rename to public/app/core/services/keyboardManager.js index f45ddad305b..14b65465281 100644 --- a/public/app/services/keyboardManager.js +++ b/public/app/core/services/keyboardManager.js @@ -1,15 +1,14 @@ define([ 'angular', - 'lodash' + 'lodash', + '../core_module', ], -function (angular, _) { +function (angular, _, coreModule) { 'use strict'; - var module = angular.module('grafana.services'); - // This service was based on OpenJS library available in BSD License // http://www.openjs.com/scripts/events/keyboard_shortcuts/index.php - module.factory('keyboardManager', ['$window', '$timeout', function ($window, $timeout) { + coreModule.factory('keyboardManager', ['$window', '$timeout', function ($window, $timeout) { var keyboardManagerService = {}; var defaultOpt = { diff --git a/public/app/services/popoverSrv.js b/public/app/core/services/popoverSrv.js similarity index 88% rename from public/app/services/popoverSrv.js rename to public/app/core/services/popoverSrv.js index cec294178c0..26a935bf283 100644 --- a/public/app/services/popoverSrv.js +++ b/public/app/core/services/popoverSrv.js @@ -2,13 +2,12 @@ define([ 'angular', 'lodash', 'jquery', + '../core_module', ], -function (angular, _, $) { +function (angular, _, $, coreModule) { 'use strict'; - var module = angular.module('grafana.services'); - - module.service('popoverSrv', function($templateCache, $timeout, $q, $http, $compile) { + coreModule.service('popoverSrv', function($templateCache, $timeout, $q, $http, $compile) { this.getTemplate = function(url) { return $q.when($templateCache.get(url) || $http.get(url, {cache: true})); diff --git a/public/app/services/timer.js b/public/app/core/services/timer.js similarity index 80% rename from public/app/services/timer.js rename to public/app/core/services/timer.js index 3939fe6b78f..668388a786e 100644 --- a/public/app/services/timer.js +++ b/public/app/core/services/timer.js @@ -1,13 +1,12 @@ define([ 'angular', - 'lodash' + 'lodash', + '../core_module', ], -function (angular, _) { +function (angular, _, coreModule) { 'use strict'; - var module = angular.module('grafana.services'); - - module.service('timer', function($timeout) { + coreModule.service('timer', function($timeout) { // This service really just tracks a list of $timeout promises to give us a // method for cancelling them all when we need to @@ -31,4 +30,4 @@ function (angular, _) { }; }); -}); \ No newline at end of file +}); diff --git a/public/app/services/uiSegmentSrv.js b/public/app/core/services/uiSegmentSrv.js similarity index 94% rename from public/app/services/uiSegmentSrv.js rename to public/app/core/services/uiSegmentSrv.js index 2973089b6ba..cb11512e12f 100644 --- a/public/app/services/uiSegmentSrv.js +++ b/public/app/core/services/uiSegmentSrv.js @@ -1,13 +1,12 @@ define([ 'angular', 'lodash', + '../core_module', ], -function (angular, _) { +function (angular, _, coreModule) { 'use strict'; - var module = angular.module('grafana.services'); - - module.service('uiSegmentSrv', function($sce, templateSrv) { + coreModule.service('uiSegmentSrv', function($sce, templateSrv) { function MetricSegment(options) { if (options === '*' || options.value === '*') { diff --git a/public/app/services/utilSrv.js b/public/app/core/services/utilSrv.js similarity index 78% rename from public/app/services/utilSrv.js rename to public/app/core/services/utilSrv.js index b9e703443e4..2418546e5da 100644 --- a/public/app/services/utilSrv.js +++ b/public/app/core/services/utilSrv.js @@ -1,12 +1,11 @@ define([ 'angular', + '../core_module', ], -function (angular) { +function (angular, coreModule) { 'use strict'; - var module = angular.module('grafana.services'); - - module.service('utilSrv', function($rootScope, $modal, $q) { + coreModule.service('utilSrv', function($rootScope, $modal, $q) { this.init = function() { $rootScope.onAppEvent('show-modal', this.showModal, $rootScope); diff --git a/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts b/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts index 4714a642d30..0a569275d05 100644 --- a/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts @@ -8,10 +8,13 @@ declare var helpers: any; describe('CloudWatchDatasource', function() { var ctx = new helpers.ServiceTestContext(); + beforeEach(angularMocks.module('grafana.core')); beforeEach(angularMocks.module('grafana.services')); beforeEach(angularMocks.module('grafana.controllers')); + beforeEach(ctx.providePhase(['templateSrv', 'backendSrv'])); beforeEach(ctx.createService('CloudWatchDatasource')); + beforeEach(function() { ctx.ds = new ctx.service({ jsonData: { diff --git a/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts b/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts index 584f915a86d..f087f847a19 100644 --- a/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts @@ -10,6 +10,7 @@ declare var helpers: any; describe('ElasticDatasource', function() { var ctx = new helpers.ServiceTestContext(); + beforeEach(angularMocks.module('grafana.core')); beforeEach(angularMocks.module('grafana.services')); beforeEach(ctx.providePhase(['templateSrv', 'backendSrv'])); beforeEach(ctx.createService('ElasticDatasource')); diff --git a/public/app/plugins/datasource/elasticsearch/specs/query_ctrl_specs.ts b/public/app/plugins/datasource/elasticsearch/specs/query_ctrl_specs.ts index d88e8446ead..bcf111827a6 100644 --- a/public/app/plugins/datasource/elasticsearch/specs/query_ctrl_specs.ts +++ b/public/app/plugins/datasource/elasticsearch/specs/query_ctrl_specs.ts @@ -1,5 +1,5 @@ /// -/// +/// /// import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; diff --git a/public/app/plugins/datasource/graphite/specs/datasource_specs.ts b/public/app/plugins/datasource/graphite/specs/datasource_specs.ts index 8ba7e35b773..0ab4899e6eb 100644 --- a/public/app/plugins/datasource/graphite/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/graphite/specs/datasource_specs.ts @@ -7,10 +7,12 @@ declare var helpers: any; describe('graphiteDatasource', function() { var ctx = new helpers.ServiceTestContext(); + beforeEach(angularMocks.module('grafana.core')); beforeEach(angularMocks.module('grafana.services')); - beforeEach(ctx.providePhase(['backendSrv'])); + beforeEach(ctx.providePhase(['backendSrv'])); beforeEach(ctx.createService('GraphiteDatasource')); + beforeEach(function() { ctx.ds = new ctx.service({ url: [''] }); }); diff --git a/public/app/plugins/datasource/graphite/specs/query_ctrl_specs.ts b/public/app/plugins/datasource/graphite/specs/query_ctrl_specs.ts index 0b12ea051e0..03602973f25 100644 --- a/public/app/plugins/datasource/graphite/specs/query_ctrl_specs.ts +++ b/public/app/plugins/datasource/graphite/specs/query_ctrl_specs.ts @@ -1,6 +1,6 @@ /// /// -/// +/// /// import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; @@ -11,8 +11,10 @@ declare var helpers: any; describe('GraphiteQueryCtrl', function() { var ctx = new helpers.ControllerTestContext(); + beforeEach(angularMocks.module('grafana.core')); beforeEach(angularMocks.module('grafana.controllers')); beforeEach(angularMocks.module('grafana.services')); + beforeEach(ctx.providePhase()); beforeEach(ctx.createControllerPhase('GraphiteQueryCtrl')); diff --git a/public/app/plugins/datasource/influxdb/specs/query_ctrl_specs.ts b/public/app/plugins/datasource/influxdb/specs/query_ctrl_specs.ts index ee02338c3da..6da2c78535d 100644 --- a/public/app/plugins/datasource/influxdb/specs/query_ctrl_specs.ts +++ b/public/app/plugins/datasource/influxdb/specs/query_ctrl_specs.ts @@ -1,5 +1,5 @@ /// -/// +/// /// import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; @@ -9,6 +9,7 @@ declare var helpers: any; describe('InfluxDBQueryCtrl', function() { var ctx = new helpers.ControllerTestContext(); + beforeEach(angularMocks.module('grafana.core')); beforeEach(angularMocks.module('grafana.controllers')); beforeEach(angularMocks.module('grafana.services')); beforeEach(ctx.providePhase()); diff --git a/public/app/plugins/datasource/influxdb_08/specs/datasource-specs.ts b/public/app/plugins/datasource/influxdb_08/specs/datasource-specs.ts index 46459939828..4d3d2b3817f 100644 --- a/public/app/plugins/datasource/influxdb_08/specs/datasource-specs.ts +++ b/public/app/plugins/datasource/influxdb_08/specs/datasource-specs.ts @@ -1,6 +1,6 @@ /// -/// -/// +/// +/// /// import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; @@ -10,6 +10,7 @@ declare var helpers: any; describe('InfluxDatasource', function() { var ctx = new helpers.ServiceTestContext(); + beforeEach(angularMocks.module('grafana.core')); beforeEach(angularMocks.module('grafana.services')); beforeEach(ctx.providePhase(['templateSrv'])); beforeEach(ctx.createService('InfluxDatasource_08')); diff --git a/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts b/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts index a6b494ccbf8..2ac4992edef 100644 --- a/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/prometheus/specs/datasource_specs.ts @@ -6,12 +6,15 @@ import moment = require('moment'); declare var helpers: any; describe('PrometheusDatasource', function() { + var ctx = new helpers.ServiceTestContext(); + beforeEach(angularMocks.module('grafana.core')); beforeEach(angularMocks.module('grafana.services')); beforeEach(ctx.createService('PrometheusDatasource')); beforeEach(function() { ctx.ds = new ctx.service({ url: 'proxied', directUrl: 'direct', user: 'test', password: 'mupp' }); }); + describe('When querying prometheus with one target using query editor target spec', function() { var results; var urlExpected = 'proxied/api/v1/query_range?query=' + diff --git a/public/test/specs/core/time_series_specs.js b/public/test/core/time_series_specs.js similarity index 100% rename from public/test/specs/core/time_series_specs.js rename to public/test/core/time_series_specs.js diff --git a/public/test/specs/core/utils/datemath_specs.ts b/public/test/core/utils/datemath_specs.ts similarity index 100% rename from public/test/specs/core/utils/datemath_specs.ts rename to public/test/core/utils/datemath_specs.ts diff --git a/public/test/specs/core/utils/rangeutil_specs.ts b/public/test/core/utils/rangeutil_specs.ts similarity index 100% rename from public/test/specs/core/utils/rangeutil_specs.ts rename to public/test/core/utils/rangeutil_specs.ts diff --git a/public/test/specs/kairosdb-datasource-specs.js b/public/test/specs/kairosdb-datasource-specs.js index edee99752f7..47cb2d7bc46 100644 --- a/public/test/specs/kairosdb-datasource-specs.js +++ b/public/test/specs/kairosdb-datasource-specs.js @@ -7,9 +7,12 @@ define([ describe('KairosDBDatasource', function() { var ctx = new helpers.ServiceTestContext(); + beforeEach(module('grafana.core')); beforeEach(module('grafana.services')); + beforeEach(ctx.providePhase(['templateSrv'])); beforeEach(ctx.createService('KairosDBDatasource')); + beforeEach(function() { ctx.ds = new ctx.service({ url: ''}); }); diff --git a/public/test/specs/linkSrv-specs.js b/public/test/specs/linkSrv-specs.js index 8a978b650ff..9ecff29bc77 100644 --- a/public/test/specs/linkSrv-specs.js +++ b/public/test/specs/linkSrv-specs.js @@ -7,6 +7,7 @@ define([ describe('linkSrv', function() { var _linkSrv; + beforeEach(module('grafana.core')); beforeEach(module('grafana.services')); beforeEach(inject(function(linkSrv) { diff --git a/public/test/specs/opentsdbDatasource-specs.js b/public/test/specs/opentsdbDatasource-specs.js index 57818fdc4fa..88eacc832c6 100644 --- a/public/test/specs/opentsdbDatasource-specs.js +++ b/public/test/specs/opentsdbDatasource-specs.js @@ -7,6 +7,7 @@ define([ describe('opentsdb', function() { var ctx = new helpers.ServiceTestContext(); + beforeEach(module('grafana.core')); beforeEach(module('grafana.services')); beforeEach(ctx.providePhase(['backendSrv'])); diff --git a/public/test/specs/timeSrv-specs.js b/public/test/specs/time_srv_specs.js similarity index 96% rename from public/test/specs/timeSrv-specs.js rename to public/test/specs/time_srv_specs.js index d369456e457..4f065af6cf8 100644 --- a/public/test/specs/timeSrv-specs.js +++ b/public/test/specs/time_srv_specs.js @@ -1,9 +1,9 @@ define([ - '../mocks/dashboard-mock', - './helpers', + 'test/mocks/dashboard-mock', + 'test/specs/helpers', 'lodash', 'moment', - 'app/services/timer', + 'app/core/services/timer', 'app/features/dashboard/timeSrv' ], function(dashboardMock, helpers, _, moment) { 'use strict'; @@ -12,6 +12,7 @@ define([ var ctx = new helpers.ServiceTestContext(); var _dashboard; + beforeEach(module('grafana.core')); beforeEach(module('grafana.services')); beforeEach(ctx.providePhase(['$routeParams'])); beforeEach(ctx.createService('timeSrv')); diff --git a/tasks/options/requirejs.js b/tasks/options/requirejs.js index d61aec067c2..6780eb4bad4 100644 --- a/tasks/options/requirejs.js +++ b/tasks/options/requirejs.js @@ -51,9 +51,7 @@ module.exports = function(config,grunt) { 'angular-strap', 'angular-dragdrop', 'app/core/core', - 'app/services/all', 'app/features/all', - 'app/controllers/all', // bundle the datasources 'app/plugins/datasource/grafana/datasource', 'app/plugins/datasource/graphite/datasource', From d8f68eb11833c73b9509680de411535c8d2b56cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 30 Oct 2015 16:06:29 +0100 Subject: [PATCH 061/394] refactoring: moving and renaming things --- public/app/core/controllers/all.js | 20 +++++++++---------- .../{errorCtrl.js => error_ctrl.js} | 0 .../{grafanaCtrl.js => grafana_ctrl.js} | 0 .../{inspectCtrl.js => inspect_ctrl.js} | 0 .../{invitedCtrl.js => invited_ctrl.js} | 0 ...{jsonEditorCtrl.js => json_editor_ctrl.js} | 0 .../{loginCtrl.js => login_ctrl.js} | 0 ...PasswordCtrl.js => reset_password_ctrl.js} | 0 .../controllers/{search.js => search_ctrl.js} | 0 .../{sidemenuCtrl.js => sidemenu_ctrl.js} | 0 .../{signupCtrl.ts => signup_ctrl.ts} | 0 .../services/{alertSrv.js => alert_srv.js} | 0 public/app/core/services/all.js | 16 +++++++-------- .../{backendSrv.js => backend_srv.js} | 0 .../{contextSrv.js => context_srv.js} | 0 .../{datasourceSrv.js => datasource_srv.js} | 0 ...keyboardManager.js => keyboard_manager.js} | 0 .../{popoverSrv.js => popover_srv.js} | 0 .../{uiSegmentSrv.js => segment_srv.js} | 0 .../core/services/{utilSrv.js => util_srv.js} | 0 .../elasticsearch/specs/query_ctrl_specs.ts | 2 +- .../graphite/specs/query_ctrl_specs.ts | 2 +- .../influxdb/specs/query_ctrl_specs.ts | 2 +- .../influxdb_08/specs/datasource-specs.ts | 4 ++-- 24 files changed, 23 insertions(+), 23 deletions(-) rename public/app/core/controllers/{errorCtrl.js => error_ctrl.js} (100%) rename public/app/core/controllers/{grafanaCtrl.js => grafana_ctrl.js} (100%) rename public/app/core/controllers/{inspectCtrl.js => inspect_ctrl.js} (100%) rename public/app/core/controllers/{invitedCtrl.js => invited_ctrl.js} (100%) rename public/app/core/controllers/{jsonEditorCtrl.js => json_editor_ctrl.js} (100%) rename public/app/core/controllers/{loginCtrl.js => login_ctrl.js} (100%) rename public/app/core/controllers/{resetPasswordCtrl.js => reset_password_ctrl.js} (100%) rename public/app/core/controllers/{search.js => search_ctrl.js} (100%) rename public/app/core/controllers/{sidemenuCtrl.js => sidemenu_ctrl.js} (100%) rename public/app/core/controllers/{signupCtrl.ts => signup_ctrl.ts} (100%) rename public/app/core/services/{alertSrv.js => alert_srv.js} (100%) rename public/app/core/services/{backendSrv.js => backend_srv.js} (100%) rename public/app/core/services/{contextSrv.js => context_srv.js} (100%) rename public/app/core/services/{datasourceSrv.js => datasource_srv.js} (100%) rename public/app/core/services/{keyboardManager.js => keyboard_manager.js} (100%) rename public/app/core/services/{popoverSrv.js => popover_srv.js} (100%) rename public/app/core/services/{uiSegmentSrv.js => segment_srv.js} (100%) rename public/app/core/services/{utilSrv.js => util_srv.js} (100%) diff --git a/public/app/core/controllers/all.js b/public/app/core/controllers/all.js index 1a313e6b019..d22010cffdc 100644 --- a/public/app/core/controllers/all.js +++ b/public/app/core/controllers/all.js @@ -1,12 +1,12 @@ define([ - './grafanaCtrl', - './search', - './inspectCtrl', - './jsonEditorCtrl', - './loginCtrl', - './invitedCtrl', - './signupCtrl', - './resetPasswordCtrl', - './sidemenuCtrl', - './errorCtrl', + './grafana_ctrl', + './search_ctrl', + './inspect_ctrl', + './json_editor_ctrl', + './login_ctrl', + './invited_ctrl', + './signup_ctrl', + './reset_password_ctrl', + './sidemenu_ctrl', + './error_ctrl', ], function () {}); diff --git a/public/app/core/controllers/errorCtrl.js b/public/app/core/controllers/error_ctrl.js similarity index 100% rename from public/app/core/controllers/errorCtrl.js rename to public/app/core/controllers/error_ctrl.js diff --git a/public/app/core/controllers/grafanaCtrl.js b/public/app/core/controllers/grafana_ctrl.js similarity index 100% rename from public/app/core/controllers/grafanaCtrl.js rename to public/app/core/controllers/grafana_ctrl.js diff --git a/public/app/core/controllers/inspectCtrl.js b/public/app/core/controllers/inspect_ctrl.js similarity index 100% rename from public/app/core/controllers/inspectCtrl.js rename to public/app/core/controllers/inspect_ctrl.js diff --git a/public/app/core/controllers/invitedCtrl.js b/public/app/core/controllers/invited_ctrl.js similarity index 100% rename from public/app/core/controllers/invitedCtrl.js rename to public/app/core/controllers/invited_ctrl.js diff --git a/public/app/core/controllers/jsonEditorCtrl.js b/public/app/core/controllers/json_editor_ctrl.js similarity index 100% rename from public/app/core/controllers/jsonEditorCtrl.js rename to public/app/core/controllers/json_editor_ctrl.js diff --git a/public/app/core/controllers/loginCtrl.js b/public/app/core/controllers/login_ctrl.js similarity index 100% rename from public/app/core/controllers/loginCtrl.js rename to public/app/core/controllers/login_ctrl.js diff --git a/public/app/core/controllers/resetPasswordCtrl.js b/public/app/core/controllers/reset_password_ctrl.js similarity index 100% rename from public/app/core/controllers/resetPasswordCtrl.js rename to public/app/core/controllers/reset_password_ctrl.js diff --git a/public/app/core/controllers/search.js b/public/app/core/controllers/search_ctrl.js similarity index 100% rename from public/app/core/controllers/search.js rename to public/app/core/controllers/search_ctrl.js diff --git a/public/app/core/controllers/sidemenuCtrl.js b/public/app/core/controllers/sidemenu_ctrl.js similarity index 100% rename from public/app/core/controllers/sidemenuCtrl.js rename to public/app/core/controllers/sidemenu_ctrl.js diff --git a/public/app/core/controllers/signupCtrl.ts b/public/app/core/controllers/signup_ctrl.ts similarity index 100% rename from public/app/core/controllers/signupCtrl.ts rename to public/app/core/controllers/signup_ctrl.ts diff --git a/public/app/core/services/alertSrv.js b/public/app/core/services/alert_srv.js similarity index 100% rename from public/app/core/services/alertSrv.js rename to public/app/core/services/alert_srv.js diff --git a/public/app/core/services/all.js b/public/app/core/services/all.js index bbe0e5235d3..2d4415a8fa2 100644 --- a/public/app/core/services/all.js +++ b/public/app/core/services/all.js @@ -1,13 +1,13 @@ define([ - './alertSrv', - './utilSrv', - './datasourceSrv', - './contextSrv', + './alert_srv', + './util_srv', + './datasource_srv', + './context_srv', './timer', - './keyboardManager', + './keyboard_manager', './analytics', - './popoverSrv', - './uiSegmentSrv', - './backendSrv', + './popover_srv', + './segment_srv', + './backend_srv', ], function () {}); diff --git a/public/app/core/services/backendSrv.js b/public/app/core/services/backend_srv.js similarity index 100% rename from public/app/core/services/backendSrv.js rename to public/app/core/services/backend_srv.js diff --git a/public/app/core/services/contextSrv.js b/public/app/core/services/context_srv.js similarity index 100% rename from public/app/core/services/contextSrv.js rename to public/app/core/services/context_srv.js diff --git a/public/app/core/services/datasourceSrv.js b/public/app/core/services/datasource_srv.js similarity index 100% rename from public/app/core/services/datasourceSrv.js rename to public/app/core/services/datasource_srv.js diff --git a/public/app/core/services/keyboardManager.js b/public/app/core/services/keyboard_manager.js similarity index 100% rename from public/app/core/services/keyboardManager.js rename to public/app/core/services/keyboard_manager.js diff --git a/public/app/core/services/popoverSrv.js b/public/app/core/services/popover_srv.js similarity index 100% rename from public/app/core/services/popoverSrv.js rename to public/app/core/services/popover_srv.js diff --git a/public/app/core/services/uiSegmentSrv.js b/public/app/core/services/segment_srv.js similarity index 100% rename from public/app/core/services/uiSegmentSrv.js rename to public/app/core/services/segment_srv.js diff --git a/public/app/core/services/utilSrv.js b/public/app/core/services/util_srv.js similarity index 100% rename from public/app/core/services/utilSrv.js rename to public/app/core/services/util_srv.js diff --git a/public/app/plugins/datasource/elasticsearch/specs/query_ctrl_specs.ts b/public/app/plugins/datasource/elasticsearch/specs/query_ctrl_specs.ts index bcf111827a6..776b9064a95 100644 --- a/public/app/plugins/datasource/elasticsearch/specs/query_ctrl_specs.ts +++ b/public/app/plugins/datasource/elasticsearch/specs/query_ctrl_specs.ts @@ -1,5 +1,5 @@ /// -/// +/// /// import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; diff --git a/public/app/plugins/datasource/graphite/specs/query_ctrl_specs.ts b/public/app/plugins/datasource/graphite/specs/query_ctrl_specs.ts index 03602973f25..364a64f82aa 100644 --- a/public/app/plugins/datasource/graphite/specs/query_ctrl_specs.ts +++ b/public/app/plugins/datasource/graphite/specs/query_ctrl_specs.ts @@ -1,6 +1,6 @@ /// /// -/// +/// /// import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; diff --git a/public/app/plugins/datasource/influxdb/specs/query_ctrl_specs.ts b/public/app/plugins/datasource/influxdb/specs/query_ctrl_specs.ts index 6da2c78535d..8fb5bea4925 100644 --- a/public/app/plugins/datasource/influxdb/specs/query_ctrl_specs.ts +++ b/public/app/plugins/datasource/influxdb/specs/query_ctrl_specs.ts @@ -1,5 +1,5 @@ /// -/// +/// /// import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; diff --git a/public/app/plugins/datasource/influxdb_08/specs/datasource-specs.ts b/public/app/plugins/datasource/influxdb_08/specs/datasource-specs.ts index 4d3d2b3817f..a8111776465 100644 --- a/public/app/plugins/datasource/influxdb_08/specs/datasource-specs.ts +++ b/public/app/plugins/datasource/influxdb_08/specs/datasource-specs.ts @@ -1,6 +1,6 @@ /// -/// -/// +/// +/// /// import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; From 2ca6acc1e975ca82b93071f550062d682d8ec2b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Bu=CC=88nemann?= Date: Sat, 31 Oct 2015 05:36:35 +0100 Subject: [PATCH 062/394] Fix npm 3 build failure in phantomjs task npm v3.0+ by default dedupes node modules and stores them in a flat tree, which means the hardcoded path to the location.js will no longer be nested under the karma-phantomjs-launcher module. This fixes issue #2999. --- tasks/options/phantomjs.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tasks/options/phantomjs.js b/tasks/options/phantomjs.js index 7eac7cb36ac..300688f5ba2 100644 --- a/tasks/options/phantomjs.js +++ b/tasks/options/phantomjs.js @@ -6,6 +6,11 @@ module.exports = function(config,grunt) { var dest = './vendor/phantomjs/phantomjs'; var confDir = './node_modules/karma-phantomjs-launcher/node_modules/phantomjs/lib/' + if (!grunt.file.exists(confDir)) { + // npm 3 or npm 2 with dedupe + confDir = './node_modules/phantomjs/lib/'; + } + if (!grunt.file.exists(dest)){ var m=grunt.file.read(confDir+"location.js") From 03130e1217f09ea9bbff9caac52ebe20d853f9a8 Mon Sep 17 00:00:00 2001 From: Utkarsh Bhatnagar Date: Fri, 30 Oct 2015 22:11:17 -0700 Subject: [PATCH 063/394] Update opentsdb.md As we merged, changes with auto suggestions in the master branch. Update docs respectively. --- docs/sources/datasources/opentsdb.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/sources/datasources/opentsdb.md b/docs/sources/datasources/opentsdb.md index bcaeed9056c..14aa67ef31c 100644 --- a/docs/sources/datasources/opentsdb.md +++ b/docs/sources/datasources/opentsdb.md @@ -30,10 +30,9 @@ Open a graph in edit mode by click the title. ![](/img/v2/opentsdb_query_editor.png) ### Auto complete suggestions -You should get auto complete suggestions for tags and tag values. If you do not you need to enable `tsd.core.meta.enable_realtime_ts` in -the OpenTSDB server settings. +As soon as you start typing metric names, tag names and tag values , you should see highlighted auto complete suggestions for them. - > Note: This is required for the OpenTSDB `lookup` api to work. + > Note: This is required for the OpenTSDB `suggest` api to work. ## Templating queries Grafana's OpenTSDB data source now supports template variable values queries. This means you can create template variables that fetch the values from OpenTSDB (for example metric names, tag names, or tag values). The query editor is also enhanced to limiting tags by metric. @@ -46,4 +45,8 @@ When using OpenTSDB with a template variable of `query` type you can use followi suggest_tagk(prefix) // return tag names (i.e. keys) for all metrics with specific prefix (can be empty) suggest_tagv(prefix) // return tag values for all metrics with specific prefix (can be empty) +If you do not see template variables being populated in `Preview of values` section, you need to enable `tsd.core.meta.enable_realtime_ts` in the OpenTSDB server settings. Also, to populate metadata of the existing time series data in OpenTSDB, you need to run `tsd uid metasync` on the OpenTSDB server. + +> Note: This is required for the OpenTSDB `lookup` api to work. + For details on opentsdb metric queries checkout the official [OpenTSDB documentation](http://opentsdb.net/docs/build/html/index.html) From c5435596ad089df6950fb020852502c42508a5f5 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Fri, 30 Oct 2015 22:34:40 -0700 Subject: [PATCH 064/394] Added All Value support for custom type templating --- .../features/templating/partials/editor.html | 17 +++++++++++++++++ .../features/templating/templateValuesSrv.js | 5 +++++ 2 files changed, 22 insertions(+) diff --git a/public/app/features/templating/partials/editor.html b/public/app/features/templating/partials/editor.html index 63ecd00adcf..133886971bd 100644 --- a/public/app/features/templating/partials/editor.html +++ b/public/app/features/templating/partials/editor.html @@ -146,6 +146,23 @@
      +
      +
        +
      • + +
      • +
      • + +
      • +
      • + All format +
      • +
      • + +
      • +
      +
      +
      diff --git a/public/app/features/templating/templateValuesSrv.js b/public/app/features/templating/templateValuesSrv.js index 18216fabd0f..a82eca012e6 100644 --- a/public/app/features/templating/templateValuesSrv.js +++ b/public/app/features/templating/templateValuesSrv.js @@ -115,6 +115,11 @@ function (angular, _, kbn) { if (variable.type === 'interval') { self.updateAutoInterval(variable); } + + if (variable.type === 'custom' && variable.includeAll) { + self.addAllOption(variable); + } + }; this.updateOptions = function(variable) { From acb5340ffb24f75f35365ca04a8a8f77877774a2 Mon Sep 17 00:00:00 2001 From: Vitaliy Fuks Date: Sat, 31 Oct 2015 18:07:24 -0400 Subject: [PATCH 065/394] Fixed typo in OpenTSDB's "metasync" documentation --- docs/sources/datasources/opentsdb.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/datasources/opentsdb.md b/docs/sources/datasources/opentsdb.md index 14aa67ef31c..43fcda643ee 100644 --- a/docs/sources/datasources/opentsdb.md +++ b/docs/sources/datasources/opentsdb.md @@ -45,7 +45,7 @@ When using OpenTSDB with a template variable of `query` type you can use followi suggest_tagk(prefix) // return tag names (i.e. keys) for all metrics with specific prefix (can be empty) suggest_tagv(prefix) // return tag values for all metrics with specific prefix (can be empty) -If you do not see template variables being populated in `Preview of values` section, you need to enable `tsd.core.meta.enable_realtime_ts` in the OpenTSDB server settings. Also, to populate metadata of the existing time series data in OpenTSDB, you need to run `tsd uid metasync` on the OpenTSDB server. +If you do not see template variables being populated in `Preview of values` section, you need to enable `tsd.core.meta.enable_realtime_ts` in the OpenTSDB server settings. Also, to populate metadata of the existing time series data in OpenTSDB, you need to run `tsdb uid metasync` on the OpenTSDB server. > Note: This is required for the OpenTSDB `lookup` api to work. From 8448e3970b80a8dc03c3587c6b7f64a50f418c35 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Sun, 1 Nov 2015 09:48:27 -0800 Subject: [PATCH 066/394] Removed unnecessary components. --- .../angular-native-dragdrop/.bower.json | 36 -- .../angular-native-dragdrop/Gulpfile.js | 15 - public/vendor/angular-native-dragdrop/LICENSE | 10 - .../vendor/angular-native-dragdrop/bower.json | 27 -- .../demo/css/styles.css | 29 -- .../angular-native-dragdrop/demo/index.html | 129 ------- .../angular-native-dragdrop/demo/js/app.js | 46 --- .../docs/css/styles.css | 32 -- .../angular-native-dragdrop/docs/index.html | 323 ------------------ .../angular-native-dragdrop/package.json | 24 -- 10 files changed, 671 deletions(-) delete mode 100644 public/vendor/angular-native-dragdrop/.bower.json delete mode 100644 public/vendor/angular-native-dragdrop/Gulpfile.js delete mode 100644 public/vendor/angular-native-dragdrop/LICENSE delete mode 100644 public/vendor/angular-native-dragdrop/bower.json delete mode 100644 public/vendor/angular-native-dragdrop/demo/css/styles.css delete mode 100644 public/vendor/angular-native-dragdrop/demo/index.html delete mode 100644 public/vendor/angular-native-dragdrop/demo/js/app.js delete mode 100644 public/vendor/angular-native-dragdrop/docs/css/styles.css delete mode 100644 public/vendor/angular-native-dragdrop/docs/index.html delete mode 100644 public/vendor/angular-native-dragdrop/package.json diff --git a/public/vendor/angular-native-dragdrop/.bower.json b/public/vendor/angular-native-dragdrop/.bower.json deleted file mode 100644 index 6cef5b66f76..00000000000 --- a/public/vendor/angular-native-dragdrop/.bower.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "angular-native-dragdrop", - "version": "1.1.1", - "homepage": "http://angular-dragdrop.github.io/angular-dragdrop", - "authors": [ - "ganarajpr" - ], - "description": "Angular HTML5 Drag and Drop directive written in pure with no dependency on JQuery.", - "main": "draganddrop.js", - "keywords": [ - "angular", - "drag", - "drop", - "html5" - ], - "dependencies": { - "angular": "^1.3" - }, - "license": "MIT", - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "test", - "tests" - ], - "_release": "1.1.1", - "_resolution": { - "type": "version", - "tag": "v1.1.1", - "commit": "4ff89cb0aa61070508e935729fb816fd46a58f60" - }, - "_source": "git://github.com/angular-dragdrop/angular-dragdrop.git", - "_target": "~1.1.1", - "_originalSource": "angular-native-dragdrop" -} \ No newline at end of file diff --git a/public/vendor/angular-native-dragdrop/Gulpfile.js b/public/vendor/angular-native-dragdrop/Gulpfile.js deleted file mode 100644 index 0aa3bdfff72..00000000000 --- a/public/vendor/angular-native-dragdrop/Gulpfile.js +++ /dev/null @@ -1,15 +0,0 @@ -/* jshint -W097 */ -'use strict'; - -/* global require */ -var jshint = require('gulp-jshint'); -var stylish = require('jshint-stylish'); -var gulp = require('gulp'); - -gulp.task('lint', function() { - return gulp.src('./draganddrop.js') - .pipe(jshint()) - .pipe(jshint.reporter(stylish)); -}); - -gulp.task('default', ['lint']); diff --git a/public/vendor/angular-native-dragdrop/LICENSE b/public/vendor/angular-native-dragdrop/LICENSE deleted file mode 100644 index d239cb9e0d3..00000000000 --- a/public/vendor/angular-native-dragdrop/LICENSE +++ /dev/null @@ -1,10 +0,0 @@ - -The MIT License - -Copyright (c) 2015 Ganaraj P R, [Nebithi](http://www.nebithi.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/public/vendor/angular-native-dragdrop/bower.json b/public/vendor/angular-native-dragdrop/bower.json deleted file mode 100644 index 8ffcb8869a7..00000000000 --- a/public/vendor/angular-native-dragdrop/bower.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "angular-native-dragdrop", - "version": "1.1.1", - "homepage": "http://angular-dragdrop.github.io/angular-dragdrop", - "authors": [ - "ganarajpr" - ], - "description": "Angular HTML5 Drag and Drop directive written in pure with no dependency on JQuery.", - "main": "draganddrop.js", - "keywords": [ - "angular", - "drag", - "drop", - "html5" - ], - "dependencies": { - "angular": "^1.3" - }, - "license": "MIT", - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "test", - "tests" - ] -} diff --git a/public/vendor/angular-native-dragdrop/demo/css/styles.css b/public/vendor/angular-native-dragdrop/demo/css/styles.css deleted file mode 100644 index 4dd18383697..00000000000 --- a/public/vendor/angular-native-dragdrop/demo/css/styles.css +++ /dev/null @@ -1,29 +0,0 @@ -body { - background-color: #f8f7f8; -} - -.heading { - border-bottom: 1px solid #b7b7b7; - padding-bottom: 10px; - margin-bottom: 10px; -} - -.topRow { - margin-bottom: 30px; -} - -.on-drag-enter { - background-color : #677ba6; -} - -.on-drag-enter-custom { - background-color : #d78cc7; -} - -.on-drag-hover { - background-color : #3eb352; -} - -.on-drag-hover-custom { - background-color : #d7a931; -} \ No newline at end of file diff --git a/public/vendor/angular-native-dragdrop/demo/index.html b/public/vendor/angular-native-dragdrop/demo/index.html deleted file mode 100644 index 9693e8f8c95..00000000000 --- a/public/vendor/angular-native-dragdrop/demo/index.html +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - Angular DragDrop (Demo) - - - - - - - - - - -
      -
      -

      - Drag and drop between the two lists. -

      -
      - -

      Beasts

      - -

      Left column of beasts is not draggable and accepts both beasts and priests

      - -
      - -
      -
      -
        -
      • - {{man}} -
      • -
      -
      -
      -
        -
      • - {{woman}} -
      • -
      -
      -
      - -
      - -

      Priests

      - -
      - -
      -
      -
        -
      • - {{man}} -
      • -
      -
      -
      -
        -
      • - {{woman}} -
      • -
      -
      -
      - -
      - -

      Terrorists

      - -

      Each terrorist list item accepts a new terrorist. Shows inserting into a particular - position in an array.

      - -
      - - -
      -
      -
        -
      • - {{man}} -
      • -
      -
      -
      -
        -
      • - {{woman}} -
      • -
      -
      -
      -
      - - - diff --git a/public/vendor/angular-native-dragdrop/demo/js/app.js b/public/vendor/angular-native-dragdrop/demo/js/app.js deleted file mode 100644 index 7fca20ac0ce..00000000000 --- a/public/vendor/angular-native-dragdrop/demo/js/app.js +++ /dev/null @@ -1,46 +0,0 @@ -angular.module('app', [ - 'hljs', - 'ang-drag-drop' -]).controller('MainCtrl', function($scope) { - $scope.men = [ - 'John', - 'Jack', - 'Mark', - 'Ernie', - 'Mike (Locked)' - ]; - - - $scope.women = [ - 'Jane', - 'Jill', - 'Betty', - 'Mary' - ]; - - $scope.addText = ''; - - $scope.dropValidateHandler = function($drop, $event, $data) { - if ($data === 'Mike (Locked)') { - return false; - } - if ($drop.element[0] === $event.srcElement.parentNode) { - // Don't allow moving to same container - return false; - } - return true; - }; - - $scope.dropSuccessHandler = function($event, index, array) { - array.splice(index, 1); - }; - - $scope.onDrop = function($event, $data, array, index) { - if (index !== undefined) { - array.splice(index, 0, $data); - } else { - array.push($data); - } - }; - -}); diff --git a/public/vendor/angular-native-dragdrop/docs/css/styles.css b/public/vendor/angular-native-dragdrop/docs/css/styles.css deleted file mode 100644 index d4480e8b536..00000000000 --- a/public/vendor/angular-native-dragdrop/docs/css/styles.css +++ /dev/null @@ -1,32 +0,0 @@ -.content { - margin : 50px auto; - font-size: 1.0em; - line-height: 1.5em; -} - -.content a{ - color: #677BA6; - cursor: pointer; -} - -body { - background-color: #f8f7f8; -} - -.jumbotron h1, -.jumbotron p{ - font-family: 'Open Sans'; -} - -.heading{ - border-bottom: 1px solid #b7b7b7; - padding-bottom: 10px; - margin-bottom: 10px; -} - -.ribbon{ - position: fixed; - top : 0; - right : 0; - z-index: 2000; -} \ No newline at end of file diff --git a/public/vendor/angular-native-dragdrop/docs/index.html b/public/vendor/angular-native-dragdrop/docs/index.html deleted file mode 100644 index 25603db4e34..00000000000 --- a/public/vendor/angular-native-dragdrop/docs/index.html +++ /dev/null @@ -1,323 +0,0 @@ - - - - Angular DragDrop - - - - - - - - - - -Fork me on GitHub -
      -
      -

      Angular Drag and Drop

      - -

      Angular-DragDrop is a AngularJS HTML5 Drag and Drop directive written in pure with no dependency on JQuery.

      - - -
      - - -
      -
      -

      Directives

      -
      - -

      ui-draggable

      - -

      - directive in module ngDragDrop -

      - -

      The ui-draggable attribute tells Angular that the element is draggable. ui-draggable - takes an expression as the attribute value. The expression should evaluate to either true or false. - You can toggle the draggability of an element using this expression. -

      - - -

      Additional Attributes

      - -

      drag

      - -

      The drag property is used to assign the data that needs to be passed along with the dragging - element.

      -
      -

      drag-handle-class

      - -

      The class used to mark child elements of draggable object to be used as drag handle. Default class name is - drag-handle.

      -
      - NOTE: If attribute is not present drag handle feature is not active. -
      -
      -

      on-drop-success

      - -

      The on-drop-success attribute takes a function. We can consider this to be an on-drop-success - handler function. - This can be useful if you need to do some post processing after the dragged element is dropped successfully on - the drop site. - -

      - NOTE: This callback function is only called when the drop succeeds. -
      - You can request the drag-end event ( very similiar to requesting the click event in - ng-click ) - by passing $event in the event handler. -

      - -
      -

      on-drop-failure

      - -

      The on-drop-failure attribute takes a function. We can consider this to be an on-drop-failure - handler function. - This can be useful if you need to do some post processing after the dragged element is dropped unsuccessfully on - any drop site. - -

      - NOTE: This callback function is only called when the drop fails. -
      - You can request the drag-end event ( very similiar to requesting the click event in - ng-click ) - by passing $event in the event handler. -

      - - -
      -

      Usage

      - -

      - -

      - ... - -
      -

      - -

      Details

      - -

      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      ParamTypeDetails
      ui-draggableExpression that should be - evaluated. The given expression should resolve to true or false. -
      dragTakes any JSON convertable $scope variable.
      drag-handle-classClass name used to mark child elements of draggable object to be used as drag handle.
      If attribute - is not present drag handle feature is not used.
      If attribute is present but have no value - drag-handle used as default.
      on-drop-successTakes any $scope function. Can also pass $event.
      on-drop-failureTakes any $scope function. Can also pass $event.
      drag-channelTakes a string that can be used as the channel name for the dragging operation. - Default channel is "defaultchannel" -
      -

      -
      - -

      Events

      - -

      On start of dragging an Angular Event ANGULAR_DRAG_START is dispatched from the - $rootScope. The event also carries - carries the information about the channel in which the dragging has started. -

      - -

      On end of dragging an Angular Event ANGULAR_DRAG_END is dispatched from the $rootScope. - The event also carries - carries the information about the channel in which the dragging has started. -

      - -

      When hovering a draggable element on top of a drop area an Angular Event ANGULAR_HOVER - is dispatched from the $rootScope. - The event also carries the information about the channel in which the dragging has started. -

      - -
      - -

      ui-on-drop

      - -

      - directive in module ngDragDrop -

      - -

      The ui-on-drop attribute tells Angular that the element is a drop site. ui-on-drop - takes a function as the attribute value. The function will be called when a valid dragged element is dropped in - that location. - A valid dragged element is one which has the same channel as the drop location. - -

      - NOTE : This callback function is only called when the drop succeeds. -
      - The ui-on-drop callback can request additional parameters. The data that is dragged is available to the - callback as $data and its channel as $channel. Apart from this the drop event is exposed as $event. -

      -

      Additional Attributes

      - -

      drop-channel

      - -

      The channel that the drop site accepts. The dragged element should have the same channel as this drop site for it - to be droppable at this location. It is possible to provide comma separated list of channels. - -

      - NOTE: Also special value of drag-channel attribute is available to accept - dragged element with any channel value — * -
      -

      - -
      - -

      drop-validate

      - -

      Extra validation that makes sure that the drop site accepts the dragged element beyond having the same channel. If - not defined, no extra validation is made. - -

      - NOTE: This callback function is called only if the channel condition is met, when the element - starts being dragged -
      -

      - -
      - -

      drag-enter-class

      - -

      The class that will be added to the the droppable element when a dragged element ( which is droppable ) - enters the drop location. The default value for this is on-drag-enter

      - -

      drag-hover-class

      - -

      The class that will be added to the drop area element when hovering with an element. - The default value for this is on-drag-hover

      - -
      -

      Usage

      - -

      - -

      - ... -
      -

      - -

      Details

      - -

      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      ParamTypeDetails
      ui-on-dropTakes any $scope function. Can also pass $event, $data and $channel. -
      drop-channelThe channel on which the drop has to listen for drag events.
      - Single value, comma separated list or special value * are possible
      drop-validateTakes any $scope function. Can also pass $data and $channel -
      drag-enter-classA class name that will be put on the droppable element when the dragged objects enters its boundaries. -
      Default class name is on-drag-enter.
      drag-hover-classA class name that will be put on the drop area when an element is dragged onto it.
      Default class - name is on-drag-hover.
      -

      -
      -

      Demo

      - - - -
      -
      - - - - - diff --git a/public/vendor/angular-native-dragdrop/package.json b/public/vendor/angular-native-dragdrop/package.json deleted file mode 100644 index 30aa4f51205..00000000000 --- a/public/vendor/angular-native-dragdrop/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "angular-native-dragdrop", - "version": "1.1.1", - "description": "Angular HTML5 Drag and Drop directive written in pure with no dependency on JQuery.", - "main": "draganddrop.js", - "scripts": { - "test": "gulp" - }, - "repository": { - "type": "git", - "url": "https://github.com/angular-dragdrop/angular-dragdrop.git" - }, - "author": "ganarajpr", - "license": "MIT", - "bugs": { - "url": "https://github.com/angular-dragdrop/angular-dragdrop/issues" - }, - "homepage": "http://angular-dragdrop.github.io/angular-dragdrop", - "devDependencies": { - "gulp": "^3.8.11", - "gulp-jshint": "^1.9.2", - "jshint-stylish": "^1.0.1" - } -} From 51003396040f994d0a0f3375ef0079ac630d154b Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Sun, 1 Nov 2015 22:58:14 -0800 Subject: [PATCH 067/394] Initialized dashboard JSON doc. --- docs/sources/reference/dashboard.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/sources/reference/dashboard.md diff --git a/docs/sources/reference/dashboard.md b/docs/sources/reference/dashboard.md new file mode 100644 index 00000000000..e69de29bb2d From 3a021a87a1df417b1e1478f39e4d209e1b22ae87 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Sun, 1 Nov 2015 23:17:23 -0800 Subject: [PATCH 068/394] Added JSON of new dashboard --- docs/sources/reference/dashboard.md | 80 +++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/docs/sources/reference/dashboard.md b/docs/sources/reference/dashboard.md index e69de29bb2d..1a05ff596c8 100644 --- a/docs/sources/reference/dashboard.md +++ b/docs/sources/reference/dashboard.md @@ -0,0 +1,80 @@ +---- +page_title: Dashboard JSON +page_description: Dashboard JSON Reference +page_keywords: grafana, dashboard, json, documentation +--- + +# Dashboard JSON + +## Overview + +A dashboard in Grafana is represented by a JSON object, which stores dashboard metadata, rows of panels, panel queries, etc. + + + +> To view the JSON of a dashboard, you can click on "Manage dashboard" cog menu and select "View JSON" from it. + +## Basic fields + +A dashboard JSON object is initialized with the following fields when it is created. + +``` +{ + "id": null, + "title": "New dashboard", + "originalTitle": "New dashboard", + "tags": [], + "style": "dark", + "timezone": "browser", + "editable": true, + "hideControls": false, + "sharedCrosshair": false, + "rows": [ + { + "height": "250px", + "panels": [], + "title": "Row", + "collapse": false, + "editable": true + } + ], + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ], + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "templating": { + "list": [] + }, + "annotations": { + "list": [] + }, + "schemaVersion": 7, + "version": 0, + "links": [] +} +``` From ada641090f04284dc9ff2f6b05c37ec9d5dddf02 Mon Sep 17 00:00:00 2001 From: Utkarsh Bhatnagar Date: Sun, 1 Nov 2015 23:53:24 -0800 Subject: [PATCH 069/394] Explained basic JSON fields --- docs/sources/reference/dashboard.md | 35 ++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/docs/sources/reference/dashboard.md b/docs/sources/reference/dashboard.md index 1a05ff596c8..c5bea813802 100644 --- a/docs/sources/reference/dashboard.md +++ b/docs/sources/reference/dashboard.md @@ -8,15 +8,19 @@ page_keywords: grafana, dashboard, json, documentation ## Overview -A dashboard in Grafana is represented by a JSON object, which stores dashboard metadata, rows of panels, panel queries, etc. +A dashboard in Grafana is represented by a JSON object, which stores metadata of its dashboard. Dashboard metadata includes dashboard properties, metadata from rows, panels, template variables, panel queries, etc. - +To view the JSON of a dashboard, follow the steps mentioned below: -> To view the JSON of a dashboard, you can click on "Manage dashboard" cog menu and select "View JSON" from it. + 1. Go to a dashboard + 2. Click on `Manage dashboard` menu on the top navigation bar + 3. Select `View JSON` from the dropdown menu ## Basic fields -A dashboard JSON object is initialized with the following fields when it is created. +When a user creates a new dashboard, a new dashboard JSON object is initialized with the following fields: + +> Note: In the following JSON, id is shown as null which is the default value assigned to it until the dashboard is not saved. Once saved, an integer value is assigned to the `id` field. ``` { @@ -26,7 +30,7 @@ A dashboard JSON object is initialized with the following fields when it is crea "tags": [], "style": "dark", "timezone": "browser", - "editable": true, + "editable": true, "hideControls": false, "sharedCrosshair": false, "rows": [ @@ -78,3 +82,24 @@ A dashboard JSON object is initialized with the following fields when it is crea "links": [] } ``` +Each field in the dashboard JSON is explained below with its usage: + +| Name | Usage | +| ---- | ----- | +| **id** | unique dashboard id, an integer | +| **title** | current title of dashboard | +| **originalTitle** | title of dashboard when saved for the first time | +| **tags** | an array of strings storing tags associated with dashboard | +| **style** | theme of dashboard, i.e. dark or light | +| **timezone** | timezone of dashboard, i.e. utc or browser | +| **editable** | whether a dashboard is editable or not | +| **hideControls** | whether row controls on the left in green are hidden or not | +| **sharedCrosshair** | TODO | +| **rows** | row metadata, see rows section for details | +| **time** | time range of dashboard, i.e. last 6 hours, last 7 days, etc | +| **timepicker** | timepicker metadata, see timepicker section for details | +| **templating** | timeplating metadata, see templating section for details | +| **annotations** | annotations metadata, see annotations section for details | +| **schemaVersion** | TODO | +| **version** | TODO | +| **links** | TODO | From 4c1b6f3059111e74464f7410a20fdc4acc3480ab Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Sun, 1 Nov 2015 23:55:03 -0800 Subject: [PATCH 070/394] Fixed a typo --- docs/sources/reference/dashboard.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/reference/dashboard.md b/docs/sources/reference/dashboard.md index c5bea813802..16481829f82 100644 --- a/docs/sources/reference/dashboard.md +++ b/docs/sources/reference/dashboard.md @@ -98,7 +98,7 @@ Each field in the dashboard JSON is explained below with its usage: | **rows** | row metadata, see rows section for details | | **time** | time range of dashboard, i.e. last 6 hours, last 7 days, etc | | **timepicker** | timepicker metadata, see timepicker section for details | -| **templating** | timeplating metadata, see templating section for details | +| **templating** | templating metadata, see templating section for details | | **annotations** | annotations metadata, see annotations section for details | | **schemaVersion** | TODO | | **version** | TODO | From f54615ed46a1741a5fd23928ff8874ce9f7819cd Mon Sep 17 00:00:00 2001 From: Utkarsh Bhatnagar Date: Mon, 2 Nov 2015 00:21:59 -0800 Subject: [PATCH 071/394] Included rows JSON and TODO headers --- docs/sources/reference/dashboard.md | 104 +++++++++++++++++----------- 1 file changed, 62 insertions(+), 42 deletions(-) diff --git a/docs/sources/reference/dashboard.md b/docs/sources/reference/dashboard.md index 16481829f82..cdef0c75119 100644 --- a/docs/sources/reference/dashboard.md +++ b/docs/sources/reference/dashboard.md @@ -16,11 +16,11 @@ To view the JSON of a dashboard, follow the steps mentioned below: 2. Click on `Manage dashboard` menu on the top navigation bar 3. Select `View JSON` from the dropdown menu -## Basic fields +## JSON fields When a user creates a new dashboard, a new dashboard JSON object is initialized with the following fields: -> Note: In the following JSON, id is shown as null which is the default value assigned to it until the dashboard is not saved. Once saved, an integer value is assigned to the `id` field. +> Note: In the following JSON, id is shown as null which is the default value assigned to it until a dashboard is saved. Once a dashboard is saved, an integer value is assigned to the `id` field. ``` { @@ -33,43 +33,14 @@ When a user creates a new dashboard, a new dashboard JSON object is initialized "editable": true, "hideControls": false, "sharedCrosshair": false, - "rows": [ - { - "height": "250px", - "panels": [], - "title": "Row", - "collapse": false, - "editable": true - } - ], + "rows": [], "time": { "from": "now-6h", "to": "now" }, "timepicker": { - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ], - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ] + "time_options": [], + "refresh_intervals": [] }, "templating": { "list": [] @@ -89,17 +60,66 @@ Each field in the dashboard JSON is explained below with its usage: | **id** | unique dashboard id, an integer | | **title** | current title of dashboard | | **originalTitle** | title of dashboard when saved for the first time | -| **tags** | an array of strings storing tags associated with dashboard | -| **style** | theme of dashboard, i.e. dark or light | -| **timezone** | timezone of dashboard, i.e. utc or browser | +| **tags** | tags associated with dashboard, an array of strings | +| **style** | theme of dashboard, i.e. `dark` or `light` | +| **timezone** | timezone of dashboard, i.e. `utc` or `browser` | | **editable** | whether a dashboard is editable or not | | **hideControls** | whether row controls on the left in green are hidden or not | | **sharedCrosshair** | TODO | -| **rows** | row metadata, see rows section for details | -| **time** | time range of dashboard, i.e. last 6 hours, last 7 days, etc | -| **timepicker** | timepicker metadata, see timepicker section for details | -| **templating** | templating metadata, see templating section for details | -| **annotations** | annotations metadata, see annotations section for details | +| **rows** | row metadata, see [rows section](/dashboard/#rows) for details | +| **time** | time range for dashboard, i.e. last 6 hours, last 7 days, etc | +| **timepicker** | timepicker metadata, see [timepicker section](/dashboard/#timepicker) for details | +| **templating** | templating metadata, see [templating section](/dashboard/#templating) for details | +| **annotations** | annotations metadata, see [annotations section](/dashboard/#annotations) for details | | **schemaVersion** | TODO | | **version** | TODO | | **links** | TODO | + +### rows + +`rows` field represents an array of JSON object representing each row in a dashboard, such as shown below: + +``` + "rows": [ + { + "collapse": false, + "editable": true, + "height": "200px", + "panels": [], + "title": "New row" + }, + { + "collapse": true, + "editable": true, + "height": "300px", + "panels": [], + "title": "New row" + } + ] +``` + +Usage of the fields is explained below: + +| Name | Usage | +| ---- | ----- | +| **collapse** | whether row is collapsed or not | +| **editable** | whether a row is editable or not | +| **height** | height of the row in pixels | +| **panels** | panels metadata, see [panels section](/dashboard/#panels) for details | +| **title** | title of row | + +#### panels + +TODO + +### timepicker + +TODO + +### templating + +TODO + +### annotations + +TODO From e51d403420c75adc2f721724244b7d773d871968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 2 Nov 2015 15:14:35 +0100 Subject: [PATCH 072/394] rename: moved test file --- .../test/{specs/kbn-format-specs.js => core/utils/kbn_specs.js} | 0 tasks/options/requirejs.js | 1 + 2 files changed, 1 insertion(+) rename public/test/{specs/kbn-format-specs.js => core/utils/kbn_specs.js} (100%) diff --git a/public/test/specs/kbn-format-specs.js b/public/test/core/utils/kbn_specs.js similarity index 100% rename from public/test/specs/kbn-format-specs.js rename to public/test/core/utils/kbn_specs.js diff --git a/tasks/options/requirejs.js b/tasks/options/requirejs.js index 6780eb4bad4..9d0522bd349 100644 --- a/tasks/options/requirejs.js +++ b/tasks/options/requirejs.js @@ -55,6 +55,7 @@ module.exports = function(config,grunt) { // bundle the datasources 'app/plugins/datasource/grafana/datasource', 'app/plugins/datasource/graphite/datasource', + 'app/plugins/datasource/elasticsearch/datasource', 'app/plugins/datasource/influxdb/datasource', ] }, From 36c4d01ef8de654b2846803bc0a5dccebd38a2c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 2 Nov 2015 17:00:47 +0100 Subject: [PATCH 073/394] feat(tablepanel) began work on new table panel --- public/app/core/settings.js | 1 + .../core/{time_series.js => time_series.ts} | 83 ++++++----- public/app/panels/graph/module.html | 4 - public/app/panels/table/module.html | 4 + public/app/panels/table/module.ts | 136 ++++++++++++++++++ 5 files changed, 192 insertions(+), 36 deletions(-) rename public/app/core/{time_series.js => time_series.ts} (77%) create mode 100644 public/app/panels/table/module.html create mode 100644 public/app/panels/table/module.ts diff --git a/public/app/core/settings.js b/public/app/core/settings.js index b7fb6e53c14..e452b59b904 100644 --- a/public/app/core/settings.js +++ b/public/app/core/settings.js @@ -10,6 +10,7 @@ function (_) { window_title_prefix : 'Grafana - ', panels : { 'graph': { path: 'app/panels/graph', name: 'Graph' }, + 'table': { path: 'app/panels/table', name: 'Table' }, 'singlestat': { path: 'app/panels/singlestat', name: 'Single stat' }, 'text': { path: 'app/panels/text', name: 'Text' }, 'dashlist': { path: 'app/panels/dashlist', name: 'Dashboard list' }, diff --git a/public/app/core/time_series.js b/public/app/core/time_series.ts similarity index 77% rename from public/app/core/time_series.js rename to public/app/core/time_series.ts index 9465232c200..30086be622a 100644 --- a/public/app/core/time_series.js +++ b/public/app/core/time_series.ts @@ -1,11 +1,46 @@ -define([ - 'lodash', - 'app/core/utils/kbn' -], -function (_, kbn) { - 'use strict'; +/// - function TimeSeries(opts) { +import _ = require('lodash'); +import kbn = require('app/core/utils/kbn'); + +function matchSeriesOverride(aliasOrRegex, seriesAlias) { + if (!aliasOrRegex) { return false; } + + if (aliasOrRegex[0] === '/') { + var regex = kbn.stringToJsRegex(aliasOrRegex); + return seriesAlias.match(regex) != null; + } + + return aliasOrRegex === seriesAlias; +} + +function translateFillOption(fill) { + return fill === 0 ? 0.001 : fill/10; +} + +class TimeSeries { + datapoints: any; + id: string; + label: string; + alias: string; + color: string; + valueFormater: any; + stats: any; + legend: boolean; + allIsNull: boolean; + decimals: number; + scaledDecimals: number; + + lines: any; + bars: any; + points: any; + yaxis: any; + zindex: any; + stack: any; + fillBelowTo: any; + transform: any; + + constructor(opts) { this.datapoints = opts.datapoints; this.label = opts.alias; this.id = opts.alias; @@ -16,22 +51,7 @@ function (_, kbn) { this.legend = true; } - function matchSeriesOverride(aliasOrRegex, seriesAlias) { - if (!aliasOrRegex) { return false; } - - if (aliasOrRegex[0] === '/') { - var regex = kbn.stringToJsRegex(aliasOrRegex); - return seriesAlias.match(regex) != null; - } - - return aliasOrRegex === seriesAlias; - } - - function translateFillOption(fill) { - return fill === 0 ? 0.001 : fill/10; - } - - TimeSeries.prototype.applySeriesOverrides = function(overrides) { + applySeriesOverrides(overrides) { this.lines = {}; this.points = {}; this.bars = {}; @@ -64,7 +84,7 @@ function (_, kbn) { } }; - TimeSeries.prototype.getFlotPairs = function (fillStyle) { + getFlotPairs(fillStyle) { var result = []; this.stats.total = 0; @@ -124,18 +144,17 @@ function (_, kbn) { } return result; - }; + } - TimeSeries.prototype.updateLegendValues = function(formater, decimals, scaledDecimals) { + updateLegendValues(formater, decimals, scaledDecimals) { this.valueFormater = formater; this.decimals = decimals; this.scaledDecimals = scaledDecimals; - }; + } - TimeSeries.prototype.formatValue = function(value) { + formatValue(value) { return this.valueFormater(value, this.decimals, this.scaledDecimals); - }; + } +} - return TimeSeries; - -}); +export = TimeSeries; diff --git a/public/app/panels/graph/module.html b/public/app/panels/graph/module.html index b0793780fa8..9c49c42741d 100644 --- a/public/app/panels/graph/module.html +++ b/public/app/panels/graph/module.html @@ -3,10 +3,6 @@
      - - - -
      No datapoints No datapoints returned from metric query diff --git a/public/app/panels/table/module.html b/public/app/panels/table/module.html new file mode 100644 index 00000000000..0463d751817 --- /dev/null +++ b/public/app/panels/table/module.html @@ -0,0 +1,4 @@ + +
      +
      +
      diff --git a/public/app/panels/table/module.ts b/public/app/panels/table/module.ts new file mode 100644 index 00000000000..e65278d44a8 --- /dev/null +++ b/public/app/panels/table/module.ts @@ -0,0 +1,136 @@ +/// + +import angular = require('angular'); +import $ = require('jquery'); +import _ = require('lodash'); +import moment = require('moment'); +import PanelMeta = require('app/features/panel/panel_meta'); +import TimeSeries = require('app/core/time_series'); + +var panelDefaults = { + targets: [{}], +}; + +export class TablePanelCtrl { + + constructor($scope, $rootScope, $q, panelSrv, panelHelper) { + $scope.ctrl = this; + + $scope.panelMeta = new PanelMeta({ + panelName: 'Table', + editIcon: "fa fa-table", + fullscreen: true, + metricsEditor: true, + }); + + $scope.panelMeta.addEditorTab('Options', 'app/panels/table/options.html'); + $scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html'); + + _.defaults($scope.panel, panelDefaults); + + $scope.refreshData = function(datasource) { + var data = { + columns: [], + rows: [], + }; + + data.columns.push({text: 'Time'}); + data.columns.push({text: 'Value'}); + data.columns.push({text: 'Value2'}); + data.rows.push([ + moment().format('LLL'), 17.2, 15.12 + ]); + data.rows.push([ + moment().format('LLL'), 12.2, 122.3244 + ]); + data.rows.push([ + moment().format('LLL'), 111.2, 2312.22 + ]); + + panelHelper.broadcastRender($scope, data); + + // panelHelper.updateTimeRange($scope); + // + // return panelHelper.issueMetricQuery($scope, datasource) + // .then($scope.dataHandler, function(err) { + // $scope.seriesList = []; + // $scope.render([]); + // throw err; + // }); + }; + + $scope.dataHandler = function(results) { + $scope.seriesList = _.map(results.data, $scope.seriesHandler); + panelHelper.broadcastRender($scope, $scope.seriesList); + }; + + $scope.seriesHandler = function(seriesData, index) { + var datapoints = seriesData.datapoints; + var alias = seriesData.target; + var colorIndex = index % $rootScope.colors.length; + var color = $scope.panel.aliasColors[alias] || $rootScope.colors[colorIndex]; + + var series = new TimeSeries({ + datapoints: datapoints, + alias: alias, + color: color, + }); + + return series; + }; + + panelSrv.init($scope); + } +} + +export function tablePanelDirective() { + 'use strict'; + return { + restrict: 'E', + templateUrl: 'app/panels/table/module.html', + controller: TablePanelCtrl, + link: function(scope, elem) { + var data; + + function renderPanel() { + var rootDiv = elem.find('.table-panel-container'); + var tableDiv = $('
      '); + var i, y, rowElem, colElem, column, row; + + rowElem = $(''); + for (i = 0; i < data.columns.length; i++) { + column = data.columns[i]; + colElem = $('' + column.text + ''); + rowElem.append(colElem); + } + + tableDiv.append(rowElem); + + for (y = 0; y < data.rows.length; y++) { + row = data.rows[y]; + rowElem = $('') + for (i = 0; i < data.columns.length; i++) { + colElem = $('' + row[i] + ''); + rowElem.append(colElem); + } + tableDiv.append(rowElem); + } + + rootDiv.empty(); + rootDiv.append(tableDiv); + } + + scope.$on('render', function(event, renderData) { + data = renderData || data; + if (!data) { + scope.get_data(); + return; + } + renderPanel(); + }); + } + }; +} + +angular.module('grafana.directives').directive('grafanaPanelTable', tablePanelDirective); + From e8c9b0806abb0ea84e3223d6a87d53f1c9171352 Mon Sep 17 00:00:00 2001 From: Utkarsh Bhatnagar Date: Mon, 2 Nov 2015 10:15:40 -0800 Subject: [PATCH 074/394] Added templating, timepicker, panel docs --- docs/sources/reference/dashboard.md | 305 +++++++++++++++++++++++++++- 1 file changed, 301 insertions(+), 4 deletions(-) diff --git a/docs/sources/reference/dashboard.md b/docs/sources/reference/dashboard.md index cdef0c75119..1a289208e3a 100644 --- a/docs/sources/reference/dashboard.md +++ b/docs/sources/reference/dashboard.md @@ -77,7 +77,7 @@ Each field in the dashboard JSON is explained below with its usage: ### rows -`rows` field represents an array of JSON object representing each row in a dashboard, such as shown below: +`rows` field consists of an array of JSON object representing each row in a dashboard, such as shown below: ``` "rows": [ @@ -110,15 +110,312 @@ Usage of the fields is explained below: #### panels -TODO +Panels are the building blocks a dashboard. It consists of datasource queries, type of graphs, aliases, etc. Panel JSON consists of an array of JSON objects, each representing a different panel in a row. Most of the fields are common for all panels but some fields depends on the panel type. Following is an example of panel JSON representing a `graph` panel type: + +``` +"panels": [ + { + "aliasColors": {}, + "bars": false, + "datasource": null, + "editable": true, + "error": false, + "fill": 0, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null, + "threshold1": null, + "threshold1Color": "rgba(216, 200, 27, 0.27)", + "threshold2": null, + "threshold2Color": "rgba(234, 112, 112, 0.22)" + }, + "id": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "span": 4, + "stack": false, + "steppedLine": false, + "targets": [ + { + "aggregator": "max", + "alias": "$tag_instance_id", + "currentTagKey": "", + "currentTagValue": "", + "downsampleAggregator": "avg", + "downsampleInterval": "", + "errors": {}, + "metric": "memory.percent-used", + "refId": "A", + "shouldComputeRate": false, + "tags": { + "app": "$app", + "env": "stage", + "instance_id": "*" + } + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Memory Utilization", + "tooltip": { + "shared": true, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "y-axis": true, + "y_formats": [ + "percent", + "short" + ] + }, + { + "aliasColors": {}, + "bars": false, + "datasource": null, + "editable": true, + "error": false, + "fill": 0, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null, + "threshold1": null, + "threshold1Color": "rgba(216, 200, 27, 0.27)", + "threshold2": null, + "threshold2Color": "rgba(234, 112, 112, 0.22)" + }, + "id": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "connected", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "span": 4, + "stack": false, + "steppedLine": false, + "targets": [ + { + "aggregator": "avg", + "alias": "$tag_instance_id", + "currentTagKey": "", + "currentTagValue": "", + "downsampleAggregator": "avg", + "downsampleInterval": "", + "errors": {}, + "metric": "memory.percent-cached", + "refId": "A", + "shouldComputeRate": false, + "tags": { + "app": "$app", + "env": "prod", + "instance_id": "*" + } + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Memory Cached", + "tooltip": { + "shared": true, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "y-axis": true, + "y_formats": [ + "short", + "short" + ] + }, +``` + +Usage of each field is explained below: + +| Name | Usage | +| ---- | ----- | +| TODO | TODO | ### timepicker -TODO +Description: TODO + +``` +"timepicker": { + "collapse": false, + "enable": true, + "notice": false, + "now": true, + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "status": "Stable", + "time_options": [ + "5m", + "15m", + "1h", + "3h", + "6h", + "12h", + "24h", + "2d", + "3d", + "4d", + "7d", + "30d" + ], + "type": "timepicker" + } +``` + +Usage of the fields is explained below: + +| Name | Usage | +| ---- | ----- | +| **collapse** | whether timepicker is collapsed or not | +| **enable** | whether timepicker is enabled or not | +| **notice** | TODO | +| **now** | TODO | +| **refresh_intervals** | TODO | +| **status** | TODO | +| **time_options** | TODO | +| **type** | TODO | ### templating -TODO +`templating` fields contains array of template variables with their saved values along with some other metadata, for example: + +``` + "templating": { + "enable": true, + "list": [ + { + "allFormat": "wildcard", + "current": { + "tags": [], + "text": "prod", + "value": "prod" + }, + "datasource": null, + "includeAll": true, + "name": "env", + "options": [ + { + "selected": false, + "text": "All", + "value": "*" + }, + { + "selected": false, + "text": "stage", + "value": "stage" + }, + { + "selected": false, + "text": "test", + "value": "test" + } + ], + "query": "tag_values(cpu.utilization.average,env)", + "refresh": false, + "refresh_on_load": false, + "type": "query" + }, + { + "allFormat": "wildcard", + "current": { + "text": "apache", + "value": "apache" + }, + "datasource": null, + "includeAll": false, + "multi": false, + "multiFormat": "glob", + "name": "app", + "options": [ + { + "selected": true, + "text": "tomcat", + "value": "tomcat" + }, + { + "selected": false, + "text": "cassandra", + "value": "cassandra" + } + ], + "query": "tag_values(cpu.utilization.average,app)", + "refresh_on_load": false, + "regex": "", + "type": "query" + } + ] + } +``` + +Usage of the above mentioned fields in the templating section is explained below: + +| Name | Usage | +| ---- | ----- | +| **enable** | whether templating is enabled or not | +| **list** | an array of objects representing, each representing one template variable | +| **allFormat** | format to use while fetching all values from datasource, eg: `wildcard`, `glob`, `regex`, `pipe`, etc. | +| **current** | shows current selected variable text/value on the dashboard | +| **datasource** | shows datasource for the variables | +| **includeAll** | whether all value option is available or not | +| **multi** | whether multiple values can be selected or not from variable value list | +| **multiFormat** | format to use while fetching timeseries from datasource | +| **name** | name of variable | +| **options** | array of variable text/value pairs available for selection on dashboard | +| **query** | datasource query used to fetch values for a variable | +| **refresh_on_load** | TODO | +| **regex** | TODO | +| **type** | type of variable, i.e. `custom`, `query` or `interval` | ### annotations From f14ef22bb6be96ed2cb1ccc0ec52a903c1e44a82 Mon Sep 17 00:00:00 2001 From: Utkarsh Bhatnagar Date: Mon, 2 Nov 2015 10:25:33 -0800 Subject: [PATCH 075/394] Fixed doc links --- docs/sources/reference/dashboard.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/sources/reference/dashboard.md b/docs/sources/reference/dashboard.md index 1a289208e3a..60064690fb2 100644 --- a/docs/sources/reference/dashboard.md +++ b/docs/sources/reference/dashboard.md @@ -66,11 +66,11 @@ Each field in the dashboard JSON is explained below with its usage: | **editable** | whether a dashboard is editable or not | | **hideControls** | whether row controls on the left in green are hidden or not | | **sharedCrosshair** | TODO | -| **rows** | row metadata, see [rows section](/dashboard/#rows) for details | +| **rows** | row metadata, see [rows section](/dashboard.md/#rows) for details | | **time** | time range for dashboard, i.e. last 6 hours, last 7 days, etc | -| **timepicker** | timepicker metadata, see [timepicker section](/dashboard/#timepicker) for details | -| **templating** | templating metadata, see [templating section](/dashboard/#templating) for details | -| **annotations** | annotations metadata, see [annotations section](/dashboard/#annotations) for details | +| **timepicker** | timepicker metadata, see [timepicker section](/dashboard.md/#timepicker) for details | +| **templating** | templating metadata, see [templating section](/dashboard.md/#templating) for details | +| **annotations** | annotations metadata, see [annotations section](/dashboard.md/#annotations) for details | | **schemaVersion** | TODO | | **version** | TODO | | **links** | TODO | @@ -105,7 +105,7 @@ Usage of the fields is explained below: | **collapse** | whether row is collapsed or not | | **editable** | whether a row is editable or not | | **height** | height of the row in pixels | -| **panels** | panels metadata, see [panels section](/dashboard/#panels) for details | +| **panels** | panels metadata, see [panels section](/dashboard.md/#panels) for details | | **title** | title of row | #### panels From 74b10a42ee66f3fa682ea139be8f560725b2f871 Mon Sep 17 00:00:00 2001 From: Utkarsh Bhatnagar Date: Mon, 2 Nov 2015 10:29:42 -0800 Subject: [PATCH 076/394] Fixed broken links in the doc page --- docs/sources/reference/dashboard.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/sources/reference/dashboard.md b/docs/sources/reference/dashboard.md index 60064690fb2..5b18b9e26ad 100644 --- a/docs/sources/reference/dashboard.md +++ b/docs/sources/reference/dashboard.md @@ -66,11 +66,11 @@ Each field in the dashboard JSON is explained below with its usage: | **editable** | whether a dashboard is editable or not | | **hideControls** | whether row controls on the left in green are hidden or not | | **sharedCrosshair** | TODO | -| **rows** | row metadata, see [rows section](/dashboard.md/#rows) for details | +| **rows** | row metadata, see [rows section](/docs/sources/reference/dashboard.md/#rows) for details | | **time** | time range for dashboard, i.e. last 6 hours, last 7 days, etc | -| **timepicker** | timepicker metadata, see [timepicker section](/dashboard.md/#timepicker) for details | -| **templating** | templating metadata, see [templating section](/dashboard.md/#templating) for details | -| **annotations** | annotations metadata, see [annotations section](/dashboard.md/#annotations) for details | +| **timepicker** | timepicker metadata, see [timepicker section](/docs/sources/reference/dashboard.md/#timepicker) for details | +| **templating** | templating metadata, see [templating section](/docs/sources/reference/dashboard.md/#templating) for details | +| **annotations** | annotations metadata, see [annotations section](/docs/sources/reference/dashboard.md/#annotations) for details | | **schemaVersion** | TODO | | **version** | TODO | | **links** | TODO | @@ -105,7 +105,7 @@ Usage of the fields is explained below: | **collapse** | whether row is collapsed or not | | **editable** | whether a row is editable or not | | **height** | height of the row in pixels | -| **panels** | panels metadata, see [panels section](/dashboard.md/#panels) for details | +| **panels** | panels metadata, see [panels section](/docs/sources/reference/dashboard.md/#panels) for details | | **title** | title of row | #### panels From 8171cd51c472d4cb5b82d9b03530ed2f66fa7c97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 2 Nov 2015 20:51:49 +0100 Subject: [PATCH 077/394] feat(tablepanel): minor progress on table panel --- public/app/panels/table/module.html | 10 ++-- public/app/panels/table/module.ts | 60 ++++--------------- .../panels/table/specs/table_model_specs.ts | 28 +++++++++ public/app/panels/table/table_model.ts | 43 +++++++++++++ public/less/grafana.less | 7 ++- .../{dashlist.less => panel_dashlist.less} | 0 public/less/{graph.less => panel_graph.less} | 0 ...{singlestat.less => panel_singlestat.less} | 0 public/less/panel_table.less | 49 +++++++++++++++ 9 files changed, 143 insertions(+), 54 deletions(-) create mode 100644 public/app/panels/table/specs/table_model_specs.ts create mode 100644 public/app/panels/table/table_model.ts rename public/less/{dashlist.less => panel_dashlist.less} (100%) rename public/less/{graph.less => panel_graph.less} (100%) rename public/less/{singlestat.less => panel_singlestat.less} (100%) create mode 100644 public/less/panel_table.less diff --git a/public/app/panels/table/module.html b/public/app/panels/table/module.html index 0463d751817..c85252354dc 100644 --- a/public/app/panels/table/module.html +++ b/public/app/panels/table/module.html @@ -1,4 +1,6 @@ - -
      -
      -
      +
      + +
      +
      +
      +
      diff --git a/public/app/panels/table/module.ts b/public/app/panels/table/module.ts index e65278d44a8..36094dd90b2 100644 --- a/public/app/panels/table/module.ts +++ b/public/app/panels/table/module.ts @@ -6,6 +6,7 @@ import _ = require('lodash'); import moment = require('moment'); import PanelMeta = require('app/features/panel/panel_meta'); import TimeSeries = require('app/core/time_series'); +import {TableModel} from './table_model'; var panelDefaults = { targets: [{}], @@ -29,54 +30,19 @@ export class TablePanelCtrl { _.defaults($scope.panel, panelDefaults); $scope.refreshData = function(datasource) { - var data = { - columns: [], - rows: [], - }; + panelHelper.updateTimeRange($scope); - data.columns.push({text: 'Time'}); - data.columns.push({text: 'Value'}); - data.columns.push({text: 'Value2'}); - data.rows.push([ - moment().format('LLL'), 17.2, 15.12 - ]); - data.rows.push([ - moment().format('LLL'), 12.2, 122.3244 - ]); - data.rows.push([ - moment().format('LLL'), 111.2, 2312.22 - ]); - - panelHelper.broadcastRender($scope, data); - - // panelHelper.updateTimeRange($scope); - // - // return panelHelper.issueMetricQuery($scope, datasource) - // .then($scope.dataHandler, function(err) { - // $scope.seriesList = []; - // $scope.render([]); - // throw err; - // }); + return panelHelper.issueMetricQuery($scope, datasource) + .then($scope.dataHandler, function(err) { + $scope.seriesList = []; + $scope.render([]); + throw err; + }); }; $scope.dataHandler = function(results) { - $scope.seriesList = _.map(results.data, $scope.seriesHandler); - panelHelper.broadcastRender($scope, $scope.seriesList); - }; - - $scope.seriesHandler = function(seriesData, index) { - var datapoints = seriesData.datapoints; - var alias = seriesData.target; - var colorIndex = index % $rootScope.colors.length; - var color = $scope.panel.aliasColors[alias] || $rootScope.colors[colorIndex]; - - var series = new TimeSeries({ - datapoints: datapoints, - alias: alias, - color: color, - }); - - return series; + $scope.tableModel = TableModel.transform(results.data, $scope.panel); + panelHelper.broadcastRender($scope, $scope.tableModel); }; panelSrv.init($scope); @@ -94,13 +60,13 @@ export function tablePanelDirective() { function renderPanel() { var rootDiv = elem.find('.table-panel-container'); - var tableDiv = $('
      '); + var tableDiv = $('
      '); var i, y, rowElem, colElem, column, row; rowElem = $(''); for (i = 0; i < data.columns.length; i++) { column = data.columns[i]; - colElem = $('' + column.text + ''); + colElem = $('' + column.text + ''); rowElem.append(colElem); } @@ -108,7 +74,7 @@ export function tablePanelDirective() { for (y = 0; y < data.rows.length; y++) { row = data.rows[y]; - rowElem = $('') + rowElem = $(''); for (i = 0; i < data.columns.length; i++) { colElem = $('' + row[i] + ''); rowElem.append(colElem); diff --git a/public/app/panels/table/specs/table_model_specs.ts b/public/app/panels/table/specs/table_model_specs.ts new file mode 100644 index 00000000000..7cb9575eb86 --- /dev/null +++ b/public/app/panels/table/specs/table_model_specs.ts @@ -0,0 +1,28 @@ +import {describe, beforeEach, it, sinon, expect} from 'test/lib/common'; + +import {TableModel} from '../table_model'; + +describe('when getting tableData', () => { + + describe('simple time series', () => { + var panel = { + }; + + it ('should return 2 columns', () => { + var data = TableModel.transform([ + { + target: 'test', + datapoints: [[12.12, new Date().getTime()]], + } + ], panel); + + expect(data.columns.length).to.be(2); + expect(data.rows.length).to.be(1); + + expect(data.columns[0].text).to.be('Time'); + expect(data.columns[1].text).to.be('Value'); + }); + + }); + +}); diff --git a/public/app/panels/table/table_model.ts b/public/app/panels/table/table_model.ts new file mode 100644 index 00000000000..0351f30b33b --- /dev/null +++ b/public/app/panels/table/table_model.ts @@ -0,0 +1,43 @@ +/// + +import moment = require('moment'); +import _ = require('lodash'); + +export class TableModel { + columns: any[]; + rows: any[]; + + static transform(data, panel) { + var model = new TableModel(); + + if (!data || data.length === 0) { + return model; + } + + model.columns = [ + {text: 'Time'}, + {text: 'Value'}, + ]; + model.rows = []; + + for (var i = 0; i < data.length; i++) { + var series = data[i]; + for (var y = 0; y < series.datapoints.length; y++) { + var dp = series.datapoints[y]; + var time = moment(dp[1]).format('LLL'); + var value = dp[0]; + if (value === null) { + value = 'null'; + } else if (_.isNumber(value)) { + value = value.toFixed(2); + } + + model.rows.push([time, value]); + } + } + + return model; + } +} + + diff --git a/public/less/grafana.less b/public/less/grafana.less index 6e23faa214d..e82c3b93c01 100644 --- a/public/less/grafana.less +++ b/public/less/grafana.less @@ -1,18 +1,19 @@ @import "type.less"; @import "login.less"; @import "submenu.less"; -@import "graph.less"; +@import "panel_graph.less"; +@import "panel_dashlist.less"; +@import "panel_singlestat.less"; +@import "panel_table.less"; @import "bootstrap-tagsinput.less"; @import "tables_lists.less"; @import "search.less"; @import "panel.less"; @import "forms.less"; -@import "singlestat.less"; @import "tightform.less"; @import "sidemenu.less"; @import "navbar.less"; @import "gfbox.less"; -@import "dashlist.less"; @import "admin.less"; @import "validation.less"; @import "fonts.less"; diff --git a/public/less/dashlist.less b/public/less/panel_dashlist.less similarity index 100% rename from public/less/dashlist.less rename to public/less/panel_dashlist.less diff --git a/public/less/graph.less b/public/less/panel_graph.less similarity index 100% rename from public/less/graph.less rename to public/less/panel_graph.less diff --git a/public/less/singlestat.less b/public/less/panel_singlestat.less similarity index 100% rename from public/less/singlestat.less rename to public/less/panel_singlestat.less diff --git a/public/less/panel_table.less b/public/less/panel_table.less new file mode 100644 index 00000000000..a46fb7e65b9 --- /dev/null +++ b/public/less/panel_table.less @@ -0,0 +1,49 @@ +.table-panel-wrapper { + .panel-content { + padding: 0; + } + .panel-title-container { + padding-bottom: 4px; + } +} + +.gf-table-panel* { + box-sizing: border-box; +} + +.gf-table-panel { + width: 100%; + table-layout: fixed; + border-collapse: collapse; +} + +.gf-table-panel tr { + border-bottom: 2px solid @bodyBackground; +} + +.gf-table-panel th { + background: @grafanaTargetFuncBackground; + padding: 5px 0 5px 15px; + text-align: left; + border-top: 2px solid @bodyBackground; + + &:first-child { + padding-left: 15px; + } +} + +.gf-table-panel td { + padding: 15px 0 15px 15px; + + &:first-child { + padding-left: 15px; + } +} + +.gf-table-panel .ellipsis { + display: block; + width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} From da9c792ca2edcf79d30569e266c4ea636f86a837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 3 Nov 2015 08:18:35 +0100 Subject: [PATCH 078/394] feat(tablepanel): minor progress --- public/app/panels/table/module.ts | 20 ++++++++++++++++++-- public/less/panel_table.less | 4 ++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/public/app/panels/table/module.ts b/public/app/panels/table/module.ts index 36094dd90b2..22b087bdfa4 100644 --- a/public/app/panels/table/module.ts +++ b/public/app/panels/table/module.ts @@ -58,6 +58,15 @@ export function tablePanelDirective() { link: function(scope, elem) { var data; + function getTableHeight() { + var panelHeight = scope.height || scope.panel.height || scope.row.height; + if (_.isString(panelHeight)) { + panelHeight = parseInt(panelHeight.replace('px', ''), 10); + } + + return (panelHeight - 40) + 'px'; + } + function renderPanel() { var rootDiv = elem.find('.table-panel-container'); var tableDiv = $('
      '); @@ -70,8 +79,10 @@ export function tablePanelDirective() { rowElem.append(colElem); } - tableDiv.append(rowElem); + var headElem = $(''); + headElem.append(rowElem); + var tbodyElem = $(''); for (y = 0; y < data.rows.length; y++) { row = data.rows[y]; rowElem = $(''); @@ -79,9 +90,14 @@ export function tablePanelDirective() { colElem = $('' + row[i] + ''); rowElem.append(colElem); } - tableDiv.append(rowElem); + tbodyElem.append(rowElem); } + tableDiv.append(headElem); + tableDiv.append(tbodyElem); + + rootDiv.css({'max-height': getTableHeight()}); + rootDiv.empty(); rootDiv.append(tableDiv); } diff --git a/public/less/panel_table.less b/public/less/panel_table.less index a46fb7e65b9..8cd62cbb76b 100644 --- a/public/less/panel_table.less +++ b/public/less/panel_table.less @@ -7,6 +7,10 @@ } } +.table-panel-container { + overflow: auto; +} + .gf-table-panel* { box-sizing: border-box; } From 58dc282ca0fb4240ef67023bb948ccc7a3d8f8e5 Mon Sep 17 00:00:00 2001 From: Nick Christus Date: Tue, 3 Nov 2015 08:24:10 -0600 Subject: [PATCH 079/394] updated-list-views: updating table layout for org users --- .../features/org/partials/datasources.html | 9 +-- .../app/features/org/partials/orgUsers.html | 68 ++++++++++++++++--- public/less/filter-controls.less | 4 ++ public/less/filter-table.less | 3 +- 4 files changed, 67 insertions(+), 17 deletions(-) diff --git a/public/app/features/org/partials/datasources.html b/public/app/features/org/partials/datasources.html index 6cf40deccde..3897b3ecd3d 100644 --- a/public/app/features/org/partials/datasources.html +++ b/public/app/features/org/partials/datasources.html @@ -62,8 +62,8 @@ - Name - Url + Name + Url @@ -76,10 +76,7 @@ - -   - {{ds.name}} - +   {{ds.name}} {{ds.url}} diff --git a/public/app/features/org/partials/orgUsers.html b/public/app/features/org/partials/orgUsers.html index 355131c2dbb..3cb560bd560 100644 --- a/public/app/features/org/partials/orgUsers.html +++ b/public/app/features/org/partials/orgUsers.html @@ -4,7 +4,7 @@
    -
    +

    Organization users

    @@ -18,21 +18,69 @@ - - - - - - - + +
    +
    +
      +
    • Filters:
    • +
    • Login
    • +
    • +
    • Role
    • +
    • +
    • + + + +
    • +
    +
    +
    +
    + +
      +
    • + +
    • +
    • + +
    • +
    • + 0 selected, showing 1 of 1 total +
    • +
    + +
    LoginEmailRole
    + + + + + + + + - + - '); - for (i = 0; i < data.columns.length; i++) { - column = data.columns[i]; - colElem = $(''); + function appendTableHeader(tableElem) { + var rowElem = $(''); + for (var i = 0; i < data.columns.length; i++) { + var column = data.columns[i]; + var colElem = $(''); rowElem.append(colElem); } var headElem = $(''); headElem.append(rowElem); + headElem.appendTo(tableElem); + } + function appendTableRows(tableElem) { var tbodyElem = $(''); - for (y = 0; y < data.rows.length; y++) { - row = data.rows[y]; - rowElem = $(''); - for (i = 0; i < data.columns.length; i++) { - colElem = $(''); + var rowEnd = Math.min(panel.pageSize, data.rows.length); + var rowStart = 0; + + for (var y = rowStart; y < rowEnd; y++) { + var row = data.rows[y]; + var rowElem = $(''); + for (var i = 0; i < data.columns.length; i++) { + var colElem = $(''); rowElem.append(colElem); } tbodyElem.append(rowElem); } - tableDiv.append(headElem); - tableDiv.append(tbodyElem); + tableElem.append(tbodyElem); + } - rootDiv.css({'max-height': getTableHeight()}); + function appendPaginationControls(footerElem) { + var paginationElem = $('
    LoginEmailRole
    {{user.login}}{{user.email}}{{user.email}} + diff --git a/public/less/filter-controls.less b/public/less/filter-controls.less index f1489431a7d..287bebe3757 100644 --- a/public/less/filter-controls.less +++ b/public/less/filter-controls.less @@ -12,6 +12,10 @@ margin-bottom: 40px; } +.tab-pane > .filter-controls-filters { + margin-top: 20px; +} + // Actions diff --git a/public/less/filter-table.less b/public/less/filter-table.less index 7b9f7a47b10..765fab5662f 100644 --- a/public/less/filter-table.less +++ b/public/less/filter-table.less @@ -13,7 +13,7 @@ .filter-table { width: 100%; - table-layout: fixed; + // table-layout: fixed; border-collapse: collapse; } @@ -23,6 +23,7 @@ } .filter-table th { + width: auto; padding: 10px 15px 10px 0; text-align: left; From 867b838053d6a903fcad5d73201225cbb23217a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 3 Nov 2015 16:19:51 +0100 Subject: [PATCH 080/394] feat(tablepanel): work on table panel --- public/app/panels/graph/styleEditor.html | 2 +- public/app/panels/table/module.ts | 29 ++++--- public/app/panels/table/options.html | 58 +++++++++++++ .../panels/table/specs/table_model_specs.ts | 49 +++++++++-- public/app/panels/table/table_model.ts | 81 ++++++++++++++++--- public/less/panel_table.less | 1 - 6 files changed, 188 insertions(+), 32 deletions(-) create mode 100644 public/app/panels/table/options.html diff --git a/public/app/panels/graph/styleEditor.html b/public/app/panels/graph/styleEditor.html index 5d5f2fd7401..f692328a0fd 100644 --- a/public/app/panels/graph/styleEditor.html +++ b/public/app/panels/graph/styleEditor.html @@ -63,7 +63,7 @@
    Series specific overrides Regex match example: /server[0-3]/i
    -
    +
    • diff --git a/public/app/panels/table/module.ts b/public/app/panels/table/module.ts index 22b087bdfa4..8cb46d6cc46 100644 --- a/public/app/panels/table/module.ts +++ b/public/app/panels/table/module.ts @@ -6,16 +6,13 @@ import _ = require('lodash'); import moment = require('moment'); import PanelMeta = require('app/features/panel/panel_meta'); import TimeSeries = require('app/core/time_series'); -import {TableModel} from './table_model'; - -var panelDefaults = { - targets: [{}], -}; +import {TableModel, transformers} from './table_model'; export class TablePanelCtrl { constructor($scope, $rootScope, $q, panelSrv, panelHelper) { $scope.ctrl = this; + $scope.transformers = transformers; $scope.panelMeta = new PanelMeta({ panelName: 'Table', @@ -27,21 +24,31 @@ export class TablePanelCtrl { $scope.panelMeta.addEditorTab('Options', 'app/panels/table/options.html'); $scope.panelMeta.addEditorTab('Time range', 'app/features/panel/partials/panelTime.html'); + var panelDefaults = { + targets: [{}], + transform: 'timeseries_to_rows' + }; + _.defaults($scope.panel, panelDefaults); $scope.refreshData = function(datasource) { panelHelper.updateTimeRange($scope); return panelHelper.issueMetricQuery($scope, datasource) - .then($scope.dataHandler, function(err) { - $scope.seriesList = []; - $scope.render([]); - throw err; - }); + .then($scope.dataHandler, function(err) { + $scope.seriesList = []; + $scope.render([]); + throw err; + }); }; $scope.dataHandler = function(results) { - $scope.tableModel = TableModel.transform(results.data, $scope.panel); + $scope.dataRaw = results.data; + $scope.render(); + }; + + $scope.render = function() { + $scope.tableModel = TableModel.transform($scope.dataRaw, $scope.panel); panelHelper.broadcastRender($scope, $scope.tableModel); }; diff --git a/public/app/panels/table/options.html b/public/app/panels/table/options.html new file mode 100644 index 00000000000..7f1332b6e18 --- /dev/null +++ b/public/app/panels/table/options.html @@ -0,0 +1,58 @@ +
      +
      +
      Data Table
      +
      +
        +
      • + Data to Table Transform +
      • +
      • + +
      • +
      +
      +
      +
      +
      + +
      +
      +
      Table Display
      +
      +
      + +
      +
      +
      Column Styles
      + +
      +
      +
        +
      • + +
      • + +
      • + alias or regex +
      • + +
      • + +
      • + + +
      +
      +
      +
      + + +
      +
      + diff --git a/public/app/panels/table/specs/table_model_specs.ts b/public/app/panels/table/specs/table_model_specs.ts index 7cb9575eb86..04106100392 100644 --- a/public/app/panels/table/specs/table_model_specs.ts +++ b/public/app/panels/table/specs/table_model_specs.ts @@ -4,25 +4,62 @@ import {TableModel} from '../table_model'; describe('when getting tableData', () => { - describe('simple time series', () => { + describe('timeseries_to_rows', () => { var panel = { + transform: 'timeseries_to_rows' }; - it ('should return 2 columns', () => { + it ('should return 2 rows', () => { var data = TableModel.transform([ { - target: 'test', + target: 'series1', + datapoints: [[12.12, new Date().getTime()]], + }, + { + target: 'series2', datapoints: [[12.12, new Date().getTime()]], } ], panel); - expect(data.columns.length).to.be(2); + expect(data.columns.length).to.be(3); + expect(data.rows.length).to.be(2); + + expect(data.columns[0].text).to.be('Time'); + expect(data.columns[1].text).to.be('Series'); + expect(data.columns[2].text).to.be('Value'); + expect(data.rows[0][1]).to.be('series1'); + expect(data.rows[0][2]).to.be('12.12'); + + expect(data.rows[1][1]).to.be('series2'); + }); + }); + + describe('timeseries_to_rows', () => { + var panel = { + transform: 'timeseries_to_columns' + }; + + it ('should return 3 columns', () => { + var data = TableModel.transform([ + { + target: 'series1', + datapoints: [[12.12, new Date().getTime()]], + }, + { + target: 'series2', + datapoints: [[16.12, new Date().getTime()]], + } + ], panel); + + expect(data.columns.length).to.be(3); expect(data.rows.length).to.be(1); expect(data.columns[0].text).to.be('Time'); - expect(data.columns[1].text).to.be('Value'); + expect(data.columns[1].text).to.be('series1'); + expect(data.columns[2].text).to.be('series2'); + expect(data.rows[0][1]).to.be('12.12'); + expect(data.rows[0][2]).to.be('16.12'); }); - }); }); diff --git a/public/app/panels/table/table_model.ts b/public/app/panels/table/table_model.ts index 0351f30b33b..9410c36badf 100644 --- a/public/app/panels/table/table_model.ts +++ b/public/app/panels/table/table_model.ts @@ -3,21 +3,17 @@ import moment = require('moment'); import _ = require('lodash'); -export class TableModel { - columns: any[]; - rows: any[]; - - static transform(data, panel) { - var model = new TableModel(); - - if (!data || data.length === 0) { - return model; - } +var transformers = {}; +transformers['timeseries_to_rows'] = { + description: 'Time series to rows', + transform: function(data, panel, model) { model.columns = [ {text: 'Time'}, + {text: 'Series'}, {text: 'Value'}, ]; + model.rows = []; for (var i = 0; i < data.length; i++) { @@ -32,12 +28,71 @@ export class TableModel { value = value.toFixed(2); } - model.rows.push([time, value]); + model.rows.push([time, series.target, value]); + } + } + }, +}; + +transformers['timeseries_to_columns'] = { + description: 'Time series to columns', + transform: function(data, panel, model) { + model.columns = [{text: 'Time'}]; + model.rows = []; + + var points = {}; + + for (var i = 0; i < data.length; i++) { + var series = data[i]; + model.columns.push({text: series.target}); + + for (var y = 0; y < series.datapoints.length; y++) { + var dp = series.datapoints[y]; + var time = dp[1]; + if (!points[time]) { + points[time] = {}; + points[time][i] = [dp[0]]; + } + else { + points[time][i] = dp[0]; + } } } + for (var time in points) { + var point = points[time]; + var values = [time]; + + for (var i = 0; i < data.length; i++) { + if (point[i] !== undefined) { + values.push(point[i]); + } + } + + model.rows.push(values); + } + } +}; + +export {transformers} + +export class TableModel { + columns: any[]; + rows: any[]; + + static transform(data, panel) { + var model = new TableModel(); + + if (!data || data.length === 0) { + return model; + } + + var transformer = transformers[panel.transform]; + if (!transformer) { + throw {message: 'Transformer ' + panel.transformer + ' not found'}; + } + + transformer.transform(data, panel, model); return model; } } - - diff --git a/public/less/panel_table.less b/public/less/panel_table.less index 8cd62cbb76b..8f7dea39f9a 100644 --- a/public/less/panel_table.less +++ b/public/less/panel_table.less @@ -17,7 +17,6 @@ .gf-table-panel { width: 100%; - table-layout: fixed; border-collapse: collapse; } From 6062930f9a69374670ff671ad1961f7f7db08a01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 4 Nov 2015 09:41:03 +0100 Subject: [PATCH 081/394] feat(tablepanel): added more unit tests for table transforms --- public/app/panels/table/module.ts | 8 +- public/app/panels/table/options.html | 11 ++- .../panels/table/specs/table_model_specs.ts | 65 -------------- .../panels/table/specs/transformers_specs.ts | 72 ++++++++++++++++ public/app/panels/table/table_model.ts | 78 +---------------- public/app/panels/table/transformers.ts | 84 +++++++++++++++++++ 6 files changed, 172 insertions(+), 146 deletions(-) delete mode 100644 public/app/panels/table/specs/table_model_specs.ts create mode 100644 public/app/panels/table/specs/transformers_specs.ts create mode 100644 public/app/panels/table/transformers.ts diff --git a/public/app/panels/table/module.ts b/public/app/panels/table/module.ts index 8cb46d6cc46..6103e11113b 100644 --- a/public/app/panels/table/module.ts +++ b/public/app/panels/table/module.ts @@ -6,7 +6,9 @@ import _ = require('lodash'); import moment = require('moment'); import PanelMeta = require('app/features/panel/panel_meta'); import TimeSeries = require('app/core/time_series'); -import {TableModel, transformers} from './table_model'; + +import {TableModel} from './table_model'; +import {transformers} from './transformers'; export class TablePanelCtrl { @@ -26,7 +28,9 @@ export class TablePanelCtrl { var panelDefaults = { targets: [{}], - transform: 'timeseries_to_rows' + transform: 'timeseries_to_rows', + pageSize: 50, + showHeader: true, }; _.defaults($scope.panel, panelDefaults); diff --git a/public/app/panels/table/options.html b/public/app/panels/table/options.html index 7f1332b6e18..717259df40f 100644 --- a/public/app/panels/table/options.html +++ b/public/app/panels/table/options.html @@ -1,10 +1,10 @@
      -
      Data Table
      +
      Data
      • - Data to Table Transform + To Table Transform
      • +
      diff --git a/public/app/panels/table/specs/table_model_specs.ts b/public/app/panels/table/specs/table_model_specs.ts deleted file mode 100644 index 04106100392..00000000000 --- a/public/app/panels/table/specs/table_model_specs.ts +++ /dev/null @@ -1,65 +0,0 @@ -import {describe, beforeEach, it, sinon, expect} from 'test/lib/common'; - -import {TableModel} from '../table_model'; - -describe('when getting tableData', () => { - - describe('timeseries_to_rows', () => { - var panel = { - transform: 'timeseries_to_rows' - }; - - it ('should return 2 rows', () => { - var data = TableModel.transform([ - { - target: 'series1', - datapoints: [[12.12, new Date().getTime()]], - }, - { - target: 'series2', - datapoints: [[12.12, new Date().getTime()]], - } - ], panel); - - expect(data.columns.length).to.be(3); - expect(data.rows.length).to.be(2); - - expect(data.columns[0].text).to.be('Time'); - expect(data.columns[1].text).to.be('Series'); - expect(data.columns[2].text).to.be('Value'); - expect(data.rows[0][1]).to.be('series1'); - expect(data.rows[0][2]).to.be('12.12'); - - expect(data.rows[1][1]).to.be('series2'); - }); - }); - - describe('timeseries_to_rows', () => { - var panel = { - transform: 'timeseries_to_columns' - }; - - it ('should return 3 columns', () => { - var data = TableModel.transform([ - { - target: 'series1', - datapoints: [[12.12, new Date().getTime()]], - }, - { - target: 'series2', - datapoints: [[16.12, new Date().getTime()]], - } - ], panel); - - expect(data.columns.length).to.be(3); - expect(data.rows.length).to.be(1); - - expect(data.columns[0].text).to.be('Time'); - expect(data.columns[1].text).to.be('series1'); - expect(data.columns[2].text).to.be('series2'); - expect(data.rows[0][1]).to.be('12.12'); - expect(data.rows[0][2]).to.be('16.12'); - }); - }); - -}); diff --git a/public/app/panels/table/specs/transformers_specs.ts b/public/app/panels/table/specs/transformers_specs.ts new file mode 100644 index 00000000000..cbe4fd6312b --- /dev/null +++ b/public/app/panels/table/specs/transformers_specs.ts @@ -0,0 +1,72 @@ +import {describe, beforeEach, it, sinon, expect} from 'test/lib/common'; + +import {TableModel} from '../table_model'; + +describe('when transforming time series table', () => { + var table; + + describe('given 2 time series', () => { + var time = new Date().getTime(); + var timeSeries = [ + { + target: 'series1', + datapoints: [[12.12, time], [14.44, time+1]], + }, + { + target: 'series2', + datapoints: [[16.12, time]], + } + ]; + + describe('timeseries_to_rows', () => { + var panel = {transform: 'timeseries_to_rows'}; + + beforeEach(() => { + table = TableModel.transform(timeSeries, panel); + }); + + it('should return 3 rows', () => { + expect(table.rows.length).to.be(3); + expect(table.rows[0][1]).to.be('series1'); + expect(table.rows[1][1]).to.be('series1'); + expect(table.rows[2][1]).to.be('series2'); + expect(table.rows[0][2]).to.be('12.12'); + }); + + it('should return 3 rows', () => { + expect(table.columns.length).to.be(3); + expect(table.columns[0].text).to.be('Time'); + expect(table.columns[1].text).to.be('Series'); + expect(table.columns[2].text).to.be('Value'); + }); + }); + + describe('timeseries_to_columns', () => { + var panel = { + transform: 'timeseries_to_columns' + }; + + beforeEach(() => { + table = TableModel.transform(timeSeries, panel); + }); + + it ('should return 3 columns', () => { + expect(table.columns.length).to.be(3); + expect(table.columns[0].text).to.be('Time'); + expect(table.columns[1].text).to.be('series1'); + expect(table.columns[2].text).to.be('series2'); + }); + + it ('should return 2 rows', () => { + expect(table.rows.length).to.be(2); + expect(table.rows[0][1]).to.be('12.12'); + expect(table.rows[0][2]).to.be('16.12'); + }); + + it ('should show - when no value for timestamp', () => { + expect(table.rows[1][2]).to.be('-'); + }); + }); + }); +}); + diff --git a/public/app/panels/table/table_model.ts b/public/app/panels/table/table_model.ts index 9410c36badf..528d250fb99 100644 --- a/public/app/panels/table/table_model.ts +++ b/public/app/panels/table/table_model.ts @@ -1,80 +1,4 @@ -/// - -import moment = require('moment'); -import _ = require('lodash'); - -var transformers = {}; - -transformers['timeseries_to_rows'] = { - description: 'Time series to rows', - transform: function(data, panel, model) { - model.columns = [ - {text: 'Time'}, - {text: 'Series'}, - {text: 'Value'}, - ]; - - model.rows = []; - - for (var i = 0; i < data.length; i++) { - var series = data[i]; - for (var y = 0; y < series.datapoints.length; y++) { - var dp = series.datapoints[y]; - var time = moment(dp[1]).format('LLL'); - var value = dp[0]; - if (value === null) { - value = 'null'; - } else if (_.isNumber(value)) { - value = value.toFixed(2); - } - - model.rows.push([time, series.target, value]); - } - } - }, -}; - -transformers['timeseries_to_columns'] = { - description: 'Time series to columns', - transform: function(data, panel, model) { - model.columns = [{text: 'Time'}]; - model.rows = []; - - var points = {}; - - for (var i = 0; i < data.length; i++) { - var series = data[i]; - model.columns.push({text: series.target}); - - for (var y = 0; y < series.datapoints.length; y++) { - var dp = series.datapoints[y]; - var time = dp[1]; - if (!points[time]) { - points[time] = {}; - points[time][i] = [dp[0]]; - } - else { - points[time][i] = dp[0]; - } - } - } - - for (var time in points) { - var point = points[time]; - var values = [time]; - - for (var i = 0; i < data.length; i++) { - if (point[i] !== undefined) { - values.push(point[i]); - } - } - - model.rows.push(values); - } - } -}; - -export {transformers} +import {transformers} from './transformers'; export class TableModel { columns: any[]; diff --git a/public/app/panels/table/transformers.ts b/public/app/panels/table/transformers.ts new file mode 100644 index 00000000000..7617f03c413 --- /dev/null +++ b/public/app/panels/table/transformers.ts @@ -0,0 +1,84 @@ +/// + +import moment = require('moment'); +import _ = require('lodash'); + +var transformers = {}; + +transformers['timeseries_to_rows'] = { + description: 'Time series to rows', + transform: function(data, panel, model) { + model.columns = [ + {text: 'Time'}, + {text: 'Series'}, + {text: 'Value'}, + ]; + + model.rows = []; + + for (var i = 0; i < data.length; i++) { + var series = data[i]; + for (var y = 0; y < series.datapoints.length; y++) { + var dp = series.datapoints[y]; + var time = moment(dp[1]).format('LLL'); + var value = dp[0]; + if (value === null) { + value = 'null'; + } else if (_.isNumber(value)) { + value = value.toFixed(2); + } + + model.rows.push([time, series.target, value]); + } + } + }, +}; + +transformers['timeseries_to_columns'] = { + description: 'Time series to columns', + transform: function(data, panel, model) { + model.columns = [{text: 'Time'}]; + model.rows = []; + + // group by time + var points = {}; + + for (var i = 0; i < data.length; i++) { + var series = data[i]; + model.columns.push({text: series.target}); + + for (var y = 0; y < series.datapoints.length; y++) { + var dp = series.datapoints[y]; + var timeKey = dp[1].toString(); + + if (!points[timeKey]) { + points[timeKey] = {time: dp[1]}; + points[timeKey][i] = dp[0]; + } + else { + points[timeKey][i] = dp[0]; + } + } + } + + for (var time in points) { + var point = points[time]; + var values = [moment(point.time).format('LLL')]; + + for (var i = 0; i < data.length; i++) { + var value = point[i]; + if (_.isNumber(value)) { + values.push(value.toFixed(2)); + } else { + values.push('-'); + } + } + + model.rows.push(values); + } + } +}; + +export {transformers} + + From fdeeb73587180417cd1524cbf8f1cb94c31fedd4 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Wed, 4 Nov 2015 02:27:35 -0800 Subject: [PATCH 082/394] AWS Region as a mandatory field --- public/app/plugins/datasource/cloudwatch/partials/config.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/cloudwatch/partials/config.html b/public/app/plugins/datasource/cloudwatch/partials/config.html index 60c645c33f9..15d747ac937 100644 --- a/public/app/plugins/datasource/cloudwatch/partials/config.html +++ b/public/app/plugins/datasource/cloudwatch/partials/config.html @@ -20,7 +20,7 @@ Default RegionSpecify the region, such as for US West (Oregon) use ` us-west-2 ` as the region.
    • - +
    From 93b4f3fac8fdda0c48a0a69f35ab90a6e3019a38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 4 Nov 2015 12:56:53 +0100 Subject: [PATCH 083/394] feat(tablepanel): minor progress on table panel --- public/app/panels/table/module.html | 2 + public/app/panels/table/module.ts | 69 +++++++++++----- public/app/panels/table/options.html | 19 +++-- public/less/grafana.less | 1 + public/less/pagination.less | 113 +++++++++++++++++++++++++++ public/less/panel_table.less | 12 ++- 6 files changed, 187 insertions(+), 29 deletions(-) create mode 100644 public/less/pagination.less diff --git a/public/app/panels/table/module.html b/public/app/panels/table/module.html index c85252354dc..75d428acf6a 100644 --- a/public/app/panels/table/module.html +++ b/public/app/panels/table/module.html @@ -2,5 +2,7 @@
    +
    diff --git a/public/app/panels/table/module.ts b/public/app/panels/table/module.ts index 6103e11113b..f4da7161f6f 100644 --- a/public/app/panels/table/module.ts +++ b/public/app/panels/table/module.ts @@ -15,6 +15,7 @@ export class TablePanelCtrl { constructor($scope, $rootScope, $q, panelSrv, panelHelper) { $scope.ctrl = this; $scope.transformers = transformers; + $scope.pageIndex = 0; $scope.panelMeta = new PanelMeta({ panelName: 'Table', @@ -68,6 +69,7 @@ export function tablePanelDirective() { controller: TablePanelCtrl, link: function(scope, elem) { var data; + var panel = scope.panel; function getTableHeight() { var panelHeight = scope.height || scope.panel.height || scope.row.height; @@ -78,39 +80,67 @@ export function tablePanelDirective() { return (panelHeight - 40) + 'px'; } - function renderPanel() { - var rootDiv = elem.find('.table-panel-container'); - var tableDiv = $('
    '); - var i, y, rowElem, colElem, column, row; - - rowElem = $('
    ' + column.text + '
    ' + column.text + '
    ' + row[i] + '
    ' + row[i] + '
    '); + + appendTableHeader(tableElem); + appendTableRows(tableElem); + + rootElem.css({'max-height': getTableHeight()}); + rootElem.empty(); + rootElem.append(tableElem); + appendPaginationControls(footerElem); } scope.$on('render', function(event, renderData) { @@ -126,4 +156,3 @@ export function tablePanelDirective() { } angular.module('grafana.directives').directive('grafanaPanelTable', tablePanelDirective); - diff --git a/public/app/panels/table/options.html b/public/app/panels/table/options.html index 717259df40f..8c91cc1ecf9 100644 --- a/public/app/panels/table/options.html +++ b/public/app/panels/table/options.html @@ -21,14 +21,19 @@
    Table Display
    +
    +
      +
    • + Pagination (Page size) +
    • +
    • + +
    • +
    +
    +
    -
  • - Pagination (Page size) -
  • -
  • - -
  • diff --git a/public/less/grafana.less b/public/less/grafana.less index e82c3b93c01..041c0112481 100644 --- a/public/less/grafana.less +++ b/public/less/grafana.less @@ -15,6 +15,7 @@ @import "navbar.less"; @import "gfbox.less"; @import "admin.less"; +@import "pagination.less"; @import "validation.less"; @import "fonts.less"; @import "tabs.less"; diff --git a/public/less/pagination.less b/public/less/pagination.less new file mode 100644 index 00000000000..6e292e5ab37 --- /dev/null +++ b/public/less/pagination.less @@ -0,0 +1,113 @@ +.pagination { +} + +.pagination ul { + display: inline-block; + margin-left: 0; + margin-bottom: 0; + .border-radius(@baseBorderRadius); + .box-shadow(0 1px 2px rgba(0,0,0,.05)); +} +.pagination ul > li { + display: inline; // Remove list-style and block-level defaults +} +.pagination ul > li > a, +.pagination ul > li > span { + float: left; // Collapse white-space + padding: 4px 12px; + line-height: @baseLineHeight; + text-decoration: none; + background-color: @paginationBackground; + border: 1px solid @paginationBorder; + border-left-width: 0; +} +.pagination ul > li > a:hover, +.pagination ul > li > a:focus, +.pagination ul > .active > a, +.pagination ul > .active > span { + background-color: @paginationActiveBackground; +} +.pagination ul > .active > a, +.pagination ul > .active > span { + color: @grayLight; + cursor: default; +} +.pagination ul > .disabled > span, +.pagination ul > .disabled > a, +.pagination ul > .disabled > a:hover, +.pagination ul > .disabled > a:focus { + color: @grayLight; + background-color: transparent; + cursor: default; +} +.pagination ul > li:first-child > a, +.pagination ul > li:first-child > span { + border-left-width: 1px; + .border-left-radius(@baseBorderRadius); +} +.pagination ul > li:last-child > a, +.pagination ul > li:last-child > span { + .border-right-radius(@baseBorderRadius); +} + + +// Alignment +// -------------------------------------------------- + +.pagination-centered { + text-align: center; +} +.pagination-right { + text-align: right; +} + + +// Sizing +// -------------------------------------------------- + +// Large +.pagination-large { + ul > li > a, + ul > li > span { + padding: @paddingLarge; + font-size: @fontSizeLarge; + } + ul > li:first-child > a, + ul > li:first-child > span { + .border-left-radius(@borderRadiusLarge); + } + ul > li:last-child > a, + ul > li:last-child > span { + .border-right-radius(@borderRadiusLarge); + } +} + +// Small and mini +.pagination-mini, +.pagination-small { + ul > li:first-child > a, + ul > li:first-child > span { + .border-left-radius(@borderRadiusSmall); + } + ul > li:last-child > a, + ul > li:last-child > span { + .border-right-radius(@borderRadiusSmall); + } +} + +// Small +.pagination-small { + ul > li > a, + ul > li > span { + padding: @paddingSmall; + font-size: @fontSizeSmall; + } +} +// Mini +.pagination-mini { + ul > li > a, + ul > li > span { + padding: @paddingMini; + font-size: @fontSizeMini; + } +} diff --git a/public/less/panel_table.less b/public/less/panel_table.less index 8f7dea39f9a..272f5a5c3dc 100644 --- a/public/less/panel_table.less +++ b/public/less/panel_table.less @@ -11,6 +11,13 @@ overflow: auto; } +.table-panel-footer { + text-align: center; + .pagination { + display: inline-block; + } +} + .gf-table-panel* { box-sizing: border-box; } @@ -25,10 +32,11 @@ } .gf-table-panel th { - background: @grafanaTargetFuncBackground; + background: @grafanaListAccent; padding: 5px 0 5px 15px; text-align: left; border-top: 2px solid @bodyBackground; + color: @blue; &:first-child { padding-left: 15px; @@ -36,7 +44,7 @@ } .gf-table-panel td { - padding: 15px 0 15px 15px; + padding: 12px 0 12px 15px; &:first-child { padding-left: 15px; From 7387f2e490ac6b6eadd8bd2487f18d1154c65998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 4 Nov 2015 17:23:16 +0100 Subject: [PATCH 084/394] feat(tablepanel): fixed header, and pagination styling --- public/app/panels/table/module.html | 16 +++++ public/app/panels/table/module.ts | 26 +++---- public/app/panels/table/transformers.ts | 10 ++- public/less/panel_table.less | 91 ++++++++++++++++--------- 4 files changed, 95 insertions(+), 48 deletions(-) diff --git a/public/app/panels/table/module.html b/public/app/panels/table/module.html index 75d428acf6a..1ceb7f52647 100644 --- a/public/app/panels/table/module.html +++ b/public/app/panels/table/module.html @@ -1,6 +1,22 @@
    +
    +
    + + + + + + + + +
    +
    + {{col.text}} +
    +
    +
    diff --git a/public/app/panels/table/module.ts b/public/app/panels/table/module.ts index f4da7161f6f..deae2aaed89 100644 --- a/public/app/panels/table/module.ts +++ b/public/app/panels/table/module.ts @@ -53,8 +53,8 @@ export class TablePanelCtrl { }; $scope.render = function() { - $scope.tableModel = TableModel.transform($scope.dataRaw, $scope.panel); - panelHelper.broadcastRender($scope, $scope.tableModel); + $scope.table = TableModel.transform($scope.dataRaw, $scope.panel); + panelHelper.broadcastRender($scope, $scope.table); }; panelSrv.init($scope); @@ -93,8 +93,8 @@ export function tablePanelDirective() { headElem.appendTo(tableElem); } - function appendTableRows(tableElem) { - var tbodyElem = $(''); + function appendTableRows(tbodyElem) { + var rowElements = $(document.createDocumentFragment()); var rowEnd = Math.min(panel.pageSize, data.rows.length); var rowStart = 0; @@ -105,14 +105,14 @@ export function tablePanelDirective() { var colElem = $('' + row[i] + ''); rowElem.append(colElem); } - tbodyElem.append(rowElem); + rowElements.append(rowElem); } - tableElem.append(tbodyElem); + tbodyElem.empty(); + tbodyElem.append(rowElements); } function appendPaginationControls(footerElem) { - var paginationElem = $(' diff --git a/public/app/panels/table/specs/transformers_specs.ts b/public/app/panels/table/specs/transformers_specs.ts index cbe4fd6312b..ea3eb609976 100644 --- a/public/app/panels/table/specs/transformers_specs.ts +++ b/public/app/panels/table/specs/transformers_specs.ts @@ -30,7 +30,7 @@ describe('when transforming time series table', () => { expect(table.rows[0][1]).to.be('series1'); expect(table.rows[1][1]).to.be('series1'); expect(table.rows[2][1]).to.be('series2'); - expect(table.rows[0][2]).to.be('12.12'); + expect(table.rows[0][2]).to.be(12.12); }); it('should return 3 rows', () => { @@ -59,12 +59,12 @@ describe('when transforming time series table', () => { it ('should return 2 rows', () => { expect(table.rows.length).to.be(2); - expect(table.rows[0][1]).to.be('12.12'); - expect(table.rows[0][2]).to.be('16.12'); + expect(table.rows[0][1]).to.be(12.12); + expect(table.rows[0][2]).to.be(16.12); }); - it ('should show - when no value for timestamp', () => { - expect(table.rows[1][2]).to.be('-'); + it ('should be undefined when no value for timestamp', () => { + expect(table.rows[1][2]).to.be(undefined); }); }); }); From 7d3146ed8d45a8d67a1381821a39cd6e5d2a59ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 5 Nov 2015 08:36:51 +0100 Subject: [PATCH 089/394] feat(tablepanel): fixed header, and pagination styling --- public/app/panels/table/table_model.ts | 5 +++ .../datasource/elasticsearch/datasource.js | 5 +-- .../datasource/elasticsearch/metric_agg.js | 8 +++-- .../elasticsearch/partials/metricAgg.html | 2 +- .../datasource/elasticsearch/query_def.js | 17 +++++----- .../elasticsearch/specs/datasource_specs.ts | 32 +++++++++++++++++++ 6 files changed, 56 insertions(+), 13 deletions(-) diff --git a/public/app/panels/table/table_model.ts b/public/app/panels/table/table_model.ts index 528d250fb99..943234bcd89 100644 --- a/public/app/panels/table/table_model.ts +++ b/public/app/panels/table/table_model.ts @@ -4,6 +4,11 @@ export class TableModel { columns: any[]; rows: any[]; + constructor() { + this.columns = []; + this.rows = []; + } + static transform(data, panel) { var model = new TableModel(); diff --git a/public/app/plugins/datasource/elasticsearch/datasource.js b/public/app/plugins/datasource/elasticsearch/datasource.js index 4e27da73850..f8770064b8e 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.js +++ b/public/app/plugins/datasource/elasticsearch/datasource.js @@ -170,7 +170,8 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes target = options.targets[i]; if (target.hide) {return;} - var esQuery = angular.toJson(this.queryBuilder.build(target)); + var queryObj = this.queryBuilder.build(target); + var esQuery = angular.toJson(queryObj); var luceneQuery = angular.toJson(target.query || '*'); // remove inner quotes luceneQuery = luceneQuery.substr(1, luceneQuery.length - 2); @@ -185,7 +186,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes payload = payload.replace(/\$timeTo/g, options.range.to.valueOf()); payload = templateSrv.replace(payload, options.scopedVars); - return this._post('/_msearch?search_type=count', payload).then(function(res) { + return this._post('/_msearch', payload).then(function(res) { return new ElasticResponse(sentTargets, res).getTimeSeries(); }); }; diff --git a/public/app/plugins/datasource/elasticsearch/metric_agg.js b/public/app/plugins/datasource/elasticsearch/metric_agg.js index fd748a1f8b0..c4e04dad325 100644 --- a/public/app/plugins/datasource/elasticsearch/metric_agg.js +++ b/public/app/plugins/datasource/elasticsearch/metric_agg.js @@ -28,6 +28,7 @@ function (angular, _, queryDef) { $scope.isFirst = $scope.index === 0; $scope.isSingle = metricAggs.length === 1; $scope.settingsLinkText = ''; + $scope.aggDef = _.findWhere($scope.metricAggTypes, {value: $scope.agg.type}); if (!$scope.agg.field) { $scope.agg.field = 'select field'; @@ -53,6 +54,11 @@ function (angular, _, queryDef) { $scope.agg.meta.std_deviation_bounds_lower = true; $scope.agg.meta.std_deviation_bounds_upper = true; } + break; + } + case 'raw_document': { + $scope.target.metrics = [$scope.agg]; + $scope.target.bucketAggs = []; } } }; @@ -65,8 +71,6 @@ function (angular, _, queryDef) { $scope.agg.settings = {}; $scope.agg.meta = {}; $scope.showOptions = false; - - $scope.validateModel(); $scope.onChange(); }; diff --git a/public/app/plugins/datasource/elasticsearch/partials/metricAgg.html b/public/app/plugins/datasource/elasticsearch/partials/metricAgg.html index 7d58be38094..65030af5655 100644 --- a/public/app/plugins/datasource/elasticsearch/partials/metricAgg.html +++ b/public/app/plugins/datasource/elasticsearch/partials/metricAgg.html @@ -6,7 +6,7 @@
  • -
  • +
  • diff --git a/public/app/plugins/datasource/elasticsearch/query_def.js b/public/app/plugins/datasource/elasticsearch/query_def.js index ca915c68dd4..baab2378e9f 100644 --- a/public/app/plugins/datasource/elasticsearch/query_def.js +++ b/public/app/plugins/datasource/elasticsearch/query_def.js @@ -6,14 +6,15 @@ function (_) { return { metricAggTypes: [ - {text: "Count", value: 'count' }, - {text: "Average", value: 'avg' }, - {text: "Sum", value: 'sum' }, - {text: "Max", value: 'max' }, - {text: "Min", value: 'min' }, - {text: "Extended Stats", value: 'extended_stats' }, - {text: "Percentiles", value: 'percentiles' }, - {text: "Unique Count", value: "cardinality" } + {text: "Count", value: 'count', requiresField: false}, + {text: "Average", value: 'avg', requiresField: true}, + {text: "Sum", value: 'sum', requiresField: true}, + {text: "Max", value: 'max', requiresField: true}, + {text: "Min", value: 'min', requiresField: true}, + {text: "Extended Stats", value: 'extended_stats', requiresField: true}, + {text: "Percentiles", value: 'percentiles', requiresField: true}, + {text: "Unique Count", value: "cardinality", requiresField: true}, + {text: "Raw Document", value: "raw_document", requiresField: false} ], bucketAggTypes: [ diff --git a/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts b/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts index f087f847a19..4747030ff3d 100644 --- a/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts @@ -80,4 +80,36 @@ describe('ElasticDatasource', function() { expect(body.query.filtered.query.query_string.query).to.be('escape\\:test'); }); }); + + describe('When issueing document query', function() { + var requestOptions, parts, header; + + beforeEach(function() { + ctx.ds = new ctx.service({url: 'http://es.com', index: 'test', jsonData: {}}); + + ctx.backendSrv.datasourceRequest = function(options) { + requestOptions = options; + return ctx.$q.when({data: {responses: []}}); + }; + + ctx.ds.query({ + range: { from: moment([2015, 4, 30, 10]), to: moment([2015, 5, 1, 10]) }, + targets: [{ bucketAggs: [], metrics: [{type: 'raw_document'}], query: 'test' }] + }); + + ctx.$rootScope.$apply(); + parts = requestOptions.data.split('\n'); + header = angular.fromJson(parts[0]); + }); + + it('should set search type to query_then_fetch', function() { + expect(header.search_type).to.eql('query_then_fetch'); + }); + + it('should set size', function() { + var body = angular.fromJson(parts[1]); + expect(body.query.size).to.be(500); + }); + }); + }); From 4e37290a7f28039251c61b0b3d78df5fda02b2b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 5 Nov 2015 09:56:19 +0100 Subject: [PATCH 090/394] feat(tablepanel/elasticsearch): extended elasticsearch data source and query editor to support document queries --- public/app/panels/table/transformers.ts | 20 +++++--- .../datasource/elasticsearch/datasource.js | 18 ++++--- .../elasticsearch/elastic_response.js | 47 ++++++++++++++++--- .../datasource/elasticsearch/query_builder.js | 19 ++++++++ .../elasticsearch/specs/datasource_specs.ts | 2 +- .../specs/elastic_response_specs.ts | 36 ++++++++++++++ .../specs/query_builder_specs.ts | 10 ++++ 7 files changed, 132 insertions(+), 20 deletions(-) diff --git a/public/app/panels/table/transformers.ts b/public/app/panels/table/transformers.ts index afca521c942..42ccae1982e 100644 --- a/public/app/panels/table/transformers.ts +++ b/public/app/panels/table/transformers.ts @@ -14,8 +14,6 @@ transformers['timeseries_to_rows'] = { {text: 'Value'}, ]; - model.rows = []; - for (var i = 0; i < data.length; i++) { var series = data[i]; for (var y = 0; y < series.datapoints.length; y++) { @@ -31,8 +29,7 @@ transformers['timeseries_to_rows'] = { transformers['timeseries_to_columns'] = { description: 'Time series to columns', transform: function(data, panel, model) { - model.columns = [{text: 'Time'}]; - model.rows = []; + model.columns.push({text: 'Time'}); // group by time var points = {}; @@ -75,8 +72,19 @@ transformers['annotations'] = { transformers['json'] = { description: 'JSON', + transform: function(data, panel, model) { + model.columns.push({text: 'JSON'}); + debugger; + + for (var i = 0; i < data.length; i++) { + var series = data[i]; + + for (var y = 0; y < series.datapoints.length; y++) { + var dp = series.datapoints[y]; + model.rows.push([JSON.stringify(dp)]); + } + } + } }; export {transformers} - - diff --git a/public/app/plugins/datasource/elasticsearch/datasource.js b/public/app/plugins/datasource/elasticsearch/datasource.js index f8770064b8e..ce370c639d2 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.js +++ b/public/app/plugins/datasource/elasticsearch/datasource.js @@ -153,8 +153,8 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes }); }; - ElasticDatasource.prototype.getQueryHeader = function(timeFrom, timeTo) { - var header = {search_type: "count", "ignore_unavailable": true}; + ElasticDatasource.prototype.getQueryHeader = function(searchType, timeFrom, timeTo) { + var header = {search_type: searchType, "ignore_unavailable": true}; header.index = this.indexPattern.getIndexList(timeFrom, timeTo); return angular.toJson(header); }; @@ -163,8 +163,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes var payload = ""; var target; var sentTargets = []; - - var header = this.getQueryHeader(options.range.from, options.range.to); + var headerAdded = false; for (var i = 0; i < options.targets.length; i++) { target = options.targets[i]; @@ -177,7 +176,14 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes luceneQuery = luceneQuery.substr(1, luceneQuery.length - 2); esQuery = esQuery.replace("$lucene_query", luceneQuery); - payload += header + '\n' + esQuery + '\n'; + if (!headerAdded) { + var searchType = queryObj.size === 0 ? 'count' : 'query_then_fetch'; + var header = this.getQueryHeader(searchType, options.range.from, options.range.to); + payload += header + '\n'; + headerAdded = true; + } + + payload += esQuery + '\n'; sentTargets.push(target); } @@ -230,7 +236,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes ElasticDatasource.prototype.getTerms = function(queryDef) { var range = timeSrv.timeRange(); - var header = this.getQueryHeader(range.from, range.to); + var header = this.getQueryHeader('count', range.from, range.to); var esQuery = angular.toJson(this.queryBuilder.getTermsQuery(queryDef)); esQuery = esQuery.replace("$lucene_query", queryDef.query || '*'); diff --git a/public/app/plugins/datasource/elasticsearch/elastic_response.js b/public/app/plugins/datasource/elasticsearch/elastic_response.js index bfbcb2034ab..aaac2e46ea1 100644 --- a/public/app/plugins/datasource/elasticsearch/elastic_response.js +++ b/public/app/plugins/datasource/elasticsearch/elastic_response.js @@ -173,6 +173,33 @@ function (_, queryDef) { } }; + ElasticResponse.prototype.processHits = function(hits, seriesList) { + var series = {target: 'docs', type: 'docs', datapoints: [], total: hits.total}; + var propName, hit, doc, i; + + for (i = 0; i < hits.hits.length; i++) { + hit = hits.hits[i]; + doc = { + _id: hit._id, + _type: hit._type, + _index: hit._index + }; + + if (hit._source) { + for (propName in hit._source) { + doc[propName] = hit._source[propName]; + } + } + + for (propName in hit.fields) { + doc[propName] = hit.fields[propName]; + } + series.datapoints.push(doc); + } + + seriesList.push(series); + }; + ElasticResponse.prototype.getTimeSeries = function() { var seriesList = []; @@ -182,15 +209,21 @@ function (_, queryDef) { throw { message: response.error }; } - var aggregations = response.aggregations; - var target = this.targets[i]; - var tmpSeriesList = []; + if (response.hits) { + this.processHits(response.hits, seriesList); + } - this.processBuckets(aggregations, target, tmpSeriesList, {}); - this.nameSeries(tmpSeriesList, target); + if (response.aggregations) { + var aggregations = response.aggregations; + var target = this.targets[i]; + var tmpSeriesList = []; - for (var y = 0; y < tmpSeriesList.length; y++) { - seriesList.push(tmpSeriesList[y]); + this.processBuckets(aggregations, target, tmpSeriesList, {}); + this.nameSeries(tmpSeriesList, target); + + for (var y = 0; y < tmpSeriesList.length; y++) { + seriesList.push(tmpSeriesList[y]); + } } } diff --git a/public/app/plugins/datasource/elasticsearch/query_builder.js b/public/app/plugins/datasource/elasticsearch/query_builder.js index d0fbb6a603a..3a956012a86 100644 --- a/public/app/plugins/datasource/elasticsearch/query_builder.js +++ b/public/app/plugins/datasource/elasticsearch/query_builder.js @@ -71,6 +71,16 @@ function (angular) { return filterObj; }; + ElasticQueryBuilder.prototype.documentQuery = function(query) { + query.size = 500; + query.sort = {}; + query.sort[this.timeField] = {order: 'desc', unmapped_type: 'boolean'}; + query.fields = ["*", "_source"]; + query.script_fields = {}, + query.fielddata_fields = [this.timeField]; + return query; + }; + ElasticQueryBuilder.prototype.build = function(target) { if (target.rawQuery) { return angular.fromJson(target.rawQuery); @@ -96,6 +106,15 @@ function (angular) { } }; + // handle document query + if (target.bucketAggs.length === 0) { + metric = target.metrics[0]; + if (metric && metric.type !== 'raw_document') { + throw {message: 'Invalid query'}; + } + return this.documentQuery(query, target); + } + nestedAggs = query; for (i = 0; i < target.bucketAggs.length; i++) { diff --git a/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts b/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts index 4747030ff3d..1e5d39ba817 100644 --- a/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts @@ -108,7 +108,7 @@ describe('ElasticDatasource', function() { it('should set size', function() { var body = angular.fromJson(parts[1]); - expect(body.query.size).to.be(500); + expect(body.size).to.be(500); }); }); diff --git a/public/app/plugins/datasource/elasticsearch/specs/elastic_response_specs.ts b/public/app/plugins/datasource/elasticsearch/specs/elastic_response_specs.ts index df810e3a9d9..5ae67f92d78 100644 --- a/public/app/plugins/datasource/elasticsearch/specs/elastic_response_specs.ts +++ b/public/app/plugins/datasource/elasticsearch/specs/elastic_response_specs.ts @@ -411,4 +411,40 @@ describe('ElasticResponse', function() { }); }); + describe('Raw documents query', function() { + beforeEach(function() { + targets = [{ refId: 'A', metrics: [{type: 'raw_document', id: '1'}], bucketAggs: [] }]; + response = { + responses: [{ + hits: { + total: 100, + hits: [ + { + _id: '1', + _type: 'type', + _index: 'index', + _source: {sourceProp: "asd"}, + fields: {fieldProp: "field" }, + }, + { + _source: {sourceProp: "asd2"}, + fields: {fieldProp: "field2" }, + } + ] + } + }] + }; + + result = new ElasticResponse(targets, response).getTimeSeries(); + }); + + it('should return docs', function() { + expect(result.data.length).to.be(1); + expect(result.data[0].type).to.be('docs'); + expect(result.data[0].total).to.be(100); + expect(result.data[0].datapoints.length).to.be(2); + expect(result.data[0].datapoints[0].sourceProp).to.be("asd"); + expect(result.data[0].datapoints[0].fieldProp).to.be("field"); + }); + }); }); diff --git a/public/app/plugins/datasource/elasticsearch/specs/query_builder_specs.ts b/public/app/plugins/datasource/elasticsearch/specs/query_builder_specs.ts index c32b1463ca3..bcae7d6e852 100644 --- a/public/app/plugins/datasource/elasticsearch/specs/query_builder_specs.ts +++ b/public/app/plugins/datasource/elasticsearch/specs/query_builder_specs.ts @@ -120,4 +120,14 @@ describe('ElasticQueryBuilder', function() { expect(query.aggs["2"].aggs["4"].date_histogram.field).to.be("@timestamp"); }); + it('with raw_document metric', function() { + var query = builder.build({ + metrics: [{type: 'raw_document', id: '1'}], + timeField: '@timestamp', + bucketAggs: [], + }); + + expect(query.size).to.be(500); + }); + }); From 90cca9395110ff84b48b5b5a6287248c31f256c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 5 Nov 2015 13:13:13 +0100 Subject: [PATCH 091/394] feat(tablepanel): lots of work on table panel --- .../app/core/directives/dropdown_typeahead.js | 24 ++- public/app/features/panel/panel_helper.js | 4 +- public/app/panels/table/controller.ts | 43 +---- public/app/panels/table/editor.html | 167 ++++++++++++++++++ public/app/panels/table/editor.ts | 106 +++++++++++ public/app/panels/table/module.ts | 13 +- public/app/panels/table/options.html | 119 +------------ .../panels/table/specs/transformers_specs.ts | 35 ++++ public/app/panels/table/transformers.ts | 36 ++-- 9 files changed, 365 insertions(+), 182 deletions(-) create mode 100644 public/app/panels/table/editor.html create mode 100644 public/app/panels/table/editor.ts diff --git a/public/app/core/directives/dropdown_typeahead.js b/public/app/core/directives/dropdown_typeahead.js index c306b9ff8e5..ad484bb18d7 100644 --- a/public/app/core/directives/dropdown_typeahead.js +++ b/public/app/core/directives/dropdown_typeahead.js @@ -45,16 +45,25 @@ function (_, $, coreModule) { } var typeaheadValues = _.reduce($scope.menuItems, function(memo, value, index) { - _.each(value.submenu, function(item, subIndex) { - item.click = 'menuItemSelected(' + index + ',' + subIndex + ')'; - memo.push(value.text + ' ' + item.text); - }); + if (!value.submenu) { + value.click = 'menuItemSelected(' + index + ')'; + memo.push(value.text); + } else { + _.each(value.submenu, function(item, subIndex) { + item.click = 'menuItemSelected(' + index + ',' + subIndex + ')'; + memo.push(value.text + ' ' + item.text); + }); + } return memo; }, []); $scope.menuItemSelected = function(index, subIndex) { - var item = $scope.menuItems[index]; - $scope.dropdownTypeaheadOnSelect({$item: item, $subItem: item.submenu[subIndex]}); + var menuItem = $scope.menuItems[index]; + var payload = {$item: menuItem}; + if (menuItem.submenu && subIndex !== void 0) { + payload.$subItem = menuItem.submenu[subIndex]; + } + $scope.dropdownTypeaheadOnSelect(payload); }; $input.attr('data-provide', 'typeahead'); @@ -65,9 +74,10 @@ function (_, $, coreModule) { updater: function (value) { var result = {}; _.each($scope.menuItems, function(menuItem) { + result.$item = menuItem; + _.each(menuItem.submenu, function(submenuItem) { if (value === (menuItem.text + ' ' + submenuItem.text)) { - result.$item = menuItem; result.$subItem = submenuItem; } }); diff --git a/public/app/features/panel/panel_helper.js b/public/app/features/panel/panel_helper.js index c3ccbfe7e09..52a6727eb2c 100644 --- a/public/app/features/panel/panel_helper.js +++ b/public/app/features/panel/panel_helper.js @@ -32,9 +32,9 @@ function (angular, _, $, kbn, dateMath, rangeUtil) { scope.timing.renderEnd = new Date().getTime(); }; - this.broadcastRender = function(scope, data) { + this.broadcastRender = function(scope, arg1, arg2) { this.setTimeRenderStart(scope); - scope.$broadcast('render', data); + scope.$broadcast('render', arg1, arg2); this.setTimeRenderEnd(scope); if ($rootScope.profilingEnabled) { diff --git a/public/app/panels/table/controller.ts b/public/app/panels/table/controller.ts index 4ae5a1807fa..7c22ba2823f 100644 --- a/public/app/panels/table/controller.ts +++ b/public/app/panels/table/controller.ts @@ -3,24 +3,15 @@ import angular = require('angular'); import _ = require('lodash'); import moment = require('moment'); -import kbn = require('app/core/utils/kbn'); import PanelMeta = require('app/features/panel/panel_meta'); import {TableModel} from './table_model'; -import {transformers} from './transformers'; export class TablePanelCtrl { constructor($scope, $rootScope, $q, panelSrv, panelHelper) { $scope.ctrl = this; - $scope.transformers = transformers; $scope.pageIndex = 0; - $scope.unitFormats = kbn.getUnitFormats(); - $scope.colorModes = { - 'cell': {text: 'Cell'}, - 'value': {text: 'Value'}, - 'row': {text: 'Row'}, - }; $scope.panelMeta = new PanelMeta({ panelName: 'Table', @@ -38,23 +29,18 @@ export class TablePanelCtrl { pageSize: 50, showHeader: true, columns: [], + fields: [] }; $scope.init = function() { _.defaults($scope.panel, panelDefaults); if ($scope.panel.columns.length === 0) { - $scope.addColumnStyle(); } panelSrv.init($scope); }; - $scope.setUnitFormat = function(column, subItem) { - column.unit = subItem.value; - $scope.render(); - }; - $scope.refreshData = function(datasource) { panelHelper.updateTimeRange($scope); @@ -73,32 +59,7 @@ export class TablePanelCtrl { $scope.render = function() { $scope.table = TableModel.transform($scope.dataRaw, $scope.panel); - panelHelper.broadcastRender($scope, $scope.table); - }; - - $scope.getColumnNames = function() { - if (!$scope.table) { - return []; - } - return _.map($scope.table.columns, function(col: any) { - return col.text; - }); - }; - - $scope.addColumnStyle = function() { - var columnStyleDefaults = { - unit: 'short', - decimals: 2, - colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"], - pattern: '/.*/', - colorMode: 'value', - }; - - $scope.panel.columns.push(angular.copy(columnStyleDefaults)); - }; - - $scope.removeColumnStyle = function(col) { - $scope.panel.columns = _.without($scope.panel.columns, col); + panelHelper.broadcastRender($scope, $scope.table, $scope.dataRaw); }; $scope.init(); diff --git a/public/app/panels/table/editor.html b/public/app/panels/table/editor.html new file mode 100644 index 00000000000..e7c76402974 --- /dev/null +++ b/public/app/panels/table/editor.html @@ -0,0 +1,167 @@ +
    +
    +
    Data
    +
    +
    +
      +
    • + To Table Transform +
    • +
    • + +
    • +
    +
    +
    +
    +
      +
    • + Fields +
    • +
    • + + + {{field.name}} + +
    • + +
    +
    +
    +
    +
    + +
    +
    Table Display
    +
    +
    +
      +
    • + Pagination (Page size) +
    • +
    • + +
    • +
    +
    +
    +
    +
    +
    + +
    +
    Column Styles
    + +
    +
    +
    +
      +
    • + +
    • +
    • + Name or regex +
    • +
    • + +
    • +
    • + Type +
    • +
    • + +
    • +
    +
    +
    +
    +
      +
    • + +
    • +
    • + Format +
    • +
    • + +
    • +
    +
    +
    +
    +
      +
    • + +
    • +
    • + Coloring +
    • +
    • + +
    • +
    • + ThresholdsComma seperated values +
    • +
    • + +
    • +
    • + Colors +
    • +
    • + + + +
    • +
    • + invert order +
    • +
    +
    +
    +
    +
      +
    • + +
    • +
    • + Unit +
    • + +
    • + Decimals +
    • +
    • + +
    • +
    +
    +
    + +
    +
    + + +
    + diff --git a/public/app/panels/table/editor.ts b/public/app/panels/table/editor.ts new file mode 100644 index 00000000000..4cd4070dd1b --- /dev/null +++ b/public/app/panels/table/editor.ts @@ -0,0 +1,106 @@ + +/// + +import angular = require('angular'); +import $ = require('jquery'); +import _ = require('lodash'); +import kbn = require('app/core/utils/kbn'); +import moment = require('moment'); + +import {transformers} from './transformers'; + +export function tablePanelEditor() { + 'use strict'; + return { + restrict: 'E', + scope: true, + templateUrl: 'app/panels/table/editor.html', + link: function(scope, elem) { + scope.transformers = transformers; + scope.unitFormats = kbn.getUnitFormats(); + scope.colorModes = { + 'cell': {text: 'Cell'}, + 'value': {text: 'Value'}, + 'row': {text: 'Row'}, + }; + scope.columnTypes = { + 'number': {text: 'Number'}, + 'string': {text: 'String'}, + 'date': {text: 'Date'}, + }; + + scope.updateJsonFieldsMenu = function(data) { + scope.jsonFieldsMenu = []; + if (!data || data.length === 0) { + return; + } + + var names = {}; + for (var i = 0; i < data.length; i++) { + var series = data[i]; + if (series.type !== 'docs') { + continue; + } + + for (var y = 0; y < series.datapoints.length; y++) { + var doc = series.datapoints[y]; + for (var propName in doc) { + names[propName] = true; + } + } + } + + _.each(names, function(value, key) { + scope.jsonFieldsMenu.push({text: key}); + }); + }; + + scope.updateJsonFieldsMenu(scope.dataRaw); + + scope.$on('render', function(event, table, rawData) { + scope.updateJsonFieldsMenu(rawData); + }); + + scope.addJsonField = function(menuItem) { + scope.panel.fields.push({name: menuItem.text}); + }; + + scope.removeJsonField = function(field) { + scope.panel.fields = _.without(scope.panel.fields, field); + }; + + scope.setUnitFormat = function(column, subItem) { + column.unit = subItem.value; + scope.render(); + }; + + scope.addColumnStyle = function() { + var columnStyleDefaults = { + unit: 'short', + type: 'number', + decimals: 2, + colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"], + colorMode: 'value', + pattern: '/.*/', + }; + + scope.panel.columns.push(angular.copy(columnStyleDefaults)); + }; + + scope.removeColumnStyle = function(col) { + scope.panel.columns = _.without(scope.panel.columns, col); + }; + + scope.getColumnNames = function() { + if (!scope.table) { + return []; + } + return _.map(scope.table.columns, function(col: any) { + return col.text; + }); + }; + + } + }; +} + diff --git a/public/app/panels/table/module.ts b/public/app/panels/table/module.ts index 39cd1056290..84955070be4 100644 --- a/public/app/panels/table/module.ts +++ b/public/app/panels/table/module.ts @@ -4,10 +4,12 @@ import angular = require('angular'); import $ = require('jquery'); import _ = require('lodash'); import kbn = require('app/core/utils/kbn'); +import moment = require('moment'); import {TablePanelCtrl} from './controller'; +import {tablePanelEditor} from './editor'; -export function tablePanelDirective() { +export function tablePanel() { 'use strict'; return { restrict: 'E', @@ -45,9 +47,13 @@ export function tablePanelDirective() { if (v === null || v === void 0) { return '-'; } - if (_.isString(v) || style) { + if (_.isString(v) || !style) { return v; } + if (style.type === 'date') { + var date = moment(v); + return date.format(style.dateFormat); + } let valueFormater = kbn.valueFormats[style.unit]; return valueFormater(v, style.decimals); }; @@ -136,4 +142,5 @@ export function tablePanelDirective() { }; } -angular.module('grafana.directives').directive('grafanaPanelTable', tablePanelDirective); +angular.module('grafana.directives').directive('grafanaPanelTable', tablePanel); +angular.module('grafana.directives').directive('grafanaPanelTableEditor', tablePanelEditor); diff --git a/public/app/panels/table/options.html b/public/app/panels/table/options.html index c1d4ca658ac..d43ff958c5d 100644 --- a/public/app/panels/table/options.html +++ b/public/app/panels/table/options.html @@ -1,117 +1,2 @@ -
    -
    -
    Data
    -
    -
    -
      -
    • - To Table Transform -
    • -
    • - -
    • -
    -
    -
    -
    -
    - -
    -
    Table Display
    -
    -
    -
      -
    • - Pagination (Page size) -
    • -
    • - -
    • -
    -
    -
    -
    -
    -
    - -
    -
    Column Styles
    - -
    -
    -
    -
      -
    • - -
    • -
    • - Name or regex -
    • -
    • - -
    • -
    • - Unit -
    • - -
    • - Decimals -
    • -
    • - -
    • -
    -
    -
    -
    -
      -
    • - -
    • -
    • - Coloring -
    • -
    • - -
    • -
    • - ThresholdsComma seperated values -
    • -
    • - -
    • -
    • - Colors -
    • -
    • - - - -
    • -
    • - invert order -
    • -
    -
    -
    -
    -
    - - -
    - + + diff --git a/public/app/panels/table/specs/transformers_specs.ts b/public/app/panels/table/specs/transformers_specs.ts index ea3eb609976..af9dd880cf1 100644 --- a/public/app/panels/table/specs/transformers_specs.ts +++ b/public/app/panels/table/specs/transformers_specs.ts @@ -67,6 +67,41 @@ describe('when transforming time series table', () => { expect(table.rows[1][2]).to.be(undefined); }); }); + + describe('JSON Data', () => { + var panel = { + transform: 'json', + fields: [{name: 'timestamp'}, {name: 'message'}] + }; + var rawData = [ + { + type: 'docs', + datapoints: [ + { + timestamp: 'time', + message: 'message' + } + ] + } + ]; + + beforeEach(() => { + table = TableModel.transform(rawData, panel); + }); + + it ('should return 2 columns', () => { + expect(table.columns.length).to.be(2); + expect(table.columns[0].text).to.be('timestamp'); + expect(table.columns[1].text).to.be('message'); + }); + + it ('should return 2 rows', () => { + expect(table.rows.length).to.be(2); + expect(table.rows[0][0]).to.be('time'); + expect(table.rows[0][1]).to.be('message'); + }); + + }); }); }); diff --git a/public/app/panels/table/transformers.ts b/public/app/panels/table/transformers.ts index 42ccae1982e..096ebb73051 100644 --- a/public/app/panels/table/transformers.ts +++ b/public/app/panels/table/transformers.ts @@ -9,7 +9,7 @@ transformers['timeseries_to_rows'] = { description: 'Time series to rows', transform: function(data, panel, model) { model.columns = [ - {text: 'Time'}, + {text: 'Time', type: 'date'}, {text: 'Series'}, {text: 'Value'}, ]; @@ -18,9 +18,7 @@ transformers['timeseries_to_rows'] = { var series = data[i]; for (var y = 0; y < series.datapoints.length; y++) { var dp = series.datapoints[y]; - var time = moment(dp[1]).format('LLL'); - var value = dp[0]; - model.rows.push([time, series.target, value]); + model.rows.push([dp[1], series.target, dp[0]]); } } }, @@ -29,7 +27,7 @@ transformers['timeseries_to_rows'] = { transformers['timeseries_to_columns'] = { description: 'Time series to columns', transform: function(data, panel, model) { - model.columns.push({text: 'Time'}); + model.columns.push({text: 'Time', type: 'date'}); // group by time var points = {}; @@ -54,7 +52,7 @@ transformers['timeseries_to_columns'] = { for (var time in points) { var point = points[time]; - var values = [moment(point.time).format('LLL')]; + var values = [point.time]; for (var i = 0; i < data.length; i++) { var value = point[i]; @@ -71,17 +69,31 @@ transformers['annotations'] = { }; transformers['json'] = { - description: 'JSON', + description: 'JSON Data', transform: function(data, panel, model) { - model.columns.push({text: 'JSON'}); - debugger; + var i, y, z; + for (i = 0; i < panel.fields.length; i++) { + model.columns.push({text: panel.fields[i].name}); + } - for (var i = 0; i < data.length; i++) { + if (model.columns.length === 0) { + model.columns.push({text: 'JSON'}); + } + + for (i = 0; i < data.length; i++) { var series = data[i]; - for (var y = 0; y < series.datapoints.length; y++) { + for (y = 0; y < series.datapoints.length; y++) { var dp = series.datapoints[y]; - model.rows.push([JSON.stringify(dp)]); + var values = []; + for (z = 0; z < panel.fields.length; z++) { + values.push(dp[panel.fields[z].name]); + } + + if (values.length === 0) { + values.push([JSON.stringify(dp)]); + } + model.rows.push(values); } } } From 1b83742e3e9f6fde94e4efa26a940b7a31dd78a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 5 Nov 2015 15:55:42 +0100 Subject: [PATCH 092/394] feat(tablepanel): began refactorin out table row html generation to write unit tests for it --- public/app/panels/table/editor.html | 16 +++------- public/app/panels/table/editor.ts | 2 ++ public/app/panels/table/module.ts | 28 ++++++++--------- public/app/panels/table/renderer.ts | 31 +++++++++++++++++++ .../app/panels/table/specs/renderer_specs.ts | 25 +++++++++++++++ .../panels/table/specs/transformers_specs.ts | 2 +- public/app/panels/table/transformers.ts | 2 +- 7 files changed, 78 insertions(+), 28 deletions(-) create mode 100644 public/app/panels/table/renderer.ts create mode 100644 public/app/panels/table/specs/renderer_specs.ts diff --git a/public/app/panels/table/editor.html b/public/app/panels/table/editor.html index e7c76402974..8540885514d 100644 --- a/public/app/panels/table/editor.html +++ b/public/app/panels/table/editor.html @@ -60,10 +60,13 @@
    -
      -
    • +
        +
      • +
      + +
      • Name or regex
      • @@ -86,9 +89,6 @@
      -
    • - -
    • Format
    • @@ -100,9 +100,6 @@
      -
    • - -
    • Coloring
    • @@ -136,9 +133,6 @@
      -
    • - -
    • Unit
    • diff --git a/public/app/panels/table/editor.ts b/public/app/panels/table/editor.ts index 4cd4070dd1b..ab10cba7f1d 100644 --- a/public/app/panels/table/editor.ts +++ b/public/app/panels/table/editor.ts @@ -63,10 +63,12 @@ export function tablePanelEditor() { scope.addJsonField = function(menuItem) { scope.panel.fields.push({name: menuItem.text}); + scope.render(); }; scope.removeJsonField = function(field) { scope.panel.fields = _.without(scope.panel.fields, field); + scope.render(); }; scope.setUnitFormat = function(column, subItem) { diff --git a/public/app/panels/table/module.ts b/public/app/panels/table/module.ts index 84955070be4..00610ca0e72 100644 --- a/public/app/panels/table/module.ts +++ b/public/app/panels/table/module.ts @@ -29,19 +29,6 @@ export function tablePanel() { return (panelHeight - 40) + 'px'; } - function appendTableHeader(tableElem) { - var rowElem = $(''); - for (var i = 0; i < data.columns.length; i++) { - var column = data.columns[i]; - var colElem = $('' + column.text + ''); - rowElem.append(colElem); - } - - var headElem = $(''); - headElem.append(rowElem); - headElem.appendTo(tableElem); - } - function createColumnFormater(style) { return function(v) { if (v === null || v === void 0) { @@ -50,12 +37,23 @@ export function tablePanel() { if (_.isString(v) || !style) { return v; } + if (style.type === 'date') { + if (_.isArray(v)) { v = v[0]; } var date = moment(v); return date.format(style.dateFormat); } - let valueFormater = kbn.valueFormats[style.unit]; - return valueFormater(v, style.decimals); + + if (_.isNumber(v) && style.type === 'number') { + let valueFormater = kbn.valueFormats[style.unit]; + return valueFormater(v, style.decimals); + } + + if (_.isArray(v)) { + v = v.join(', '); + } + + return v; }; } diff --git a/public/app/panels/table/renderer.ts b/public/app/panels/table/renderer.ts new file mode 100644 index 00000000000..c3b09ee45da --- /dev/null +++ b/public/app/panels/table/renderer.ts @@ -0,0 +1,31 @@ + +export class TableRenderer { + constructor(private panel, private table) { + } + + formatColumnValue(columnIndex, value) { + return "value"; + } + + renderCell(columnIndex, value) { + var colValue = this.formatColumnValue(columnIndex, value); + return '' + colValue + ''; + } + + render(page) { + let endPos = Math.min(this.panel.pageSize, this.table.rows.length); + let startPos = 0; + var html = ""; + + for (var y = startPos; y < endPos; y++) { + let row = this.table.rows[y]; + html += ''; + for (var i = 0; i < this.table.columns.length; i++) { + html += this.renderCell(i, row[i]); + } + html += ''; + } + + return html; + } +} diff --git a/public/app/panels/table/specs/renderer_specs.ts b/public/app/panels/table/specs/renderer_specs.ts new file mode 100644 index 00000000000..c4c5ff5d3c3 --- /dev/null +++ b/public/app/panels/table/specs/renderer_specs.ts @@ -0,0 +1,25 @@ +import {describe, beforeEach, it, sinon, expect} from 'test/lib/common'; + +import {TableModel} from '../table_model'; +import {TableRenderer} from '../renderer'; + +describe('when rendering table', () => { + describe('given 2 columns', () => { + var table = new TableModel(); + table.columns = [{text: 'Time'}, {text: 'Value'}]; + table.rows.push([1446733230253, 12.4]); + table.rows.push([1446733231253, 10.4]); + + var panel = { + pageSize: 10 + }; + + it('render should return html', () => { + var html = new TableRenderer(panel, table).render(0); + expect(html).to.be('value'); + }); + + }); +}); + + diff --git a/public/app/panels/table/specs/transformers_specs.ts b/public/app/panels/table/specs/transformers_specs.ts index af9dd880cf1..597b8913b82 100644 --- a/public/app/panels/table/specs/transformers_specs.ts +++ b/public/app/panels/table/specs/transformers_specs.ts @@ -96,7 +96,7 @@ describe('when transforming time series table', () => { }); it ('should return 2 rows', () => { - expect(table.rows.length).to.be(2); + expect(table.rows.length).to.be(1); expect(table.rows[0][0]).to.be('time'); expect(table.rows[0][1]).to.be('message'); }); diff --git a/public/app/panels/table/transformers.ts b/public/app/panels/table/transformers.ts index 096ebb73051..dce0098dde3 100644 --- a/public/app/panels/table/transformers.ts +++ b/public/app/panels/table/transformers.ts @@ -91,7 +91,7 @@ transformers['json'] = { } if (values.length === 0) { - values.push([JSON.stringify(dp)]); + values.push(JSON.stringify(dp)); } model.rows.push(values); } From e1433ebb417cc94dc9df3c36c8864a11da8be4e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 5 Nov 2015 12:42:47 -0500 Subject: [PATCH 093/394] feat(tablepanel) more refactoring --- public/app/panels/table/module.ts | 71 +------------------ public/app/panels/table/renderer.ts | 63 +++++++++++++++- .../app/panels/table/specs/renderer_specs.ts | 19 +++-- .../datasource/influxdb/partials/config.html | 1 - 4 files changed, 76 insertions(+), 78 deletions(-) diff --git a/public/app/panels/table/module.ts b/public/app/panels/table/module.ts index 00610ca0e72..beb6273fd62 100644 --- a/public/app/panels/table/module.ts +++ b/public/app/panels/table/module.ts @@ -7,6 +7,7 @@ import kbn = require('app/core/utils/kbn'); import moment = require('moment'); import {TablePanelCtrl} from './controller'; +import {TableRenderer} from './renderer'; import {tablePanelEditor} from './editor'; export function tablePanel() { @@ -29,76 +30,10 @@ export function tablePanel() { return (panelHeight - 40) + 'px'; } - function createColumnFormater(style) { - return function(v) { - if (v === null || v === void 0) { - return '-'; - } - if (_.isString(v) || !style) { - return v; - } - - if (style.type === 'date') { - if (_.isArray(v)) { v = v[0]; } - var date = moment(v); - return date.format(style.dateFormat); - } - - if (_.isNumber(v) && style.type === 'number') { - let valueFormater = kbn.valueFormats[style.unit]; - return valueFormater(v, style.decimals); - } - - if (_.isArray(v)) { - v = v.join(', '); - } - - return v; - }; - } - - function formatColumnValue(colIndex, value) { - if (formaters[colIndex]) { - return formaters[colIndex](value); - } - - for (let i = 0; i < panel.columns.length; i++) { - let style = panel.columns[i]; - let column = data.columns[colIndex]; - var regex = kbn.stringToJsRegex(style.pattern); - if (column.text.match(regex)) { - formaters[colIndex] = createColumnFormater(style); - return formaters[colIndex](value); - } - } - - formaters[colIndex] = function(v) { - return v; - }; - - return formaters[colIndex](value); - } - function appendTableRows(tbodyElem) { - let rowElements = $(document.createDocumentFragment()); - let rowEnd = Math.min(panel.pageSize, data.rows.length); - let rowStart = 0; - // reset formater cache - formaters = []; - - for (var y = rowStart; y < rowEnd; y++) { - let row = data.rows[y]; - let rowElem = $(''); - for (var i = 0; i < data.columns.length; i++) { - var colValue = formatColumnValue(i, row[i]); - let colElem = $(' ' + colValue + ''); - rowElem.append(colElem); - } - rowElements.append(rowElem); - } - + var renderer = new TableRenderer(panel, data, scope.dashboard.timezone); tbodyElem.empty(); - tbodyElem.append(rowElements); + tbodyElem.html(renderer.render(0)); } function appendPaginationControls(footerElem) { diff --git a/public/app/panels/table/renderer.ts b/public/app/panels/table/renderer.ts index c3b09ee45da..8672629a23b 100644 --- a/public/app/panels/table/renderer.ts +++ b/public/app/panels/table/renderer.ts @@ -1,10 +1,67 @@ +/// + +import _ = require('lodash'); +import kbn = require('app/core/utils/kbn'); +import moment = require('moment'); export class TableRenderer { - constructor(private panel, private table) { + formaters: any[]; + + constructor(private panel, private table, private timezone) { + this.formaters = []; } - formatColumnValue(columnIndex, value) { - return "value"; + createColumnFormater(style) { + return (v) => { + if (v === null || v === void 0) { + return '-'; + } + if (_.isString(v) || !style) { + return v; + } + + if (style.type === 'date') { + if (_.isArray(v)) { v = v[0]; } + var date = moment(v); + if (this.timezone === 'utc') { + date = date.utc(); + } + return date.format(style.dateFormat); + } + + if (_.isNumber(v) && style.type === 'number') { + let valueFormater = kbn.valueFormats[style.unit]; + return valueFormater(v, style.decimals); + } + + if (_.isArray(v)) { + v = v.join(', '); + } + + return v; + }; + } + + formatColumnValue(colIndex, value) { + if (this.formaters[colIndex]) { + return this.formaters[colIndex](value); + } + + for (let i = 0; i < this.panel.columns.length; i++) { + let style = this.panel.columns[i]; + let column = this.table.columns[colIndex]; + var regex = kbn.stringToJsRegex(style.pattern); + if (column.text.match(regex)) { + this.formaters[colIndex] = this.createColumnFormater(style); + return this.formaters[colIndex](value); + } + } + + this.formaters[colIndex] = function(v) { + return v; + }; + + return this.formaters[colIndex](value); } renderCell(columnIndex, value) { diff --git a/public/app/panels/table/specs/renderer_specs.ts b/public/app/panels/table/specs/renderer_specs.ts index c4c5ff5d3c3..8bf6dd308d0 100644 --- a/public/app/panels/table/specs/renderer_specs.ts +++ b/public/app/panels/table/specs/renderer_specs.ts @@ -7,16 +7,23 @@ describe('when rendering table', () => { describe('given 2 columns', () => { var table = new TableModel(); table.columns = [{text: 'Time'}, {text: 'Value'}]; - table.rows.push([1446733230253, 12.4]); - table.rows.push([1446733231253, 10.4]); var panel = { - pageSize: 10 + pageSize: 10, + columns: [ + { + pattern: 'Time', + type: 'date', + format: 'LLL' + } + ] }; - it('render should return html', () => { - var html = new TableRenderer(panel, table).render(0); - expect(html).to.be('value'); + var renderer = new TableRenderer(panel, table, 'utc'); + + it('time column should be formated', () => { + var html = renderer.renderCell(0, 1388556366666); + expect(html).to.be('2014-01-01T06:06:06+00:00'); }); }); diff --git a/public/app/plugins/datasource/influxdb/partials/config.html b/public/app/plugins/datasource/influxdb/partials/config.html index 66c39fe7b69..4d51a33e6b2 100644 --- a/public/app/plugins/datasource/influxdb/partials/config.html +++ b/public/app/plugins/datasource/influxdb/partials/config.html @@ -1,4 +1,3 @@ -

      From b678daa744ea78cf4b3928a11080b8fd86aeb30e Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Thu, 5 Nov 2015 22:31:33 -0800 Subject: [PATCH 094/394] Added throughput units. --- public/app/components/kbn.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/public/app/components/kbn.js b/public/app/components/kbn.js index ad2d4c3c422..825df91ff3c 100644 --- a/public/app/components/kbn.js +++ b/public/app/components/kbn.js @@ -344,6 +344,12 @@ function($, _) { kbn.valueFormats.bps = kbn.formatBuilders.decimalSIPrefix('bps'); kbn.valueFormats.Bps = kbn.formatBuilders.decimalSIPrefix('Bps'); + // Throughput + kbn.valueFormats.ops = kbn.formatBuilders.fixedUnit('ops'); + kbn.valueFormats.rps = kbn.formatBuilders.fixedUnit('rps'); + kbn.valueFormats.wps = kbn.formatBuilders.fixedUnit('wps'); + kbn.valueFormats.iops = kbn.formatBuilders.fixedUnit('iops'); + // Energy kbn.valueFormats.watt = kbn.formatBuilders.decimalSIPrefix('W'); kbn.valueFormats.kwatt = kbn.formatBuilders.decimalSIPrefix('W', 1); @@ -522,6 +528,15 @@ function($, _) { {text: 'bytes/sec', value: 'Bps'}, ] }, + { + text: 'throughput', + submenu: [ + {text: 'ops/sec (ops)', value: 'ops' }, + {text: 'reads/sec (rps)', value: 'rps' }, + {text: 'writes/sec (wps)', value: 'wps' }, + {text: 'I/O ops/sec (iops)', value: 'iops'}, + ] + }, { text: 'length', submenu: [ @@ -576,7 +591,7 @@ function($, _) { {text: 'Inches of mercury', value: 'pressurehg' }, {text: 'PSI', value: 'pressurepsi' }, ] - }, + } ]; }; From 22c3ec2d638e94c985e40bff3aea6e8515f7841b Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Thu, 5 Nov 2015 22:57:05 -0800 Subject: [PATCH 095/394] Made the units more readable --- public/app/components/kbn.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/public/app/components/kbn.js b/public/app/components/kbn.js index 825df91ff3c..34d943ed028 100644 --- a/public/app/components/kbn.js +++ b/public/app/components/kbn.js @@ -310,6 +310,16 @@ function($, _) { }; }; + kbn.formatBuilders.simpleCountUnit = function(symbol) { + var units = ['', 'K', 'M', 'B', 'T']; + var scaler = kbn.formatBuilders.scaledUnits(1000, units); + return function(size, decimals, scaledDecimals) { + if (size === null) { return ""; } + var scaled = scaler(size, decimals, scaledDecimals); + return scaled + " " + symbol; + }; + }; + ///// VALUE FORMATS ///// // Dimensionless Units @@ -345,10 +355,10 @@ function($, _) { kbn.valueFormats.Bps = kbn.formatBuilders.decimalSIPrefix('Bps'); // Throughput - kbn.valueFormats.ops = kbn.formatBuilders.fixedUnit('ops'); - kbn.valueFormats.rps = kbn.formatBuilders.fixedUnit('rps'); - kbn.valueFormats.wps = kbn.formatBuilders.fixedUnit('wps'); - kbn.valueFormats.iops = kbn.formatBuilders.fixedUnit('iops'); + kbn.valueFormats.ops = kbn.formatBuilders.simpleCountUnit('ops'); + kbn.valueFormats.rps = kbn.formatBuilders.simpleCountUnit('rps'); + kbn.valueFormats.wps = kbn.formatBuilders.simpleCountUnit('wps'); + kbn.valueFormats.iops = kbn.formatBuilders.simpleCountUnit('iops'); // Energy kbn.valueFormats.watt = kbn.formatBuilders.decimalSIPrefix('W'); From b5f18561ab401a49446d5e46cf334679b94c0b67 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Thu, 5 Nov 2015 23:13:53 -0800 Subject: [PATCH 096/394] Added unit tests to verify units --- public/test/specs/kbn-format-specs.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/public/test/specs/kbn-format-specs.js b/public/test/specs/kbn-format-specs.js index f84544fff4f..3e4803633e3 100644 --- a/public/test/specs/kbn-format-specs.js +++ b/public/test/specs/kbn-format-specs.js @@ -62,6 +62,12 @@ define([ describeValueFormat('ns', 25, 1, 0, '25 ns'); describeValueFormat('ns', 2558, 50, 0, '2.56 µs'); + describeValueFormat('ops', 123, 1, 0, '123 ops'); + describeValueFormat('rps', 456000, 1000, -1, '456K rps'); + describeValueFormat('rps', 123456789, 1000000, 2, '123.457M rps'); + describeValueFormat('wps', 789000000, 1000000, -1, '789M wps'); + describeValueFormat('iops', 11000000000, 1000000000, -1, '11B iops'); + describe('kbn.toFixed and negative decimals', function() { it('should treat as zero decimals', function() { var str = kbn.toFixed(186.123, -2); From b8e6fcfeaebad99da576e08e5d90214c428ef6bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 6 Nov 2015 13:16:17 +0100 Subject: [PATCH 097/394] feat(tablepanel): worked on cell / value threshold coloring --- public/app/panels/table/editor.html | 15 ++--- public/app/panels/table/editor.ts | 24 +++---- public/app/panels/table/renderer.ts | 65 +++++++++++++++---- .../app/panels/table/specs/renderer_specs.ts | 30 ++++++++- 4 files changed, 99 insertions(+), 35 deletions(-) diff --git a/public/app/panels/table/editor.html b/public/app/panels/table/editor.html index 8540885514d..72bc1cf8a1e 100644 --- a/public/app/panels/table/editor.html +++ b/public/app/panels/table/editor.html @@ -79,21 +79,18 @@
    -
    -
    -
    -
      -
    • +
        +
      • Format
      • - +
      @@ -106,7 +103,7 @@
    • @@ -115,7 +112,7 @@ ThresholdsComma seperated values
    • - +
    • Colors diff --git a/public/app/panels/table/editor.ts b/public/app/panels/table/editor.ts index ab10cba7f1d..8aae1840629 100644 --- a/public/app/panels/table/editor.ts +++ b/public/app/panels/table/editor.ts @@ -18,16 +18,17 @@ export function tablePanelEditor() { link: function(scope, elem) { scope.transformers = transformers; scope.unitFormats = kbn.getUnitFormats(); - scope.colorModes = { - 'cell': {text: 'Cell'}, - 'value': {text: 'Value'}, - 'row': {text: 'Row'}, - }; - scope.columnTypes = { - 'number': {text: 'Number'}, - 'string': {text: 'String'}, - 'date': {text: 'Date'}, - }; + scope.colorModes = [ + {text: 'Disabled', value: null}, + {text: 'Cell', value: 'cell'}, + {text: 'Value', value: 'value'}, + {text: 'Row', value: 'row'}, + ]; + scope.columnTypes = [ + {text: 'Number', value: 'number'}, + {text: 'String', value: 'string'}, + {text: 'Date', value: 'date'}, + ]; scope.updateJsonFieldsMenu = function(data) { scope.jsonFieldsMenu = []; @@ -82,8 +83,9 @@ export function tablePanelEditor() { type: 'number', decimals: 2, colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"], - colorMode: 'value', + colorMode: null, pattern: '/.*/', + thresholds: [], }; scope.panel.columns.push(angular.copy(columnStyleDefaults)); diff --git a/public/app/panels/table/renderer.ts b/public/app/panels/table/renderer.ts index 8672629a23b..a2c86d940b9 100644 --- a/public/app/panels/table/renderer.ts +++ b/public/app/panels/table/renderer.ts @@ -6,32 +6,59 @@ import moment = require('moment'); export class TableRenderer { formaters: any[]; + colorState: any; constructor(private panel, private table, private timezone) { this.formaters = []; + this.colorState = {}; + } + + getColorForValue(value, style) { + if (!style.thresholds) { return null; } + + for (var i = style.thresholds.length - 1; i >= 0 ; i--) { + if (value >= style.thresholds[i]) { + return style.colors[i]; + } + } + return null; } createColumnFormater(style) { - return (v) => { - if (v === null || v === void 0) { - return '-'; - } - if (_.isString(v) || !style) { - return v; - } + if (!style) { + return v => v; + } - if (style.type === 'date') { + if (style.type === 'date') { + return v => { if (_.isArray(v)) { v = v[0]; } var date = moment(v); if (this.timezone === 'utc') { date = date.utc(); } return date.format(style.dateFormat); - } + }; + } - if (_.isNumber(v) && style.type === 'number') { - let valueFormater = kbn.valueFormats[style.unit]; - return valueFormater(v, style.decimals); + if (style.type === 'number') { + let valueFormater = kbn.valueFormats[style.unit]; + + return v => { + if (v === null || v === void 0) { + return '-'; + } + + if (style.colorMode) { + this.colorState[style.colorMode] = this.getColorForValue(v, style); + } + + return valueFormater(v, style.decimals, null); + }; + } + + return v => { + if (v === null || v === void 0) { + return '-'; } if (_.isArray(v)) { @@ -65,8 +92,18 @@ export class TableRenderer { } renderCell(columnIndex, value) { - var colValue = this.formatColumnValue(columnIndex, value); - return '' + colValue + ''; + var value = this.formatColumnValue(columnIndex, value); + var style = ''; + if (this.colorState.cell) { + style = ' style="background-color:' + this.colorState.cell + ';color: white"'; + this.colorState.cell = null; + } + else if (this.colorState.value) { + style = ' style="color:' + this.colorState.value + '"'; + this.colorState.value = null; + } + + return '' + value + ''; } render(page) { diff --git a/public/app/panels/table/specs/renderer_specs.ts b/public/app/panels/table/specs/renderer_specs.ts index 8bf6dd308d0..08f63dfe799 100644 --- a/public/app/panels/table/specs/renderer_specs.ts +++ b/public/app/panels/table/specs/renderer_specs.ts @@ -6,7 +6,11 @@ import {TableRenderer} from '../renderer'; describe('when rendering table', () => { describe('given 2 columns', () => { var table = new TableModel(); - table.columns = [{text: 'Time'}, {text: 'Value'}]; + table.columns = [ + {text: 'Time'}, + {text: 'Value'}, + {text: 'Colored'} + ]; var panel = { pageSize: 10, @@ -15,6 +19,21 @@ describe('when rendering table', () => { pattern: 'Time', type: 'date', format: 'LLL' + }, + { + pattern: 'Value', + type: 'number', + unit: 'ms', + decimals: 3, + }, + { + pattern: 'Colored', + type: 'number', + unit: 'none', + decimals: 1, + colorMode: 'value', + thresholds: [0, 50, 80], + colors: ['green', 'orange', 'red'] } ] }; @@ -26,6 +45,15 @@ describe('when rendering table', () => { expect(html).to.be('2014-01-01T06:06:06+00:00'); }); + it('number column should be formated', () => { + var html = renderer.renderCell(1, 1230); + expect(html).to.be('1.230 s'); + }); + + it('colored cell should have style', () => { + var html = renderer.renderCell(2, 55); + expect(html).to.be('55.0'); + }); }); }); From 2676f24e0a09040331d49c391cb774ca447a8b11 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Fri, 6 Nov 2015 20:17:27 -0800 Subject: [PATCH 098/394] Fixed user deletion in Postgres SQL --- pkg/services/sqlstore/user.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pkg/services/sqlstore/user.go b/pkg/services/sqlstore/user.go index 6c6f581dcc4..cf39199c1da 100644 --- a/pkg/services/sqlstore/user.go +++ b/pkg/services/sqlstore/user.go @@ -319,10 +319,15 @@ func SearchUsers(query *m.SearchUsersQuery) error { func DeleteUser(cmd *m.DeleteUserCommand) error { return inTransaction(func(sess *xorm.Session) error { - deletes := []string{ - "DELETE FROM star WHERE user_id = ?", - "DELETE FROM user WHERE id = ?", - } + var deletes [2]string + + if (sess.Engine.DriverName() == "postgres") { + deletes[0] = "DELETE FROM star WHERE user_id = ?" + deletes[1] = "DELETE FROM \"user\" WHERE id = ?" + } else { + deletes[0] = "DELETE FROM star WHERE user_id = ?" + deletes[1] = "DELETE FROM user WHERE id = ?" + } for _, sql := range deletes { _, err := sess.Exec(sql, cmd.UserId) From e16bbc660d7bc19e3ae0e81173dcf55817e24e5e Mon Sep 17 00:00:00 2001 From: Don Date: Sat, 7 Nov 2015 00:06:15 -0600 Subject: [PATCH 099/394] use [auth.github] -> api_url property; supports git enterprise --- pkg/social/social.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/social/social.go b/pkg/social/social.go index 1a00934b937..06932f3d500 100644 --- a/pkg/social/social.go +++ b/pkg/social/social.go @@ -193,7 +193,7 @@ func (s *SocialGithub) FetchPrivateEmail(client *http.Client) (string, error) { Verified bool `json:"verified"` } - emailsUrl := fmt.Sprintf("https://api.github.com/user/emails") + emailsUrl := fmt.Sprintf(s.apiUrl + "/emails") r, err := client.Get(emailsUrl) if err != nil { return "", err @@ -222,7 +222,7 @@ func (s *SocialGithub) FetchTeamMemberships(client *http.Client) ([]int, error) Id int `json:"id"` } - membershipUrl := fmt.Sprintf("https://api.github.com/user/teams") + membershipUrl := fmt.Sprintf(s.apiUrl + "/teams") r, err := client.Get(membershipUrl) if err != nil { return nil, err @@ -249,7 +249,7 @@ func (s *SocialGithub) FetchOrganizations(client *http.Client) ([]string, error) Login string `json:"login"` } - url := fmt.Sprintf("https://api.github.com/user/orgs") + url := fmt.Sprintf(s.apiUrl + "/orgs") r, err := client.Get(url) if err != nil { return nil, err From fe2d8f1ea0175b3c3b6e6868b26286f56277648c Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Sat, 7 Nov 2015 05:21:22 -0800 Subject: [PATCH 100/394] Used dialect for postgres --- pkg/services/sqlstore/user.go | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/pkg/services/sqlstore/user.go b/pkg/services/sqlstore/user.go index cf39199c1da..96b8c24b8fc 100644 --- a/pkg/services/sqlstore/user.go +++ b/pkg/services/sqlstore/user.go @@ -319,15 +319,10 @@ func SearchUsers(query *m.SearchUsersQuery) error { func DeleteUser(cmd *m.DeleteUserCommand) error { return inTransaction(func(sess *xorm.Session) error { - var deletes [2]string - - if (sess.Engine.DriverName() == "postgres") { - deletes[0] = "DELETE FROM star WHERE user_id = ?" - deletes[1] = "DELETE FROM \"user\" WHERE id = ?" - } else { - deletes[0] = "DELETE FROM star WHERE user_id = ?" - deletes[1] = "DELETE FROM user WHERE id = ?" - } + deletes := []string{ + "DELETE FROM star WHERE user_id = ?", + "DELETE FROM " + dialect.Quote("user") + " WHERE id = ?", + } for _, sql := range deletes { _, err := sess.Exec(sql, cmd.UserId) From b345c7cf461a3647ab8d0e376dc6d959a75084f8 Mon Sep 17 00:00:00 2001 From: Don Thapa Date: Sat, 7 Nov 2015 11:32:06 -0600 Subject: [PATCH 101/394] gofmt happiness --- pkg/social/social.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/social/social.go b/pkg/social/social.go index 06932f3d500..4a8cb8bac5d 100644 --- a/pkg/social/social.go +++ b/pkg/social/social.go @@ -222,7 +222,7 @@ func (s *SocialGithub) FetchTeamMemberships(client *http.Client) ([]int, error) Id int `json:"id"` } - membershipUrl := fmt.Sprintf(s.apiUrl + "/teams") + membershipUrl := fmt.Sprintf(s.apiUrl + "/teams") r, err := client.Get(membershipUrl) if err != nil { return nil, err From b0cb6d6d4c5ae63119f282b1c1cc26ffcc808b51 Mon Sep 17 00:00:00 2001 From: Nick Owens Date: Sat, 7 Nov 2015 18:17:05 -0800 Subject: [PATCH 102/394] pkg/api/cloudwatch: fix api client construction against aws-sdk-go v0.10.2 --- pkg/api/cloudwatch/cloudwatch.go | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/pkg/api/cloudwatch/cloudwatch.go b/pkg/api/cloudwatch/cloudwatch.go index 763eb3d489a..6311d62f43e 100644 --- a/pkg/api/cloudwatch/cloudwatch.go +++ b/pkg/api/cloudwatch/cloudwatch.go @@ -9,6 +9,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds" + "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/cloudwatch" "github.com/aws/aws-sdk-go/service/ec2" "github.com/grafana/grafana/pkg/middleware" @@ -45,10 +46,13 @@ func handleGetMetricStatistics(req *cwRequest, c *middleware.Context) { &credentials.SharedCredentialsProvider{Filename: "", Profile: req.DataSource.Database}, &ec2rolecreds.EC2RoleProvider{ExpiryWindow: 5 * time.Minute}, }) - svc := cloudwatch.New(&aws.Config{ + + cfg := &aws.Config{ Region: aws.String(req.Region), Credentials: creds, - }) + } + + svc := cloudwatch.New(session.New(cfg), cfg) reqParam := &struct { Parameters struct { @@ -89,10 +93,13 @@ func handleListMetrics(req *cwRequest, c *middleware.Context) { &credentials.SharedCredentialsProvider{Filename: "", Profile: req.DataSource.Database}, &ec2rolecreds.EC2RoleProvider{ExpiryWindow: 5 * time.Minute}, }) - svc := cloudwatch.New(&aws.Config{ + + cfg := &aws.Config{ Region: aws.String(req.Region), Credentials: creds, - }) + } + + svc := cloudwatch.New(session.New(cfg), cfg) reqParam := &struct { Parameters struct { @@ -119,7 +126,11 @@ func handleListMetrics(req *cwRequest, c *middleware.Context) { } func handleDescribeInstances(req *cwRequest, c *middleware.Context) { - svc := ec2.New(&aws.Config{Region: aws.String(req.Region)}) + cfg := &aws.Config{ + Region: aws.String(req.Region), + } + + svc := ec2.New(session.New(cfg), cfg) reqParam := &struct { Parameters struct { From 0bff097afbf7aaea31974084bfd6bffe519ecb4e Mon Sep 17 00:00:00 2001 From: Nick Owens Date: Sat, 7 Nov 2015 17:38:27 -0800 Subject: [PATCH 103/394] pkg/log: implement syslog logger --- pkg/log/syslog.go | 93 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 pkg/log/syslog.go diff --git a/pkg/log/syslog.go b/pkg/log/syslog.go new file mode 100644 index 00000000000..4fe9d6a7522 --- /dev/null +++ b/pkg/log/syslog.go @@ -0,0 +1,93 @@ +package log + +import ( + "encoding/json" + "errors" + "log/syslog" +) + +type SyslogWriter struct { + syslog *syslog.Writer + Network string `json:"network"` + Address string `json:"address"` + Facility string `json:"facility"` + Tag string `json:"tag"` +} + +func NewSyslog() LoggerInterface { + return new(SyslogWriter) +} + +func (sw *SyslogWriter) Init(config string) error { + if err := json.Unmarshal([]byte(config), sw); err != nil { + return err + } + + prio, err := parseFacility(sw.Facility) + if err != nil { + return err + } + + w, err := syslog.Dial(sw.Network, sw.Address, prio, sw.Tag) + if err != nil { + return err + } + + sw.syslog = w + return nil +} + +func (sw *SyslogWriter) WriteMsg(msg string, skip, level int) error { + var err error + + switch level { + case TRACE, DEBUG: + err = sw.syslog.Debug(msg) + case INFO: + err = sw.syslog.Info(msg) + case WARN: + err = sw.syslog.Warning(msg) + case ERROR: + err = sw.syslog.Err(msg) + case CRITICAL: + err = sw.syslog.Crit(msg) + case FATAL: + err = sw.syslog.Alert(msg) + default: + err = errors.New("invalid syslog level") + } + + return err +} + +func (sw *SyslogWriter) Destroy() { + sw.syslog.Close() +} + +func (sw *SyslogWriter) Flush() {} + +var facilities = map[string]syslog.Priority{ + "user": syslog.LOG_USER, + "daemon": syslog.LOG_DAEMON, + "local0": syslog.LOG_LOCAL0, + "local1": syslog.LOG_LOCAL1, + "local2": syslog.LOG_LOCAL2, + "local3": syslog.LOG_LOCAL3, + "local4": syslog.LOG_LOCAL4, + "local5": syslog.LOG_LOCAL5, + "local6": syslog.LOG_LOCAL6, + "local7": syslog.LOG_LOCAL7, +} + +func parseFacility(facility string) (syslog.Priority, error) { + prio, ok := facilities[facility] + if !ok { + return syslog.LOG_LOCAL0, errors.New("invalid syslog facility") + } + + return prio, nil +} + +func init() { + Register("syslog", NewSyslog) +} From 20b553461b176ae395685e524e154425cfdab21e Mon Sep 17 00:00:00 2001 From: Nick Owens Date: Sat, 7 Nov 2015 18:06:31 -0800 Subject: [PATCH 104/394] conf: add syslog logging defaults --- conf/defaults.ini | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/conf/defaults.ini b/conf/defaults.ini index 69d045dc4ae..e90b99e30d3 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -245,6 +245,18 @@ daily_rotate = true # Expired days of log file(delete after max days), default is 7 max_days = 7 +[log.syslog] +level = +# Syslog network type and address. This can be udp, tcp, or unix. If left blank, the default unix endpoints will be used. +network = +address = + +# Syslog facility. user, daemon and local0 through local7 are valid. +facility = + +# Syslog tag. By default, the process' argv[0] is used. +tag = + #################################### AMPQ Event Publisher ########################## [event_publisher] enabled = false From 60e797ccc421d5aab425369311b0cb0accbabc5a Mon Sep 17 00:00:00 2001 From: Nick Owens Date: Sat, 7 Nov 2015 18:06:53 -0800 Subject: [PATCH 105/394] pkg/setting: integrate syslog logger settings --- pkg/setting/setting.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index d6a853a9cdc..291454f6134 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -567,6 +567,14 @@ func initLogging(args *CommandLineArgs) { "driver": sec.Key("driver").String(), "conn": sec.Key("conn").String(), } + case "syslog": + LogConfigs[i] = util.DynMap{ + "level": level, + "network": sec.Key("network").MustString(""), + "address": sec.Key("address").MustString(""), + "facility": sec.Key("facility").MustString("local7"), + "tag": sec.Key("tag").MustString(""), + } } cfgJsonBytes, _ := json.Marshal(LogConfigs[i]) From efc0b60d4122f1b40a1ea5df520f7ec023c67b03 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Sun, 8 Nov 2015 21:44:58 -0800 Subject: [PATCH 106/394] Fixed opentsdb queryctrl uncheck Rate UI bug --- .../datasource/opentsdb/partials/query.editor.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/public/app/plugins/datasource/opentsdb/partials/query.editor.html b/public/app/plugins/datasource/opentsdb/partials/query.editor.html index f2fc4afb181..5cd4fd4f283 100644 --- a/public/app/plugins/datasource/opentsdb/partials/query.editor.html +++ b/public/app/plugins/datasource/opentsdb/partials/query.editor.html @@ -162,20 +162,20 @@
    • -
    • +
    • Counter Max:
    • -
    • +
    • -
    • +
    • Reset Value:
    • -
    • +
    • Date: Mon, 9 Nov 2015 00:28:16 -0800 Subject: [PATCH 107/394] Added missing AWS Regions --- pkg/api/cloudwatch/metrics.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/api/cloudwatch/metrics.go b/pkg/api/cloudwatch/metrics.go index 6d6ff10dfb9..bc21da81f44 100644 --- a/pkg/api/cloudwatch/metrics.go +++ b/pkg/api/cloudwatch/metrics.go @@ -67,8 +67,8 @@ func init() { func handleGetRegions(req *cwRequest, c *middleware.Context) { regions := []string{ - "us-west-2", "us-west-1", "eu-west-1", "eu-central-1", "ap-southeast-1", - "ap-southeast-2", "ap-northeast-1", "sa-east-1", + "us-east-1", "us-west-2", "us-west-1", "eu-west-1", "eu-central-1", "ap-southeast-1", + "ap-southeast-2", "ap-northeast-1", "sa-east-1", "cn-north-1", } result := []interface{}{} From 5d166dc8cb52e2c63e7fb342c9b7a06cfbc06db5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 9 Nov 2015 09:46:49 +0100 Subject: [PATCH 108/394] feat(tablepanel): added new renderer spec --- public/app/panels/table/renderer.ts | 4 ++++ public/app/panels/table/specs/renderer_specs.ts | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/public/app/panels/table/renderer.ts b/public/app/panels/table/renderer.ts index a2c86d940b9..acfef961f07 100644 --- a/public/app/panels/table/renderer.ts +++ b/public/app/panels/table/renderer.ts @@ -48,6 +48,10 @@ export class TableRenderer { return '-'; } + if (_.isString(v)) { + return v; + } + if (style.colorMode) { this.colorState[style.colorMode] = this.getColorForValue(v, style); } diff --git a/public/app/panels/table/specs/renderer_specs.ts b/public/app/panels/table/specs/renderer_specs.ts index 08f63dfe799..fad4f993acf 100644 --- a/public/app/panels/table/specs/renderer_specs.ts +++ b/public/app/panels/table/specs/renderer_specs.ts @@ -50,6 +50,11 @@ describe('when rendering table', () => { expect(html).to.be('1.230 s'); }); + it('number style should ignore string values', () => { + var html = renderer.renderCell(1, 'asd'); + expect(html).to.be('asd'); + }); + it('colored cell should have style', () => { var html = renderer.renderCell(2, 55); expect(html).to.be('55.0'); From 88d27fe649a0d312c57581237b2ed1fcdb015e78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 9 Nov 2015 09:48:02 +0100 Subject: [PATCH 109/394] log(color): disabled console log formating by default --- conf/defaults.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index 69d045dc4ae..8b2b34f3fbc 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -225,7 +225,7 @@ level = Info [log.console] level = # Set formatting to "false" to disable color formatting of console logs -formatting = true +formatting = false # For "file" mode only [log.file] From 9f066d01b1cf368bb71783532046cbfb4d1684a5 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Mon, 9 Nov 2015 03:35:51 -0800 Subject: [PATCH 110/394] Check specified AWS Region and not us-east-1 --- public/app/plugins/datasource/cloudwatch/datasource.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/cloudwatch/datasource.js b/public/app/plugins/datasource/cloudwatch/datasource.js index 576fb9ffcdd..6847d714975 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.js +++ b/public/app/plugins/datasource/cloudwatch/datasource.js @@ -216,7 +216,7 @@ function (angular, _) { CloudWatchDatasource.prototype.testDatasource = function() { /* use billing metrics for test */ - var region = 'us-east-1'; + var region = this.defaultRegion; var namespace = 'AWS/Billing'; var metricName = 'EstimatedCharges'; var dimensions = {}; From d06b9401ea3b47a5970c2bab312b64f7af7fcff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 9 Nov 2015 17:58:02 +0100 Subject: [PATCH 111/394] feat(tablepanel): added support for column sorting --- public/app/panels/table/controller.ts | 19 ++++++- public/app/panels/table/module.html | 6 ++- .../panels/table/specs/table_model_specs.ts | 51 +++++++++++++++++++ .../panels/table/specs/transformers_specs.ts | 5 +- public/app/panels/table/table_model.ts | 25 +++++++++ 5 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 public/app/panels/table/specs/table_model_specs.ts diff --git a/public/app/panels/table/controller.ts b/public/app/panels/table/controller.ts index 7c22ba2823f..463096f097c 100644 --- a/public/app/panels/table/controller.ts +++ b/public/app/panels/table/controller.ts @@ -29,7 +29,8 @@ export class TablePanelCtrl { pageSize: 50, showHeader: true, columns: [], - fields: [] + fields: [], + sort: {col: null, desc: true}, }; $scope.init = function() { @@ -52,6 +53,21 @@ export class TablePanelCtrl { }); }; + $scope.toggleColumnSort = function(col, colIndex) { + if ($scope.panel.sort.col === colIndex) { + if ($scope.panel.sort.desc) { + $scope.panel.sort.desc = false; + } else { + $scope.panel.sort.col = null; + } + } else { + $scope.panel.sort.col = colIndex; + $scope.panel.sort.desc = true; + } + + $scope.render(); + }; + $scope.dataHandler = function(results) { $scope.dataRaw = results.data; $scope.render(); @@ -59,6 +75,7 @@ export class TablePanelCtrl { $scope.render = function() { $scope.table = TableModel.transform($scope.dataRaw, $scope.panel); + $scope.table.sort($scope.panel.sort); panelHelper.broadcastRender($scope, $scope.table, $scope.dataRaw); }; diff --git a/public/app/panels/table/module.html b/public/app/panels/table/module.html index 1ceb7f52647..030324c325c 100644 --- a/public/app/panels/table/module.html +++ b/public/app/panels/table/module.html @@ -7,8 +7,12 @@ -
      +
      {{col.text}} + + + +
      diff --git a/public/app/panels/table/specs/table_model_specs.ts b/public/app/panels/table/specs/table_model_specs.ts new file mode 100644 index 00000000000..ad515835730 --- /dev/null +++ b/public/app/panels/table/specs/table_model_specs.ts @@ -0,0 +1,51 @@ +import {describe, beforeEach, it, sinon, expect} from 'test/lib/common'; + +import {TableModel} from '../table_model'; + +describe('when sorting table desc', () => { + var table; + var panel = { + sort: {col: 0, desc: true}, + }; + + beforeEach(() => { + table = new TableModel(); + table.columns = [{}, {}]; + table.rows = [[100, 12], [105, 10], [103, 11]]; + table.sort(panel.sort); + }); + + it('should sort by time', () => { + expect(table.rows[0][0]).to.be(105); + expect(table.rows[1][0]).to.be(103); + expect(table.rows[2][0]).to.be(100); + }); + + it ('should mark column being sorted', () => { + expect(table.columns[0].sort).to.be(true); + expect(table.columns[0].desc).to.be(true); + }); + +}); + +describe('when sorting table asc', () => { + var table; + var panel = { + sort: {col: 1, desc: false}, + }; + + beforeEach(() => { + table = new TableModel(); + table.columns = [{}, {}]; + table.rows = [[100, 11], [105, 15], [103, 10]]; + table.sort(panel.sort); + }); + + it('should sort by time', () => { + expect(table.rows[0][1]).to.be(10); + expect(table.rows[1][1]).to.be(11); + expect(table.rows[2][1]).to.be(15); + }); + +}); + diff --git a/public/app/panels/table/specs/transformers_specs.ts b/public/app/panels/table/specs/transformers_specs.ts index 597b8913b82..6e002a5d561 100644 --- a/public/app/panels/table/specs/transformers_specs.ts +++ b/public/app/panels/table/specs/transformers_specs.ts @@ -19,7 +19,10 @@ describe('when transforming time series table', () => { ]; describe('timeseries_to_rows', () => { - var panel = {transform: 'timeseries_to_rows'}; + var panel = { + transform: 'timeseries_to_rows', + sort: {col: 0, desc: true}, + }; beforeEach(() => { table = TableModel.transform(timeSeries, panel); diff --git a/public/app/panels/table/table_model.ts b/public/app/panels/table/table_model.ts index 943234bcd89..c881dd82d8f 100644 --- a/public/app/panels/table/table_model.ts +++ b/public/app/panels/table/table_model.ts @@ -9,6 +9,31 @@ export class TableModel { this.rows = []; } + sort(options) { + if (options.col === null || this.columns.length < options.col) { + return; + } + + this.rows.sort(function(a, b) { + a = a[options.col]; + b = b[options.col]; + if (a < b) { + return -1; + } + if (a > b) { + return 1; + } + return 0; + }); + + this.columns[options.col].sort = true; + + if (options.desc) { + this.rows.reverse(); + this.columns[options.col].desc = true; + } + } + static transform(data, panel) { var model = new TableModel(); From 7612e47aee5424bdca82ec7a7d9b63921b5f607d Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Mon, 9 Nov 2015 12:08:40 -0800 Subject: [PATCH 112/394] Select AWS region from dropdown --- pkg/api/cloudwatch/metrics.go | 2 ++ public/app/features/org/datasourceEditCtrl.js | 18 ------------------ .../datasource/cloudwatch/partials/config.html | 9 +++++++-- 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/pkg/api/cloudwatch/metrics.go b/pkg/api/cloudwatch/metrics.go index 6d6ff10dfb9..ab4df6784c7 100644 --- a/pkg/api/cloudwatch/metrics.go +++ b/pkg/api/cloudwatch/metrics.go @@ -65,6 +65,8 @@ func init() { } } +// Whenever this list is updated, frontend list should also be updated. +// Please update the region list in public/app/features/org/datasourceEditCtrl.js func handleGetRegions(req *cwRequest, c *middleware.Context) { regions := []string{ "us-west-2", "us-west-1", "eu-west-1", "eu-central-1", "ap-southeast-1", diff --git a/public/app/features/org/datasourceEditCtrl.js b/public/app/features/org/datasourceEditCtrl.js index 46b8ab11abc..09dd8606092 100644 --- a/public/app/features/org/datasourceEditCtrl.js +++ b/public/app/features/org/datasourceEditCtrl.js @@ -50,34 +50,16 @@ function (angular, config, _) { }); }; - $scope.loadAWSRegions = function() { - var options = { - method: 'POST', - url: '/api/datasources/proxy/' + $scope.current.id, - data: { action: '__GetRegions' } - }; - - return backendSrv.datasourceRequest(options).then(function(result) { - $scope.current.jsonData.allRegions = result.data.map(function(region) { - return region.text; - }); - }); - }; - $scope.getDatasourceById = function(id) { backendSrv.get('/api/datasources/' + id).then(function(ds) { $scope.isNew = false; $scope.current = ds; - console.log($scope.current); $scope.typeChanged(); }); }; $scope.typeChanged = function() { $scope.datasourceMeta = $scope.types[$scope.current.type]; - if ($scope.current.type === 'cloudwatch') { - $scope.loadAWSRegions(); - } }; $scope.updateFrontendSettings = function() { diff --git a/public/app/plugins/datasource/cloudwatch/partials/config.html b/public/app/plugins/datasource/cloudwatch/partials/config.html index 0d7bf81777f..311ca0ae3dc 100644 --- a/public/app/plugins/datasource/cloudwatch/partials/config.html +++ b/public/app/plugins/datasource/cloudwatch/partials/config.html @@ -19,8 +19,13 @@
    • Default RegionSpecify the region, such as for US West (Oregon) use ` us-west-2 ` as the region.
    • -
    • - + +
    • +
    From 6325635fce4159d632ef21c75ba9bf4eb46b3bb4 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Mon, 9 Nov 2015 12:31:35 -0800 Subject: [PATCH 113/394] Corrected the frontend filename --- pkg/api/cloudwatch/metrics.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/api/cloudwatch/metrics.go b/pkg/api/cloudwatch/metrics.go index ab4df6784c7..a5e738a2ae9 100644 --- a/pkg/api/cloudwatch/metrics.go +++ b/pkg/api/cloudwatch/metrics.go @@ -66,7 +66,7 @@ func init() { } // Whenever this list is updated, frontend list should also be updated. -// Please update the region list in public/app/features/org/datasourceEditCtrl.js +// Please update the region list in public/app/plugins/datasource/cloudwatch/partials/config.html func handleGetRegions(req *cwRequest, c *middleware.Context) { regions := []string{ "us-west-2", "us-west-1", "eu-west-1", "eu-central-1", "ap-southeast-1", From 99ee38cea31e29ff911b010f0e3cc346fef46ca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 10 Nov 2015 08:38:34 +0100 Subject: [PATCH 114/394] feat(tablepanel): minor change --- public/app/panels/table/module.html | 2 +- public/app/panels/table/module.ts | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/public/app/panels/table/module.html b/public/app/panels/table/module.html index 030324c325c..75b9b4ce136 100644 --- a/public/app/panels/table/module.html +++ b/public/app/panels/table/module.html @@ -7,7 +7,7 @@ -
    +
    {{col.text}} diff --git a/public/app/panels/table/module.ts b/public/app/panels/table/module.ts index beb6273fd62..9ed800f827c 100644 --- a/public/app/panels/table/module.ts +++ b/public/app/panels/table/module.ts @@ -37,9 +37,14 @@ export function tablePanel() { } function appendPaginationControls(footerElem) { - var paginationList = $('
      '); + footerElem.empty(); - var pageCount = data.rows.length / panel.pageSize; + var pageCount = Math.ceil(data.rows.length / panel.pageSize); + if (pageCount === 1) { + return; + } + + var paginationList = $('
        '); for (var i = 0; i < pageCount; i++) { var pageLinkElem = $('
      • ' + (i+1) + '
      • '); paginationList.append(pageLinkElem); @@ -48,7 +53,6 @@ export function tablePanel() { var nextLink = $('
      • »
      • '); paginationList.append(nextLink); - footerElem.empty(); footerElem.append(paginationList); } From 65bc194c422b10b5a2b6541e438062b82bead796 Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Tue, 10 Nov 2015 19:01:37 +0900 Subject: [PATCH 115/394] fix templating error dialog for Prometheus --- public/app/services/backendSrv.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/public/app/services/backendSrv.js b/public/app/services/backendSrv.js index 819ce9d938e..dcd922b886d 100644 --- a/public/app/services/backendSrv.js +++ b/public/app/services/backendSrv.js @@ -106,6 +106,11 @@ function (angular, _, config) { }); } + // for Prometheus + if (!err.data.message && _.isString(err.data.error)) { + err.data.message = err.data.error; + } + throw err; }); }; From 5339ec66b7dcdbb7fcc78b20088f5ee268d7e24e Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Tue, 10 Nov 2015 02:22:15 -0800 Subject: [PATCH 116/394] added now-3h option --- public/app/core/utils/rangeutil.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/app/core/utils/rangeutil.ts b/public/app/core/utils/rangeutil.ts index 1e64fcc0061..5975ccbd181 100644 --- a/public/app/core/utils/rangeutil.ts +++ b/public/app/core/utils/rangeutil.ts @@ -34,11 +34,12 @@ var rangeOptions = [ { from: 'now-15m', to: 'now', display: 'Last 15 minutes', section: 3 }, { from: 'now-30m', to: 'now', display: 'Last 30 minutes', section: 3 }, { from: 'now-1h', to: 'now', display: 'Last 1 hour', section: 3 }, + { from: 'now-3h', to: 'now', display: 'Last 3 hour', section: 3 }, { from: 'now-6h', to: 'now', display: 'Last 6 hours', section: 3 }, { from: 'now-12h', to: 'now', display: 'Last 12 hours', section: 3 }, { from: 'now-24h', to: 'now', display: 'Last 24 hours', section: 3 }, - { from: 'now-7d', to: 'now', display: 'Last 7 days', section: 3 }, + { from: 'now-7d', to: 'now', display: 'Last 7 days', section: 0 }, { from: 'now-30d', to: 'now', display: 'Last 30 days', section: 0 }, { from: 'now-60d', to: 'now', display: 'Last 60 days', section: 0 }, { from: 'now-90d', to: 'now', display: 'Last 90 days', section: 0 }, From 0c50a7437b19fa13c178c1191c3b813512c297b8 Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Tue, 10 Nov 2015 02:29:02 -0800 Subject: [PATCH 117/394] missed an S at the end --- public/app/core/utils/rangeutil.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/core/utils/rangeutil.ts b/public/app/core/utils/rangeutil.ts index 5975ccbd181..21fea34fe68 100644 --- a/public/app/core/utils/rangeutil.ts +++ b/public/app/core/utils/rangeutil.ts @@ -34,7 +34,7 @@ var rangeOptions = [ { from: 'now-15m', to: 'now', display: 'Last 15 minutes', section: 3 }, { from: 'now-30m', to: 'now', display: 'Last 30 minutes', section: 3 }, { from: 'now-1h', to: 'now', display: 'Last 1 hour', section: 3 }, - { from: 'now-3h', to: 'now', display: 'Last 3 hour', section: 3 }, + { from: 'now-3h', to: 'now', display: 'Last 3 hours', section: 3 }, { from: 'now-6h', to: 'now', display: 'Last 6 hours', section: 3 }, { from: 'now-12h', to: 'now', display: 'Last 12 hours', section: 3 }, { from: 'now-24h', to: 'now', display: 'Last 24 hours', section: 3 }, From 167c02d7731049a20699ba767cc2783a023bc86f Mon Sep 17 00:00:00 2001 From: utkarshcmu Date: Tue, 10 Nov 2015 04:53:42 -0800 Subject: [PATCH 118/394] Timepicker display fixed for now-* --- public/app/core/utils/rangeutil.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/app/core/utils/rangeutil.ts b/public/app/core/utils/rangeutil.ts index 1e64fcc0061..3fc4252ceaf 100644 --- a/public/app/core/utils/rangeutil.ts +++ b/public/app/core/utils/rangeutil.ts @@ -133,6 +133,10 @@ _.each(rangeOptions, function (frame) { return from.fromNow() + ' to ' + formatDate(range.to); } + if (!moment.isMoment(range.from) && !moment.isMoment(range.to)) { + return formatDate(dateMath.parse(range.from, true)) + ' to ' + formatDate(dateMath.parse(range.to, true)); + } + var res = describeTextRange(range.from); return res.display; } From 1bec6c2aae625284e67dd63d1d724021b72816dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 10 Nov 2015 16:15:23 +0100 Subject: [PATCH 119/394] feat(tablepanel): made annotations transform work --- public/app/panels/table/controller.ts | 15 +++++---- public/app/panels/table/renderer.ts | 32 +++++++++---------- .../app/panels/table/specs/renderer_specs.ts | 8 ++++- .../panels/table/specs/transformers_specs.ts | 31 +++++++++++++++++- public/app/panels/table/transformers.ts | 15 +++++++++ 5 files changed, 77 insertions(+), 24 deletions(-) diff --git a/public/app/panels/table/controller.ts b/public/app/panels/table/controller.ts index 463096f097c..cfd2fdf7786 100644 --- a/public/app/panels/table/controller.ts +++ b/public/app/panels/table/controller.ts @@ -9,7 +9,7 @@ import {TableModel} from './table_model'; export class TablePanelCtrl { - constructor($scope, $rootScope, $q, panelSrv, panelHelper) { + constructor($scope, $rootScope, $q, panelSrv, panelHelper, annotationsSrv) { $scope.ctrl = this; $scope.pageIndex = 0; @@ -36,19 +36,22 @@ export class TablePanelCtrl { $scope.init = function() { _.defaults($scope.panel, panelDefaults); - if ($scope.panel.columns.length === 0) { - } - panelSrv.init($scope); }; $scope.refreshData = function(datasource) { panelHelper.updateTimeRange($scope); + if ($scope.panel.transform === 'annotations') { + return annotationsSrv.getAnnotations($scope.dashboard).then(annotations => { + $scope.dataRaw = annotations; + $scope.render(); + }); + } + return panelHelper.issueMetricQuery($scope, datasource) .then($scope.dataHandler, function(err) { - $scope.seriesList = []; - $scope.render([]); + $scope.render(); throw err; }); }; diff --git a/public/app/panels/table/renderer.ts b/public/app/panels/table/renderer.ts index acfef961f07..fae851e54b5 100644 --- a/public/app/panels/table/renderer.ts +++ b/public/app/panels/table/renderer.ts @@ -24,9 +24,22 @@ export class TableRenderer { return null; } + defaultCellFormater(v) { + if (v === null || v === void 0) { + return ''; + } + + if (_.isArray(v)) { + v = v.join(', '); + } + + return v; + } + + createColumnFormater(style) { if (!style) { - return v => v; + return this.defaultCellFormater; } if (style.type === 'date') { @@ -60,17 +73,7 @@ export class TableRenderer { }; } - return v => { - if (v === null || v === void 0) { - return '-'; - } - - if (_.isArray(v)) { - v = v.join(', '); - } - - return v; - }; + return this.defaultCellFormater; } formatColumnValue(colIndex, value) { @@ -88,10 +91,7 @@ export class TableRenderer { } } - this.formaters[colIndex] = function(v) { - return v; - }; - + this.formaters[colIndex] = this.defaultCellFormater; return this.formaters[colIndex](value); } diff --git a/public/app/panels/table/specs/renderer_specs.ts b/public/app/panels/table/specs/renderer_specs.ts index fad4f993acf..43b60c1b364 100644 --- a/public/app/panels/table/specs/renderer_specs.ts +++ b/public/app/panels/table/specs/renderer_specs.ts @@ -9,7 +9,8 @@ describe('when rendering table', () => { table.columns = [ {text: 'Time'}, {text: 'Value'}, - {text: 'Colored'} + {text: 'Colored'}, + {text: 'Undefined'}, ]; var panel = { @@ -59,6 +60,11 @@ describe('when rendering table', () => { var html = renderer.renderCell(2, 55); expect(html).to.be('55.0'); }); + + it('unformated undefined should be rendered as -', () => { + var html = renderer.renderCell(3, undefined); + expect(html).to.be(''); + }); }); }); diff --git a/public/app/panels/table/specs/transformers_specs.ts b/public/app/panels/table/specs/transformers_specs.ts index 6e002a5d561..c41143ab016 100644 --- a/public/app/panels/table/specs/transformers_specs.ts +++ b/public/app/panels/table/specs/transformers_specs.ts @@ -103,8 +103,37 @@ describe('when transforming time series table', () => { expect(table.rows[0][0]).to.be('time'); expect(table.rows[0][1]).to.be('message'); }); - }); + + describe('Annnotations', () => { + var panel = {transform: 'annotations'}; + var rawData = [ + { + min: 1000, + text: 'hej', + tags: ['tags', 'asd'], + title: 'title', + } + ]; + + beforeEach(() => { + table = TableModel.transform(rawData, panel); + }); + + it ('should return 4 columns', () => { + expect(table.columns.length).to.be(4); + expect(table.columns[0].text).to.be('Time'); + expect(table.columns[1].text).to.be('Title'); + expect(table.columns[2].text).to.be('Text'); + expect(table.columns[3].text).to.be('Tags'); + }); + + it ('should return 1 rows', () => { + expect(table.rows.length).to.be(1); + expect(table.rows[0][0]).to.be(1000); + }); + }); + }); }); diff --git a/public/app/panels/table/transformers.ts b/public/app/panels/table/transformers.ts index dce0098dde3..044a20c8c0b 100644 --- a/public/app/panels/table/transformers.ts +++ b/public/app/panels/table/transformers.ts @@ -66,6 +66,21 @@ transformers['timeseries_to_columns'] = { transformers['annotations'] = { description: 'Annotations', + transform: function(data, panel, model) { + model.columns.push({text: 'Time', type: 'date'}); + model.columns.push({text: 'Title'}); + model.columns.push({text: 'Text'}); + model.columns.push({text: 'Tags'}); + + if (!data || data.length === 0) { + return; + } + + for (var i = 0; i < data.length; i++) { + var evt = data[i]; + model.rows.push([evt.min, evt.title, evt.text, evt.tags]); + } + } }; transformers['json'] = { From a7deca1df54282b64d0f97434861c12db2ae8ccc Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Wed, 11 Nov 2015 14:06:47 +0900 Subject: [PATCH 120/394] sort regions by alphabetical order --- pkg/api/cloudwatch/metrics.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/api/cloudwatch/metrics.go b/pkg/api/cloudwatch/metrics.go index 1357fccb127..4dbc86c420d 100644 --- a/pkg/api/cloudwatch/metrics.go +++ b/pkg/api/cloudwatch/metrics.go @@ -69,8 +69,8 @@ func init() { // Please update the region list in public/app/plugins/datasource/cloudwatch/partials/config.html func handleGetRegions(req *cwRequest, c *middleware.Context) { regions := []string{ - "us-east-1", "us-west-2", "us-west-1", "eu-west-1", "eu-central-1", "ap-southeast-1", - "ap-southeast-2", "ap-northeast-1", "sa-east-1", "cn-north-1", + "ap-northeast-1", "ap-southeast-1", "ap-southeast-2", "cn-north-1", + "eu-central-1", "eu-west-1", "sa-east-1", "us-east-1", "us-west-1", "us-west-2", } result := []interface{}{} From 02a37d670cd234b537ef3c46ac0c28f968fbd1b2 Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Wed, 11 Nov 2015 14:15:20 +0900 Subject: [PATCH 121/394] sort namespaces by alphabetical order --- pkg/api/cloudwatch/metrics.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/api/cloudwatch/metrics.go b/pkg/api/cloudwatch/metrics.go index 4dbc86c420d..813dc9d0b89 100644 --- a/pkg/api/cloudwatch/metrics.go +++ b/pkg/api/cloudwatch/metrics.go @@ -2,6 +2,7 @@ package cloudwatch import ( "encoding/json" + "sort" "github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/util" @@ -82,8 +83,14 @@ func handleGetRegions(req *cwRequest, c *middleware.Context) { } func handleGetNamespaces(req *cwRequest, c *middleware.Context) { - result := []interface{}{} + keys := []string{} for key := range metricsMap { + keys = append(keys, key) + } + sort.Sort(sort.StringSlice(keys)) + + result := []interface{}{} + for _, key := range keys { result = append(result, util.DynMap{"text": key, "value": key}) } From d503c5d83d2e93e91926872115ca807de18df1d6 Mon Sep 17 00:00:00 2001 From: woodsaj Date: Wed, 11 Nov 2015 14:30:07 +0800 Subject: [PATCH 122/394] refer to plugins are ExternalPlugin instead of thirdParty --- pkg/api/api.go | 2 +- pkg/api/{thirdparty.go => externalplugin.go} | 23 +++++------ pkg/api/index.go | 28 ++++++------- .../{third_party.go => external_plugin.go} | 18 ++++---- pkg/plugins/plugins.go | 36 ++++++++-------- public/app/controllers/sidemenuCtrl.js | 16 ++++---- .../externalPlugins/example/README.TXT | 3 ++ .../externalPlugins/example/_plugin.json | 41 +++++++++++++++++++ .../raintank/plugin.json | 40 ------------------ public/views/index.html | 12 +++--- 10 files changed, 112 insertions(+), 107 deletions(-) rename pkg/api/{thirdparty.go => externalplugin.go} (67%) rename pkg/models/{third_party.go => external_plugin.go} (52%) create mode 100644 public/app/plugins/externalPlugins/example/README.TXT create mode 100644 public/app/plugins/externalPlugins/example/_plugin.json delete mode 100644 public/app/plugins/thirdPartyIntegration/raintank/plugin.json diff --git a/pkg/api/api.go b/pkg/api/api.go index e9d85dc0c8d..0d783968c57 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -185,7 +185,7 @@ func Register(r *macaron.Macaron) { // rendering r.Get("/render/*", reqSignedIn, RenderToPng) - InitThirdPartyRoutes(r) + InitExternalPluginRoutes(r) r.NotFound(NotFoundHandler) } diff --git a/pkg/api/thirdparty.go b/pkg/api/externalplugin.go similarity index 67% rename from pkg/api/thirdparty.go rename to pkg/api/externalplugin.go index 588c3b40ce7..e1ee5806b9e 100644 --- a/pkg/api/thirdparty.go +++ b/pkg/api/externalplugin.go @@ -3,16 +3,16 @@ package api import ( "encoding/json" "github.com/Unknwon/macaron" + "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/util" - "log" "net/http" "net/http/httputil" "net/url" ) -func InitThirdPartyRoutes(r *macaron.Macaron) { +func InitExternalPluginRoutes(r *macaron.Macaron) { /* // Handle Auth and role requirements if route.ReqSignedIn { @@ -30,34 +30,33 @@ func InitThirdPartyRoutes(r *macaron.Macaron) { } } */ - for _, integration := range plugins.Integrations { - log.Printf("adding routes for integration") - for _, route := range integration.Routes { - log.Printf("adding route %s %s", route.Method, route.Path) - r.Route(util.JoinUrlFragments("/thirdparty/", route.Path), route.Method, ThirdParty(route.Url)) + for _, plugin := range plugins.ExternalPlugins { + log.Info("adding routes for external plugin") + for _, route := range plugin.Settings.Routes { + log.Info("adding route %s /plugins%s", route.Method, route.Path) + r.Route(util.JoinUrlFragments("/plugins/", route.Path), route.Method, ExternalPlugin(route.Url)) } } } -func ThirdParty(routeUrl string) macaron.Handler { +func ExternalPlugin(routeUrl string) macaron.Handler { return func(c *middleware.Context) { path := c.Params("*") //Create a HTTP header with the context in it. ctx, err := json.Marshal(c.SignedInUser) if err != nil { - c.JsonApiErr(500, "Not found", err) + c.JsonApiErr(500, "failed to marshal context to json.", err) return } - log.Printf(string(ctx)) targetUrl, _ := url.Parse(routeUrl) - proxy := NewThirdPartyProxy(string(ctx), path, targetUrl) + proxy := NewExternalPluginProxy(string(ctx), path, targetUrl) proxy.Transport = dataProxyTransport proxy.ServeHTTP(c.RW(), c.Req.Request) } } -func NewThirdPartyProxy(ctx string, proxyPath string, targetUrl *url.URL) *httputil.ReverseProxy { +func NewExternalPluginProxy(ctx string, proxyPath string, targetUrl *url.URL) *httputil.ReverseProxy { director := func(req *http.Request) { req.URL.Scheme = targetUrl.Scheme req.URL.Host = targetUrl.Host diff --git a/pkg/api/index.go b/pkg/api/index.go index 072878a2cfd..e41db285efc 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -52,25 +52,25 @@ func setIndexViewData(c *middleware.Context) error { if setting.GoogleTagManagerId != "" { c.Data["GoogleTagManagerId"] = setting.GoogleTagManagerId } - // This can be loaded from the DB/file to allow 3rdParty integration - thirdPartyJs := make([]string, 0) - thirdPartyCss := make([]string, 0) - thirdPartyMenu := make([]*plugins.ThirdPartyMenuItem, 0) - for _, integration := range plugins.Integrations { - for _, js := range integration.Js { - thirdPartyJs = append(thirdPartyJs, js.Src) + + externalPluginJs := make([]string, 0) + externalPluginCss := make([]string, 0) + externalPluginMenu := make([]*plugins.ExternalPluginMenuItem, 0) + for _, plugin := range plugins.ExternalPlugins { + for _, js := range plugin.Settings.Js { + externalPluginJs = append(externalPluginJs, js.Src) } - for _, css := range integration.Css { - thirdPartyCss = append(thirdPartyCss, css.Href) + for _, css := range plugin.Settings.Css { + externalPluginCss = append(externalPluginCss, css.Href) } - for _, item := range integration.MenuItems { - thirdPartyMenu = append(thirdPartyMenu, item) + for _, item := range plugin.Settings.MenuItems { + externalPluginMenu = append(externalPluginMenu, item) } } - c.Data["ThirdPartyJs"] = thirdPartyJs - c.Data["ThirdPartyCss"] = thirdPartyCss - c.Data["ThirdPartyMenu"] = thirdPartyMenu + c.Data["ExternalPluginJs"] = externalPluginJs + c.Data["ExternalPluginCss"] = externalPluginCss + c.Data["ExternalPluginMenu"] = externalPluginMenu return nil } diff --git a/pkg/models/third_party.go b/pkg/models/external_plugin.go similarity index 52% rename from pkg/models/third_party.go rename to pkg/models/external_plugin.go index 0bce0d86f8f..a5fab329fb1 100644 --- a/pkg/models/third_party.go +++ b/pkg/models/external_plugin.go @@ -1,6 +1,6 @@ package models -type ThirdPartyRoute struct { +type ExternalPluginRoute struct { Path string `json:"path"` Method string `json:"method"` ReqSignedIn bool `json:"req_signed_in"` @@ -9,23 +9,23 @@ type ThirdPartyRoute struct { Url string `json:"url"` } -type ThirdPartyJs struct { +type ExternalPluginJs struct { src string `json:"src"` } -type ThirdPartyMenuItem struct { +type ExternalPluginMenuItem struct { Text string `json:"text"` Icon string `json:"icon"` Href string `json:"href"` } -type ThirdPartyCss struct { +type ExternalPluginCss struct { Href string `json:"href"` } -type ThirdPartyIntegration struct { - Routes []*ThirdPartyRoute `json:"routes"` - Js []*ThirdPartyJs `json:"js"` - Css []*ThirdPartyCss `json:"css"` - MenuItems []*ThirdPartyMenuItem `json:"menu_items"` +type ExternalPluginIntegration struct { + Routes []*ExternalPluginRoute `json:"routes"` + Js []*ExternalPluginJs `json:"js"` + Css []*ExternalPluginCss `json:"css"` + MenuItems []*ExternalPluginMenuItem `json:"menu_items"` } diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index c411a47a977..73c642176bb 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -16,7 +16,7 @@ type PluginMeta struct { Name string `json:"name"` } -type ThirdPartyRoute struct { +type ExternalPluginRoute struct { Path string `json:"path"` Method string `json:"method"` ReqSignedIn bool `json:"req_signed_in"` @@ -25,35 +25,35 @@ type ThirdPartyRoute struct { Url string `json:"url"` } -type ThirdPartyJs struct { +type ExternalPluginJs struct { Src string `json:"src"` } -type ThirdPartyMenuItem struct { +type ExternalPluginMenuItem struct { Text string `json:"text"` Icon string `json:"icon"` Href string `json:"href"` } -type ThirdPartyCss struct { +type ExternalPluginCss struct { Href string `json:"href"` } -type ThirdPartyIntegration struct { - Routes []*ThirdPartyRoute `json:"routes"` - Js []*ThirdPartyJs `json:"js"` - Css []*ThirdPartyCss `json:"css"` - MenuItems []*ThirdPartyMenuItem `json:"menu_items"` +type ExternalPluginSettings struct { + Routes []*ExternalPluginRoute `json:"routes"` + Js []*ExternalPluginJs `json:"js"` + Css []*ExternalPluginCss `json:"css"` + MenuItems []*ExternalPluginMenuItem `json:"menu_items"` } -type ThirdPartyPlugin struct { - PluginType string `json:"pluginType"` - Integration ThirdPartyIntegration `json:"integration"` +type ExternalPlugin struct { + PluginType string `json:"pluginType"` + Settings ExternalPluginSettings `json:"settings"` } var ( - DataSources map[string]interface{} - Integrations []ThirdPartyIntegration + DataSources map[string]interface{} + ExternalPlugins []ExternalPlugin ) type PluginScanner struct { @@ -67,7 +67,7 @@ func Init() { func scan(pluginDir string) error { DataSources = make(map[string]interface{}) - Integrations = make([]ThirdPartyIntegration, 0) + ExternalPlugins = make([]ExternalPlugin, 0) scanner := &PluginScanner{ pluginPath: pluginDir, @@ -131,13 +131,13 @@ func (scanner *PluginScanner) loadPluginJson(path string) error { DataSources[datasourceType.(string)] = pluginJson } - if pluginType == "thirdPartyIntegration" { - p := ThirdPartyPlugin{} + if pluginType == "externalPlugin" { + p := ExternalPlugin{} reader.Seek(0, 0) if err := jsonParser.Decode(&p); err != nil { return err } - Integrations = append(Integrations, p.Integration) + ExternalPlugins = append(ExternalPlugins, p) } return nil diff --git a/public/app/controllers/sidemenuCtrl.js b/public/app/controllers/sidemenuCtrl.js index bd6538b15cb..64fe3763aeb 100644 --- a/public/app/controllers/sidemenuCtrl.js +++ b/public/app/controllers/sidemenuCtrl.js @@ -30,13 +30,15 @@ function (angular, _, $, config) { }); } - if (_.isArray(window.thirdParty.MainLinks)) { - _.forEach(window.thirdParty.MainLinks, function(item) { - $scope.mainLinks.push({ - text: item.text, - icon: item.icon, - href: $scope.getUrl(item.href) - }); + if (_.isArray(window.externalPlugins.MainLinks)) { + _.forEach(window.externalPlugins.MainLinks, function(item) { + if (!item.adminOnly || contextSrv.hasRole('Admin')) { + $scope.mainLinks.push({ + text: item.text, + icon: item.icon, + href: $scope.getUrl(item.href) + }); + } }); } }; diff --git a/public/app/plugins/externalPlugins/example/README.TXT b/public/app/plugins/externalPlugins/example/README.TXT new file mode 100644 index 00000000000..0963375e9fe --- /dev/null +++ b/public/app/plugins/externalPlugins/example/README.TXT @@ -0,0 +1,3 @@ +Example app is available at https://github.com/raintank/grafana-plugin-example + +To use, download the example app from github and run it (requires python Flask). Then rename the "_plugin.json" file in this director to "plugin.json" and restart Grafana. diff --git a/public/app/plugins/externalPlugins/example/_plugin.json b/public/app/plugins/externalPlugins/example/_plugin.json new file mode 100644 index 00000000000..a8469eadd62 --- /dev/null +++ b/public/app/plugins/externalPlugins/example/_plugin.json @@ -0,0 +1,41 @@ +{ + "pluginType": "externalPlugin", + "settings": { + "routes": [ + { + "path": "/example/static/*", + "method": "*", + "req_signed_in": false, + "req_grafana_admin": false, + "req_role": "Admin", + "url": "http://localhost:5000/static" + }, + { + "path": "/example/api/*", + "method": "*", + "req_signed_in": true, + "req_grafana_admin": false, + "req_role": "Admin", + "url": "http://localhost:5000/api" + } + ], + "css": [ + { + "href": "/example/static/css/example.css" + } + ], + "js": [ + { + "src": "/example/static/js/app.js" + } + ], + "menu_items": [ + { + "text": "Example Plugin", + "icon": "fa fa-fw fa-smile-o", + "href": "/example/servers", + "adminOnly": false, + } + ] + } +} diff --git a/public/app/plugins/thirdPartyIntegration/raintank/plugin.json b/public/app/plugins/thirdPartyIntegration/raintank/plugin.json deleted file mode 100644 index 7c1dcb0dcee..00000000000 --- a/public/app/plugins/thirdPartyIntegration/raintank/plugin.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "pluginType": "thirdPartyIntegration", - "integration": { - "routes": [ - { - "path": "/raintank/public/*", - "method": "*", - "req_signed_in": false, - "req_grafana_admin": false, - "req_role": "Admin", - "url": "http://localhost:3001/public" - }, - { - "path": "/raintank/api/*", - "method": "*", - "req_signed_in": true, - "req_grafana_admin": false, - "req_role": "Admin", - "url": "http://localhost:3001/api" - } - ], - "css": [ - { - "href": "/path/to/file.css" - } - ], - "js": [ - { - "src": "/raintank/public/app.js" - } - ], - "menu_items": [ - { - "text": "Menu Text", - "icon": "fa fa-fw fa-database", - "href": "/raintank/test" - } - ] - } -} diff --git a/public/views/index.html b/public/views/index.html index 9998b0d2b68..5dc46550fef 100644 --- a/public/views/index.html +++ b/public/views/index.html @@ -13,8 +13,8 @@ [[else]] [[end]] - [[ range $css := .ThirdPartyCss ]] - + [[ range $css := .ExternalPluginCss ]] + [[ end ]] @@ -56,16 +56,16 @@ settings: [[.Settings]], }; - window.thirdParty = { - MainLinks: [[.ThirdPartyMenu]] + window.externalPlugins = { + MainLinks: [[.ExternalPluginMenu]] }; require(['app/app'], function (app) { app.boot(); }) - [[ range $js := .ThirdPartyJs]] - + [[ range $js := .ExternalPluginJs]] + [[ end ]] [[if .GoogleAnalyticsId]] - [[ range $js := .ExternalPluginJs]] - - [[ end ]] + [[if .GoogleAnalyticsId]] diff --git a/tasks/build_task.js b/tasks/build_task.js index 7773299a06d..54562dfbde1 100644 --- a/tasks/build_task.js +++ b/tasks/build_task.js @@ -13,7 +13,7 @@ module.exports = function(grunt) { 'karma:test', 'phantomjs', 'css', - 'htmlmin:build', + // 'htmlmin:build', 'ngtemplates', 'cssmin:build', 'ngAnnotate:build', @@ -34,8 +34,8 @@ module.exports = function(grunt) { for(var key in summary){ if(summary.hasOwnProperty(key)){ - var orig = key.replace(root, root+'/[[.AppSubUrl]]'); - var revved = summary[key].replace(root, root+'/[[.AppSubUrl]]'); + var orig = key.replace(root, root+'/[[.AppSubUrl]]/public'); + var revved = summary[key].replace(root, root+'/[[.AppSubUrl]]/public'); fixed[orig] = revved; } } diff --git a/tasks/options/concat.js b/tasks/options/concat.js index c15aa8a2d6e..4e6927b306a 100644 --- a/tasks/options/concat.js +++ b/tasks/options/concat.js @@ -27,7 +27,7 @@ module.exports = function(config) { js: { src: [ '<%= tempDir %>/vendor/requirejs/require.js', - '<%= tempDir %>/app/components/require.config.js', + '<%= tempDir %>/app/require_config.js', '<%= tempDir %>/app/app.js', ], dest: '<%= genDir %>/app/app.js' From a1f103576ac96a139a133500d9e4bd21f935e00c Mon Sep 17 00:00:00 2001 From: Greg Look Date: Wed, 2 Dec 2015 15:13:02 -0800 Subject: [PATCH 248/394] Wait for all panels to render in PhantomJS. --- vendor/phantomjs/render.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vendor/phantomjs/render.js b/vendor/phantomjs/render.js index 33fda200cba..5cd442eb338 100644 --- a/vendor/phantomjs/render.js +++ b/vendor/phantomjs/render.js @@ -36,7 +36,8 @@ page.open(params.url, function (status) { var canvas = page.evaluate(function() { var body = angular.element(document.body); // 1 var rootScope = body.scope().$root; - return rootScope.performance.panelsRendered > 0; + var panels = angular.element('div.panel').length; + return rootScope.performance.panelsRendered >= panels; }); if (canvas || tries === 1000) { From 13864853a8f0f0f4917e3acdf37c76deef9d681b Mon Sep 17 00:00:00 2001 From: woodsaj Date: Thu, 3 Dec 2015 12:29:57 +0800 Subject: [PATCH 249/394] support separate css files for light/dark themes. --- pkg/api/dtos/index.go | 7 ++++++- pkg/api/index.go | 2 +- pkg/plugins/models.go | 6 ++++-- public/views/index.html | 10 +++++++--- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/pkg/api/dtos/index.go b/pkg/api/dtos/index.go index 88a694f542a..1314d2d94ac 100644 --- a/pkg/api/dtos/index.go +++ b/pkg/api/dtos/index.go @@ -8,11 +8,16 @@ type IndexViewData struct { GoogleAnalyticsId string GoogleTagManagerId string - PluginCss []string + PluginCss []*PluginCss PluginJs []string MainNavLinks []*NavLink } +type PluginCss struct { + Light string `json:"light"` + Dark string `json:"dark"` +} + type NavLink struct { Text string `json:"text"` Icon string `json:"icon"` diff --git a/pkg/api/index.go b/pkg/api/index.go index efc0fd7a69a..5620d5726cd 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -67,7 +67,7 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) { data.PluginJs = append(data.PluginJs, js.Module) } for _, css := range plugin.Css { - data.PluginCss = append(data.PluginCss, css.Href) + data.PluginCss = append(data.PluginCss, &dtos.PluginCss{Light: css.Light, Dark: css.Dark}) } for _, item := range plugin.MainNavLinks { // only show menu items for the specified roles. diff --git a/pkg/plugins/models.go b/pkg/plugins/models.go index 14a4407adad..8438e96f227 100644 --- a/pkg/plugins/models.go +++ b/pkg/plugins/models.go @@ -37,7 +37,8 @@ type ExternalPluginRoute struct { } type ExternalPluginJs struct { - Module string `json:"module"` + Module string `json:"module"` + Directive string `json:"Directive"` } type ExternalPluginNavLink struct { @@ -48,7 +49,8 @@ type ExternalPluginNavLink struct { } type ExternalPluginCss struct { - Href string `json:"href"` + Light string `json:"light"` + Dark string `json:"dark"` } type ExternalPlugin struct { diff --git a/public/views/index.html b/public/views/index.html index 2932276c62f..73d57bb2d9a 100644 --- a/public/views/index.html +++ b/public/views/index.html @@ -10,13 +10,17 @@ [[if .User.LightTheme]] + [[ range $css := .PluginCss ]] + + [[ end ]] [[else]] + [[ range $css := .PluginCss ]] + + [[ end ]] [[end]] - [[ range $css := .PluginCss ]] - - [[ end ]] + From bd4cb549d6224893e0fb97b665282590b86bbff4 Mon Sep 17 00:00:00 2001 From: woodsaj Date: Thu, 3 Dec 2015 15:52:37 +0800 Subject: [PATCH 250/394] add pluginBundle support A plugnBundle is meta plugin that has a set of dependent plugins to enable. This commit includes a plugin.json for a default "core" bundle that enables all of the shipped panels and datasources. --- pkg/api/datasources.go | 5 ++- pkg/api/frontendsettings.go | 8 +++-- pkg/api/index.go | 5 ++- pkg/models/plugin_bundle.go | 8 +++++ pkg/plugins/models.go | 22 ++++++++++++ pkg/plugins/plugins.go | 61 ++++++++++++++++++++++++++++++---- public/app/plugins/plugin.json | 8 +++++ 7 files changed, 107 insertions(+), 10 deletions(-) create mode 100644 pkg/models/plugin_bundle.go create mode 100644 public/app/plugins/plugin.json diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go index 15a13ee60c7..b2c554de0e2 100644 --- a/pkg/api/datasources.go +++ b/pkg/api/datasources.go @@ -115,8 +115,11 @@ func UpdateDataSource(c *middleware.Context, cmd m.UpdateDataSourceCommand) { func GetDataSourcePlugins(c *middleware.Context) { dsList := make(map[string]interface{}) + //TODO(awoods): query DB for orgPlugins + orgPlugins := map[string]m.PluginBundle{} + enabledPlugins := plugins.GetEnabledPlugins(orgPlugins) - for key, value := range plugins.DataSources { + for key, value := range enabledPlugins.DataSourcePlugins { if !value.BuiltIn { dsList[key] = value } diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 93b08f79eb4..49b679af194 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -29,6 +29,10 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro datasources := make(map[string]interface{}) var defaultDatasource string + //TODO(awoods): query DB to get list of the users plugin preferences. + orgPlugins := map[string]m.PluginBundle{} + enabledPlugins := plugins.GetEnabledPlugins(orgPlugins) + for _, ds := range orgDataSources { url := ds.Url @@ -42,7 +46,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro "url": url, } - meta, exists := plugins.DataSources[ds.Type] + meta, exists := enabledPlugins.DataSourcePlugins[ds.Type] if !exists { log.Error(3, "Could not find plugin definition for data source: %v", ds.Type) continue @@ -107,7 +111,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro } panels := map[string]interface{}{} - for _, panel := range plugins.Panels { + for _, panel := range enabledPlugins.PanelPlugins { panels[panel.Type] = map[string]interface{}{ "module": panel.Module, "name": panel.Name, diff --git a/pkg/api/index.go b/pkg/api/index.go index 5620d5726cd..b9f880faefc 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -62,7 +62,10 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) { }) } - for _, plugin := range plugins.ExternalPlugins { + //TODO(awoods): query DB to get list of the users plugin preferences. + orgPlugins := map[string]m.PluginBundle{} + enabledPlugins := plugins.GetEnabledPlugins(orgPlugins) + for _, plugin := range enabledPlugins.ExternalPlugins { for _, js := range plugin.Js { data.PluginJs = append(data.PluginJs, js.Module) } diff --git a/pkg/models/plugin_bundle.go b/pkg/models/plugin_bundle.go new file mode 100644 index 00000000000..3c126a913ac --- /dev/null +++ b/pkg/models/plugin_bundle.go @@ -0,0 +1,8 @@ +package models + +type PluginBundle struct { + Id int64 + Type string + Org int64 + Enabled bool +} diff --git a/pkg/plugins/models.go b/pkg/plugins/models.go index 8438e96f227..255ea462e67 100644 --- a/pkg/plugins/models.go +++ b/pkg/plugins/models.go @@ -61,3 +61,25 @@ type ExternalPlugin struct { MainNavLinks []*ExternalPluginNavLink `json:"mainNavLinks"` StaticRootConfig *StaticRootConfig `json:"staticRoot"` } + +type PluginBundle struct { + Type string `json:"type"` + Enabled bool `json:"enabled"` + PanelPlugins []string `json:"panelPlugins"` + DatasourcePlugins []string `json:"datasourcePlugins"` + ExternalPlugins []string `json:"externalPlugins"` +} + +type EnabledPlugins struct { + PanelPlugins []*PanelPlugin + DataSourcePlugins map[string]*DataSourcePlugin + ExternalPlugins []*ExternalPlugin +} + +func NewEnabledPlugins() EnabledPlugins { + return EnabledPlugins{ + PanelPlugins: make([]*PanelPlugin, 0), + DataSourcePlugins: make(map[string]*DataSourcePlugin), + ExternalPlugins: make([]*ExternalPlugin, 0), + } +} diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index 16ba1cca7d0..6d45267f8d8 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -9,14 +9,16 @@ import ( "strings" "github.com/grafana/grafana/pkg/log" + "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" ) var ( DataSources map[string]DataSourcePlugin - Panels []PanelPlugin - ExternalPlugins []ExternalPlugin + Panels map[string]PanelPlugin + ExternalPlugins map[string]ExternalPlugin StaticRoutes []*StaticRootConfig + Bundles map[string]PluginBundle ) type PluginScanner struct { @@ -26,9 +28,10 @@ type PluginScanner struct { func Init() error { DataSources = make(map[string]DataSourcePlugin) - ExternalPlugins = make([]ExternalPlugin, 0) + ExternalPlugins = make(map[string]ExternalPlugin) StaticRoutes = make([]*StaticRootConfig, 0) - Panels = make([]PanelPlugin, 0) + Panels = make(map[string]PanelPlugin) + Bundles = make(map[string]PluginBundle) scan(path.Join(setting.StaticRootPath, "app/plugins")) checkExternalPluginPaths() @@ -137,7 +140,7 @@ func (scanner *PluginScanner) loadPluginJson(pluginJsonFilePath string) error { return errors.New("Did not find type property in plugin.json") } - Panels = append(Panels, p) + Panels[p.Type] = p addStaticRoot(p.StaticRootConfig, currentDir) } @@ -147,9 +150,55 @@ func (scanner *PluginScanner) loadPluginJson(pluginJsonFilePath string) error { if err := jsonParser.Decode(&p); err != nil { return err } - ExternalPlugins = append(ExternalPlugins, p) + if p.Type == "" { + return errors.New("Did not find type property in plugin.json") + } + ExternalPlugins[p.Type] = p addStaticRoot(p.StaticRootConfig, currentDir) } + if pluginType == "bundle" { + p := PluginBundle{} + reader.Seek(0, 0) + if err := jsonParser.Decode(&p); err != nil { + return err + } + if p.Type == "" { + return errors.New("Did not find type property in plugin.json") + } + Bundles[p.Type] = p + } + return nil } + +func GetEnabledPlugins(bundles map[string]models.PluginBundle) EnabledPlugins { + enabledPlugins := NewEnabledPlugins() + + for bundleType, bundle := range Bundles { + enabled := bundle.Enabled + // check if the bundle is stored in the DB. + if b, ok := bundles[bundleType]; ok { + enabled = b.Enabled + } + + if enabled { + for _, d := range bundle.DatasourcePlugins { + if ds, ok := DataSources[d]; ok { + enabledPlugins.DataSourcePlugins[d] = &ds + } + } + for _, p := range bundle.PanelPlugins { + if panel, ok := Panels[p]; ok { + enabledPlugins.PanelPlugins = append(enabledPlugins.PanelPlugins, &panel) + } + } + for _, e := range bundle.ExternalPlugins { + if external, ok := ExternalPlugins[e]; ok { + enabledPlugins.ExternalPlugins = append(enabledPlugins.ExternalPlugins, &external) + } + } + } + } + return enabledPlugins +} diff --git a/public/app/plugins/plugin.json b/public/app/plugins/plugin.json new file mode 100644 index 00000000000..bbb637bcf9c --- /dev/null +++ b/public/app/plugins/plugin.json @@ -0,0 +1,8 @@ +{ + "pluginType": "bundle", + "type": "core", + "enabled": true, + "panelPlugins": ["graph", "singlestat", "text", "dashlist"], + "datasourcePlugins": ["grafana", "graphite"], + "externalPlugins": [] +} From 579bc1c2c8319df3c4bf95eca85c546c03fdd3be Mon Sep 17 00:00:00 2001 From: Piotr Popieluch Date: Thu, 3 Dec 2015 09:23:14 +0100 Subject: [PATCH 251/394] Add more info in route logging - Add remote address - Add method - Add protocol - Add response size - Use consistent unit for response time (us) --- pkg/middleware/logger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/middleware/logger.go b/pkg/middleware/logger.go index eb5c7b8dde4..4b8e1f06bae 100644 --- a/pkg/middleware/logger.go +++ b/pkg/middleware/logger.go @@ -32,7 +32,7 @@ func Logger() macaron.Handler { rw := res.(macaron.ResponseWriter) c.Next() - content := fmt.Sprintf("Completed %s %v %s in %v", req.URL.Path, rw.Status(), http.StatusText(rw.Status()), time.Since(start)) + content := fmt.Sprintf("Completed %s \"%s %s %s\" %v %s %d bytes in %dus", c.RemoteAddr(), req.Method, req.URL.Path, req.Proto, rw.Status(), http.StatusText(rw.Status()), rw.Size(), time.Since(start)/time.Microsecond) switch rw.Status() { case 200, 304: From 207c1a20ee2d9093ef77aba2a76e0cc71a17ad10 Mon Sep 17 00:00:00 2001 From: Piotr Popieluch Date: Thu, 3 Dec 2015 11:05:50 +0100 Subject: [PATCH 252/394] router logger, log username taken from cookie --- pkg/middleware/logger.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/middleware/logger.go b/pkg/middleware/logger.go index 4b8e1f06bae..38262de573d 100644 --- a/pkg/middleware/logger.go +++ b/pkg/middleware/logger.go @@ -32,7 +32,12 @@ func Logger() macaron.Handler { rw := res.(macaron.ResponseWriter) c.Next() - content := fmt.Sprintf("Completed %s \"%s %s %s\" %v %s %d bytes in %dus", c.RemoteAddr(), req.Method, req.URL.Path, req.Proto, rw.Status(), http.StatusText(rw.Status()), rw.Size(), time.Since(start)/time.Microsecond) + uname := c.GetCookie(setting.CookieUserName) + if len(uname) == 0 { + uname = "-" + } + + content := fmt.Sprintf("Completed %s %s \"%s %s %s\" %v %s %d bytes in %dus", c.RemoteAddr(), uname, req.Method, req.URL.Path, req.Proto, rw.Status(), http.StatusText(rw.Status()), rw.Size(), time.Since(start)/time.Microsecond) switch rw.Status() { case 200, 304: From efbbb313702dc0f208b2fc8545c5a205983fbd33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 3 Dec 2015 12:03:06 +0100 Subject: [PATCH 253/394] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 99854966938..3886c32a971 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ Replace X.Y.Z by actual version number. cd $GOPATH/src/github.com/grafana/grafana go run build.go setup (only needed once to install godep) godep restore (will pull down all golang lib dependencies in your current GOPATH) -godep go run build.go build +go run build.go build ``` ### Building frontend assets From d6935847b4ebad698684b9428f0982baf8604bf6 Mon Sep 17 00:00:00 2001 From: Alexey Larkov Date: Thu, 3 Dec 2015 17:36:29 +0500 Subject: [PATCH 254/394] Web. Fix double slash --- public/app/plugins/datasource/elasticsearch/datasource.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/datasource/elasticsearch/datasource.js b/public/app/plugins/datasource/elasticsearch/datasource.js index 9c749b9459e..bd1773028fb 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.js +++ b/public/app/plugins/datasource/elasticsearch/datasource.js @@ -94,7 +94,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes var payload = angular.toJson(header) + '\n' + angular.toJson(data) + '\n'; - return this._post('/_msearch', payload).then(function(res) { + return this._post('_msearch', payload).then(function(res) { var list = []; var hits = res.responses[0].hits.hits; @@ -188,7 +188,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes payload = payload.replace(/\$timeTo/g, options.range.to.valueOf()); payload = templateSrv.replace(payload, options.scopedVars); - return this._post('/_msearch', payload).then(function(res) { + return this._post('_msearch', payload).then(function(res) { return new ElasticResponse(sentTargets, res).getTimeSeries(); }); }; From cf1f43dc9de5c66572d16cc89ca7b6e13511ad0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 3 Dec 2015 15:09:39 +0100 Subject: [PATCH 255/394] feat(influxdb): support for table queries, closes #3409, #3219 --- .../app/{panels/table => core}/table_model.ts | 23 +++------- public/app/panels/table/controller.ts | 20 ++++++++- .../app/panels/table/specs/renderer_specs.ts | 2 +- .../panels/table/specs/transformers_specs.ts | 13 +++--- public/app/panels/table/transformers.ts | 40 +++++++++++++++++- .../plugins/datasource/influxdb/datasource.js | 24 ++++++++--- .../datasource/influxdb/influx_query.ts | 2 + .../datasource/influxdb/influx_series.js | 42 ++++++++++++++++++- .../influxdb/partials/query.editor.html | 6 +++ .../plugins/datasource/influxdb/query_ctrl.js | 5 +++ .../influxdb/specs/influx_series_specs.ts | 23 ++++++++++ .../specs => test/core}/table_model_specs.ts | 2 +- 12 files changed, 166 insertions(+), 36 deletions(-) rename public/app/{panels/table => core}/table_model.ts (56%) rename public/{app/panels/table/specs => test/core}/table_model_specs.ts (95%) diff --git a/public/app/panels/table/table_model.ts b/public/app/core/table_model.ts similarity index 56% rename from public/app/panels/table/table_model.ts rename to public/app/core/table_model.ts index 1fa4007e6e3..7eb8d5ad92a 100644 --- a/public/app/panels/table/table_model.ts +++ b/public/app/core/table_model.ts @@ -1,12 +1,13 @@ -import {transformers} from './transformers'; -export class TableModel { +class TableModel { columns: any[]; rows: any[]; + type: string; constructor() { this.columns = []; this.rows = []; + this.type = 'table'; } sort(options) { @@ -33,20 +34,6 @@ export class TableModel { this.columns[options.col].desc = true; } } - - static transform(data, panel) { - var model = new TableModel(); - - if (!data || data.length === 0) { - return model; - } - - var transformer = transformers[panel.transform]; - if (!transformer) { - throw {message: 'Transformer ' + panel.transformer + ' not found'}; - } - - transformer.transform(data, panel, model); - return model; - } } + +export = TableModel; diff --git a/public/app/panels/table/controller.ts b/public/app/panels/table/controller.ts index 09e77108631..270e2f65a3d 100644 --- a/public/app/panels/table/controller.ts +++ b/public/app/panels/table/controller.ts @@ -5,7 +5,7 @@ import _ = require('lodash'); import moment = require('moment'); import PanelMeta = require('app/features/panel/panel_meta'); -import {TableModel} from './table_model'; +import {transformDataToTable} from './transformers'; export class TablePanelCtrl { @@ -104,7 +104,23 @@ export class TablePanelCtrl { }; $scope.render = function() { - $scope.table = TableModel.transform($scope.dataRaw, $scope.panel); + // automatically correct transform mode + // based on data + if ($scope.dataRaw && $scope.dataRaw.length) { + if ($scope.dataRaw[0].type === 'table') { + $scope.panel.transform = 'table'; + } else { + if ($scope.dataRaw[0].type === 'docs') { + $scope.panel.transform = 'json'; + } else { + if ($scope.panel.transform === 'table' || $scope.panel.transform === 'json') { + $scope.panel.transform = 'timeseries_to_rows'; + } + } + } + } + + $scope.table = transformDataToTable($scope.dataRaw, $scope.panel); $scope.table.sort($scope.panel.sort); panelHelper.broadcastRender($scope, $scope.table, $scope.dataRaw); }; diff --git a/public/app/panels/table/specs/renderer_specs.ts b/public/app/panels/table/specs/renderer_specs.ts index f8fdebb9ab0..f8af1baba17 100644 --- a/public/app/panels/table/specs/renderer_specs.ts +++ b/public/app/panels/table/specs/renderer_specs.ts @@ -1,6 +1,6 @@ import {describe, beforeEach, it, sinon, expect} from 'test/lib/common'; -import {TableModel} from '../table_model'; +import TableModel = require('app/core/table_model'); import {TableRenderer} from '../renderer'; describe('when rendering table', () => { diff --git a/public/app/panels/table/specs/transformers_specs.ts b/public/app/panels/table/specs/transformers_specs.ts index bb42b997d33..e3cdf44c8b2 100644 --- a/public/app/panels/table/specs/transformers_specs.ts +++ b/public/app/panels/table/specs/transformers_specs.ts @@ -1,7 +1,6 @@ import {describe, beforeEach, it, sinon, expect} from 'test/lib/common'; -import {TableModel} from '../table_model'; -import {transformers} from '../transformers'; +import {transformers, transformDataToTable} from '../transformers'; describe('when transforming time series table', () => { var table; @@ -26,7 +25,7 @@ describe('when transforming time series table', () => { }; beforeEach(() => { - table = TableModel.transform(timeSeries, panel); + table = transformDataToTable(timeSeries, panel); }); it('should return 3 rows', () => { @@ -51,7 +50,7 @@ describe('when transforming time series table', () => { }; beforeEach(() => { - table = TableModel.transform(timeSeries, panel); + table = transformDataToTable(timeSeries, panel); }); it ('should return 3 columns', () => { @@ -80,7 +79,7 @@ describe('when transforming time series table', () => { }; beforeEach(() => { - table = TableModel.transform(timeSeries, panel); + table = transformDataToTable(timeSeries, panel); }); it('should return 2 rows', () => { @@ -133,7 +132,7 @@ describe('when transforming time series table', () => { describe('transform', function() { beforeEach(() => { - table = TableModel.transform(rawData, panel); + table = transformDataToTable(rawData, panel); }); it ('should return 2 columns', () => { @@ -164,7 +163,7 @@ describe('when transforming time series table', () => { ]; beforeEach(() => { - table = TableModel.transform(rawData, panel); + table = transformDataToTable(rawData, panel); }); it ('should return 4 columns', () => { diff --git a/public/app/panels/table/transformers.ts b/public/app/panels/table/transformers.ts index a4d0d4395c5..843eb83c034 100644 --- a/public/app/panels/table/transformers.ts +++ b/public/app/panels/table/transformers.ts @@ -4,6 +4,7 @@ import moment = require('moment'); import _ = require('lodash'); import flatten = require('app/core/utils/flatten'); import TimeSeries = require('app/core/time_series'); +import TableModel = require('app/core/table_model'); var transformers = {}; @@ -136,6 +137,27 @@ transformers['annotations'] = { } }; +transformers['table'] = { + description: 'Table', + getColumns: function(data) { + if (!data || data.length === 0) { + return []; + } + }, + transform: function(data, panel, model) { + if (!data || data.length === 0) { + return; + } + + if (data[0].type !== 'table') { + throw {message: 'Query result is not in table format, try using another transform.'}; + } + + model.columns = data[0].columns; + model.rows = data[0].rows; + } +}; + transformers['json'] = { description: 'JSON Data', getColumns: function(data) { @@ -197,4 +219,20 @@ transformers['json'] = { } }; -export {transformers} +function transformDataToTable(data, panel) { + var model = new TableModel(); + + if (!data || data.length === 0) { + return model; + } + + var transformer = transformers[panel.transform]; + if (!transformer) { + throw {message: 'Transformer ' + panel.transformer + ' not found'}; + } + + transformer.transform(data, panel, model); + return model; +} + +export {transformers, transformDataToTable} diff --git a/public/app/plugins/datasource/influxdb/datasource.js b/public/app/plugins/datasource/influxdb/datasource.js index 9adabe9a83f..a23937cd74c 100644 --- a/public/app/plugins/datasource/influxdb/datasource.js +++ b/public/app/plugins/datasource/influxdb/datasource.js @@ -53,6 +53,7 @@ function (angular, _, dateMath, InfluxSeries, InfluxQuery) { // replace templated variables allQueries = templateSrv.replace(allQueries, options.scopedVars); + return this._seriesQuery(allQueries).then(function(data) { if (!data || !data.results) { return []; @@ -63,13 +64,26 @@ function (angular, _, dateMath, InfluxSeries, InfluxQuery) { var result = data.results[i]; if (!result || !result.series) { continue; } - var alias = (queryTargets[i] || {}).alias; + var target = queryTargets[i]; + var alias = target.alias; if (alias) { - alias = templateSrv.replace(alias, options.scopedVars); + alias = templateSrv.replace(target.alias, options.scopedVars); } - var targetSeries = new InfluxSeries({ series: data.results[i].series, alias: alias }).getTimeSeries(); - for (y = 0; y < targetSeries.length; y++) { - seriesList.push(targetSeries[y]); + + var influxSeries = new InfluxSeries({ series: data.results[i].series, alias: alias }); + + switch(target.resultFormat) { + case 'table': { + seriesList.push(influxSeries.getTable()); + break; + } + default: { + var timeSeries = influxSeries.getTimeSeries(); + for (y = 0; y < timeSeries.length; y++) { + seriesList.push(timeSeries[y]); + } + break; + } } } diff --git a/public/app/plugins/datasource/influxdb/influx_query.ts b/public/app/plugins/datasource/influxdb/influx_query.ts index 34b86e16930..f50627c7f89 100644 --- a/public/app/plugins/datasource/influxdb/influx_query.ts +++ b/public/app/plugins/datasource/influxdb/influx_query.ts @@ -12,6 +12,8 @@ class InfluxQuery { constructor(target) { this.target = target; + target.dsType = 'influxdb'; + target.resultFormat = target.resultFormat || 'time_series'; target.tags = target.tags || []; target.groupBy = target.groupBy || [ {type: 'time', params: ['$interval']}, diff --git a/public/app/plugins/datasource/influxdb/influx_series.js b/public/app/plugins/datasource/influxdb/influx_series.js index fff3536b5e8..63495ffb9b0 100644 --- a/public/app/plugins/datasource/influxdb/influx_series.js +++ b/public/app/plugins/datasource/influxdb/influx_series.js @@ -1,7 +1,8 @@ define([ 'lodash', + 'app/core/table_model', ], -function (_) { +function (_, TableModel) { 'use strict'; function InfluxSeries(options) { @@ -108,5 +109,44 @@ function (_) { return list; }; + p.getTable = function() { + var table = new TableModel(); + var self = this; + var i, j; + + if (self.series.length === 0) { + return table; + } + + _.each(self.series, function(series, seriesIndex) { + + if (seriesIndex === 0) { + table.columns.push({text: 'Time', type: 'time'}); + _.each(_.keys(series.tags), function(key) { + table.columns.push({text: key}); + }); + for (j = 1; j < series.columns.length; j++) { + table.columns.push({text: series.columns[j]}); + } + } + + if (series.values) { + for (i = 0; i < series.values.length; i++) { + var values = series.values[i]; + if (series.tags) { + for (var key in series.tags) { + if (series.tags.hasOwnProperty(key)) { + values.splice(1, 0, series.tags[key]); + } + } + } + table.rows.push(values); + } + } + }); + + return table; + }; + return InfluxSeries; }); diff --git a/public/app/plugins/datasource/influxdb/partials/query.editor.html b/public/app/plugins/datasource/influxdb/partials/query.editor.html index 0aeb8a44224..2b9f7e1a620 100644 --- a/public/app/plugins/datasource/influxdb/partials/query.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/query.editor.html @@ -103,6 +103,12 @@
      • +
      • + Format as +
      • +
      • + +
      • diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.js b/public/app/plugins/datasource/influxdb/query_ctrl.js index 38f87ecd84e..52b7e7f1b7a 100644 --- a/public/app/plugins/datasource/influxdb/query_ctrl.js +++ b/public/app/plugins/datasource/influxdb/query_ctrl.js @@ -20,6 +20,11 @@ function (angular, _, InfluxQueryBuilder, InfluxQuery, queryPart) { $scope.queryModel = new InfluxQuery($scope.target); $scope.queryBuilder = new InfluxQueryBuilder($scope.target); $scope.groupBySegment = uiSegmentSrv.newPlusButton(); + $scope.resultFormats = [ + {text: 'Time series', value: 'time_series'}, + {text: 'Table', value: 'table'}, + {text: 'JSON field', value: 'json_field'}, + ]; if (!$scope.target.measurement) { $scope.measurementSegment = uiSegmentSrv.newSelectMeasurement(); diff --git a/public/app/plugins/datasource/influxdb/specs/influx_series_specs.ts b/public/app/plugins/datasource/influxdb/specs/influx_series_specs.ts index c8c127ed759..8352e41d99a 100644 --- a/public/app/plugins/datasource/influxdb/specs/influx_series_specs.ts +++ b/public/app/plugins/datasource/influxdb/specs/influx_series_specs.ts @@ -186,5 +186,28 @@ describe('when generating timeseries from influxdb response', function() { }); }); + describe('given table response', function() { + var options = { + alias: '', + series: [ + { + name: 'app.prod.server1.count', + tags: {}, + columns: ['time', 'datacenter', 'value'], + values: [[1431946625000, 'America', 10], [1431946626000, 'EU', 12]] + } + ] + }; + + it('should return table', function() { + var series = new InfluxSeries(options); + var table = series.getTable(); + + expect(table.type).to.be('table'); + expect(table.columns.length).to.be(3); + expect(table.rows[0]).to.eql([1431946625000, 'America', 10]);; + }); + }); + }); diff --git a/public/app/panels/table/specs/table_model_specs.ts b/public/test/core/table_model_specs.ts similarity index 95% rename from public/app/panels/table/specs/table_model_specs.ts rename to public/test/core/table_model_specs.ts index ad515835730..8cdeb04f8d0 100644 --- a/public/app/panels/table/specs/table_model_specs.ts +++ b/public/test/core/table_model_specs.ts @@ -1,6 +1,6 @@ import {describe, beforeEach, it, sinon, expect} from 'test/lib/common'; -import {TableModel} from '../table_model'; +import TableModel = require('app/core/table_model'); describe('when sorting table desc', () => { var table; From d7c7f27207fa6a4682383672d664f4e28361a0f7 Mon Sep 17 00:00:00 2001 From: carl bergquist Date: Thu, 3 Dec 2015 15:29:28 +0100 Subject: [PATCH 256/394] add npm-debug.log to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0ac42cbcb4b..3aa23b45149 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules +npm-debug.log coverage/ .aws-config.json awsconfig From 419251ed3514ca4b322ec1bcbed3416711873522 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 3 Dec 2015 16:32:35 +0100 Subject: [PATCH 257/394] fix(elasticsearch): fixed issue with default state of elasticsearch query, result in error before query controller could set defaults, moved defaults to query builder, also removed raw query mode as it is pretty broken, fixes #3396 --- CHANGELOG.md | 4 +++- .../plugins/datasource/elasticsearch/datasource.js | 4 ++++ .../elasticsearch/partials/query.editor.html | 1 - .../datasource/elasticsearch/query_builder.js | 11 ++++++----- .../plugins/datasource/elasticsearch/query_ctrl.js | 12 +----------- .../elasticsearch/specs/query_builder_specs.ts | 8 -------- 6 files changed, 14 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81b315d6323..4d6fa247b59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,6 @@ * **Elasticsearch**: Support for dynamic daily indices for annotations, closes [#3061](https://github.com/grafana/grafana/issues/3061) * **Graph Panel**: Option to hide series with all zeroes from legend and tooltip, closes [#1381](https://github.com/grafana/grafana/issues/1381), [#3336](https://github.com/grafana/grafana/issues/3336) - ### Bug Fixes * **cloudwatch**: fix for handling of period for long time ranges, fixes [#3086](https://github.com/grafana/grafana/issues/3086) * **dashboard**: fix for collapse row by clicking on row title, fixes [#3065](https://github.com/grafana/grafana/issues/3065) @@ -16,6 +15,9 @@ * **graph**: layout fix for color picker when right side legend was enabled, fixes [#3093](https://github.com/grafana/grafana/issues/3093) * **elasticsearch**: disabling elastic query (via eye) caused error, fixes [#3300](https://github.com/grafana/grafana/issues/3300) +### Breaking changes +* **elasticsearch**: Manual json edited queries are not supported any more (They very barely worked in 2.5) + # 2.5 (2015-10-28) **New Feature: Mix data sources** diff --git a/public/app/plugins/datasource/elasticsearch/datasource.js b/public/app/plugins/datasource/elasticsearch/datasource.js index 9c749b9459e..9307031689c 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.js +++ b/public/app/plugins/datasource/elasticsearch/datasource.js @@ -183,6 +183,10 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes sentTargets.push(target); } + if (sentTargets.length === 0) { + return $q.when([]); + } + payload = payload.replace(/\$interval/g, options.interval); payload = payload.replace(/\$timeFrom/g, options.range.from.valueOf()); payload = payload.replace(/\$timeTo/g, options.range.to.valueOf()); diff --git a/public/app/plugins/datasource/elasticsearch/partials/query.editor.html b/public/app/plugins/datasource/elasticsearch/partials/query.editor.html index dee2401e6f3..d027e1a5c14 100644 --- a/public/app/plugins/datasource/elasticsearch/partials/query.editor.html +++ b/public/app/plugins/datasource/elasticsearch/partials/query.editor.html @@ -14,7 +14,6 @@