From 0a44add6c96cee16f71bf4afec50143b7e313783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 14 Sep 2016 11:20:58 +0200 Subject: [PATCH 01/63] feat(adhoc fiters): began work on ad-hoc filters, refactored submenu filters to use gf-form styles, #6038 --- .../app/features/dashboard/submenu/submenu.html | 17 +++++++++++++---- public/app/features/templating/editorCtrl.js | 1 + .../features/templating/templateValuesSrv.js | 7 ++++++- public/app/partials/valueSelectDropdown.html | 4 ++-- public/sass/components/_submenu.scss | 5 +---- 5 files changed, 23 insertions(+), 11 deletions(-) diff --git a/public/app/features/dashboard/submenu/submenu.html b/public/app/features/dashboard/submenu/submenu.html index 464d8c4cecf..f02b3cfc881 100644 --- a/public/app/features/dashboard/submenu/submenu.html +++ b/public/app/features/dashboard/submenu/submenu.html @@ -1,10 +1,19 @@ - @@ -215,26 +205,26 @@
-
- + + -
-
+ + + + +
-
Options
+
Options
Data source @@ -242,58 +232,58 @@
- + -
-
Selection Options
-
- - - - -
-
- Custom all value - -
-
+
+
Selection Options
+
+ + + + +
+
+ Custom all value + +
+
-
-
Value groups/tags (Experimental feature)
-
- -
-
- Tags query - -
-
-
  • Tag values query
  • - -
    -
    +
    +
    Value groups/tags (Experimental feature)
    +
    + +
    +
    + Tags query + +
    +
    +
  • Tag values query
  • + +
    +
    -
    -
    Preview of values (shows max 20)
    -
    -
    - {{option.text}} -
    -
    -
    - +
    +
    Preview of values (shows max 20)
    +
    +
    + {{option.text}} +
    +
    +
    + -
    - +
    +
    diff --git a/public/app/features/templating/query_variable.ts b/public/app/features/templating/query_variable.ts index 82aaa2e5b8f..5d6492849f4 100644 --- a/public/app/features/templating/query_variable.ts +++ b/public/app/features/templating/query_variable.ts @@ -2,8 +2,8 @@ import _ from 'lodash'; import kbn from 'app/core/utils/kbn'; -import {Variable, containsVariable, assignModelProperties} from './variable'; -import {VariableSrv, variableConstructorMap} from './variable_srv'; +import {Variable, containsVariable, assignModelProperties, variableTypes} from './variable'; +import {VariableSrv} from './variable_srv'; function getNoneOption() { return { text: 'None', value: '', isNone: true }; @@ -37,8 +37,6 @@ export class QueryVariable implements Variable { current: {text: '', value: ''}, }; - supportsMulti = true; - constructor(private model, private datasourceSrv, private templateSrv, private variableSrv, private $q) { // copy model properties to this instance assignModelProperties(this, model, this.defaults); @@ -151,4 +149,9 @@ export class QueryVariable implements Variable { } } -variableConstructorMap['query'] = QueryVariable; +variableTypes['query'] = { + name: 'Query', + ctor: QueryVariable, + description: 'Variable values are fetched from a datasource query', + supportsMulti: true, +}; diff --git a/public/app/features/templating/variable.ts b/public/app/features/templating/variable.ts index b9441b55840..9a478b50840 100644 --- a/public/app/features/templating/variable.ts +++ b/public/app/features/templating/variable.ts @@ -11,6 +11,7 @@ export interface Variable { getModel(); } +export var variableTypes = {}; export function assignModelProperties(target, source, defaults) { _.forEach(defaults, function(value, key) { diff --git a/public/app/features/templating/variable_srv.ts b/public/app/features/templating/variable_srv.ts index f31ce7515e0..d2efdeb971e 100644 --- a/public/app/features/templating/variable_srv.ts +++ b/public/app/features/templating/variable_srv.ts @@ -3,9 +3,7 @@ import angular from 'angular'; import _ from 'lodash'; import coreModule from 'app/core/core_module'; -import {Variable} from './variable'; - -export var variableConstructorMap: any = {}; +import {Variable, variableTypes} from './variable'; export class VariableSrv { dashboard: any; @@ -85,7 +83,7 @@ export class VariableSrv { } createVariableFromModel(model) { - var ctor = variableConstructorMap[model.type]; + var ctor = variableTypes[model.type].ctor; if (!ctor) { throw "Unable to find variable constructor for " + model.type; } From cb522d58cd896b316994c56707215a83acc17c9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 19 Sep 2016 18:41:42 +0200 Subject: [PATCH 22/63] feat(templating): back to be able to continue work on ad hoc filters, #6048 --- .../app/features/templating/adhoc_variable.ts | 52 +++++++++++++++++++ public/app/features/templating/all.ts | 2 + 2 files changed, 54 insertions(+) create mode 100644 public/app/features/templating/adhoc_variable.ts diff --git a/public/app/features/templating/adhoc_variable.ts b/public/app/features/templating/adhoc_variable.ts new file mode 100644 index 00000000000..579b0268efa --- /dev/null +++ b/public/app/features/templating/adhoc_variable.ts @@ -0,0 +1,52 @@ +/// + +import _ from 'lodash'; +import kbn from 'app/core/utils/kbn'; +import {Variable, assignModelProperties, variableTypes} from './variable'; +import {VariableSrv} from './variable_srv'; + +export class AdhocVariable implements Variable { + + defaults = { + type: 'adhoc', + name: '', + label: '', + hide: 0, + datasource: null, + options: [], + current: {}, + tags: {}, + }; + + /** @ngInject **/ + constructor(private model, private timeSrv, private templateSrv, private variableSrv) { + assignModelProperties(this, model, this.defaults); + } + + setValue(option) { + return Promise.resolve(); + } + + getModel() { + assignModelProperties(this.model, this, this.defaults); + return this.model; + } + + updateOptions() { + return Promise.resolve(); + } + + dependsOn(variable) { + return false; + } + + setValueFromUrl(urlValue) { + return Promise.resolve(); + } +} + +variableTypes['adhoc'] = { + name: 'Ad hoc', + ctor: AdhocVariable, + description: 'Ad hoc filters', +}; diff --git a/public/app/features/templating/all.ts b/public/app/features/templating/all.ts index 3b36f18b9b0..7205da52d19 100644 --- a/public/app/features/templating/all.ts +++ b/public/app/features/templating/all.ts @@ -7,6 +7,7 @@ import {QueryVariable} from './query_variable'; import {DatasourceVariable} from './datasource_variable'; import {CustomVariable} from './custom_variable'; import {ConstantVariable} from './constant_variable'; +import {AdhocVariable} from './adhoc_variable'; export { VariableSrv, @@ -15,4 +16,5 @@ export { DatasourceVariable, CustomVariable, ConstantVariable, + AdhocVariable, } From 490141da8252aaf80c6bd0660ee253cecdbbfda1 Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Sat, 19 Mar 2016 23:02:42 +0900 Subject: [PATCH 23/63] (cloudwatch) expand multi select template variable --- .../datasource/cloudwatch/datasource.js | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/public/app/plugins/datasource/cloudwatch/datasource.js b/public/app/plugins/datasource/cloudwatch/datasource.js index d9fc6464491..c98c7e09b2b 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.js +++ b/public/app/plugins/datasource/cloudwatch/datasource.js @@ -23,6 +23,7 @@ function (angular, _, moment, dateMath, CloudWatchAnnotationQuery) { var queries = []; options = angular.copy(options); + options.targets = this.expandTemplateVariable(options.targets); _.each(options.targets, function(target) { if (target.hide || !target.namespace || !target.metricName || _.isEmpty(target.statistics)) { return; @@ -337,6 +338,36 @@ function (angular, _, moment, dateMath, CloudWatchAnnotationQuery) { }); } + this.expandTemplateVariable = function(targets) { + return _.chain(targets) + .map(function(target) { + var dimensionKey = null; + var variableName = null; + _.each(target.dimensions, function(v, k) { + if (templateSrv.variableExists(v)) { + dimensionKey = k; + variableName = v; + } + }); + if (dimensionKey) { + var variable = _.find(templateSrv.variables, function(variable) { + return templateSrv.containsVariable(variableName, variable.name); + }); + return _.chain(variable.options) + .filter(function(v) { + return v.selected; + }) + .map(function(v) { + var t = angular.copy(target); + t.dimensions[dimensionKey] = v.value; + return t; + }).value(); + } else { + return [target]; + } + }).flatten().value(); + }; + this.convertToCloudWatchTime = function(date, roundUp) { if (_.isString(date)) { date = dateMath.parse(date, roundUp); From 540436e9d5f9f068882e952ae65498e09a9748a7 Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Wed, 20 Apr 2016 21:06:53 +0900 Subject: [PATCH 24/63] inject templateSrv --- public/app/plugins/datasource/cloudwatch/datasource.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/datasource/cloudwatch/datasource.js b/public/app/plugins/datasource/cloudwatch/datasource.js index c98c7e09b2b..1a3d990eac6 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.js +++ b/public/app/plugins/datasource/cloudwatch/datasource.js @@ -23,7 +23,7 @@ function (angular, _, moment, dateMath, CloudWatchAnnotationQuery) { var queries = []; options = angular.copy(options); - options.targets = this.expandTemplateVariable(options.targets); + options.targets = this.expandTemplateVariable(options.targets, templateSrv); _.each(options.targets, function(target) { if (target.hide || !target.namespace || !target.metricName || _.isEmpty(target.statistics)) { return; @@ -338,7 +338,7 @@ function (angular, _, moment, dateMath, CloudWatchAnnotationQuery) { }); } - this.expandTemplateVariable = function(targets) { + this.expandTemplateVariable = function(targets, templateSrv) { return _.chain(targets) .map(function(target) { var dimensionKey = null; From 481cc012423c4d667babeb96ef0800b327526fcb Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Tue, 19 Apr 2016 23:27:18 +0900 Subject: [PATCH 25/63] (cloudwatch) add test for expand templater variables --- .../cloudwatch/specs/datasource_specs.ts | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts b/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts index 86e085b3f6f..0b9b3b53fb6 100644 --- a/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts @@ -98,6 +98,38 @@ describe('CloudWatchDatasource', function() { }); ctx.$rootScope.$apply(); }); + + it('should generate the correct targets by expanding template variables', function() { + var templateSrv = { + variables: [ + { + name: 'instance_id', + options: [ + { value: 'i-23456789', selected: false }, + { value: 'i-34567890', selected: true } + ] + } + ], + variableExists: function (e) { return true; }, + containsVariable: function (str, variableName) { return str.indexOf('$' + variableName) !== -1; } + }; + + var targets = [ + { + region: 'us-east-1', + namespace: 'AWS/EC2', + metricName: 'CPUUtilization', + dimensions: { + InstanceId: '$instance_id' + }, + statistics: ['Average'], + period: 300 + } + ]; + + var result = ctx.ds.expandTemplateVariable(targets, templateSrv); + expect(result[0].dimensions.InstanceId).to.be('i-34567890'); + }); }); function describeMetricFindQuery(query, func) { From 07cce5f6bd06a5a5e36b63da39efb2b24b2f5b04 Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Tue, 6 Sep 2016 01:22:39 +0900 Subject: [PATCH 26/63] refactor --- .../datasource/cloudwatch/datasource.js | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/public/app/plugins/datasource/cloudwatch/datasource.js b/public/app/plugins/datasource/cloudwatch/datasource.js index 1a3d990eac6..10bf4e1295c 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.js +++ b/public/app/plugins/datasource/cloudwatch/datasource.js @@ -338,7 +338,20 @@ function (angular, _, moment, dateMath, CloudWatchAnnotationQuery) { }); } + this.getExpandedVariables = function(target, dimensionKey, variable) { + return _.chain(variable.options) + .filter(function(v) { + return v.selected; + }) + .map(function(v) { + var t = angular.copy(target); + t.dimensions[dimensionKey] = v.value; + return t; + }).value(); + }; + this.expandTemplateVariable = function(targets, templateSrv) { + var self = this; return _.chain(targets) .map(function(target) { var dimensionKey = null; @@ -353,15 +366,7 @@ function (angular, _, moment, dateMath, CloudWatchAnnotationQuery) { var variable = _.find(templateSrv.variables, function(variable) { return templateSrv.containsVariable(variableName, variable.name); }); - return _.chain(variable.options) - .filter(function(v) { - return v.selected; - }) - .map(function(v) { - var t = angular.copy(target); - t.dimensions[dimensionKey] = v.value; - return t; - }).value(); + return self.getExpandedVariables(target, dimensionKey, variable); } else { return [target]; } From 71b5007ec7722d3a4ab4e6fb09694a15e6f67146 Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Tue, 6 Sep 2016 01:51:10 +0900 Subject: [PATCH 27/63] refactor --- .../app/plugins/datasource/cloudwatch/datasource.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/public/app/plugins/datasource/cloudwatch/datasource.js b/public/app/plugins/datasource/cloudwatch/datasource.js index 10bf4e1295c..4365c2e2596 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.js +++ b/public/app/plugins/datasource/cloudwatch/datasource.js @@ -354,17 +354,13 @@ function (angular, _, moment, dateMath, CloudWatchAnnotationQuery) { var self = this; return _.chain(targets) .map(function(target) { - var dimensionKey = null; - var variableName = null; - _.each(target.dimensions, function(v, k) { - if (templateSrv.variableExists(v)) { - dimensionKey = k; - variableName = v; - } + var dimensionKey = _.findKey(target.dimensions, function(v) { + return templateSrv.variableExists(v); }); + if (dimensionKey) { var variable = _.find(templateSrv.variables, function(variable) { - return templateSrv.containsVariable(variableName, variable.name); + return templateSrv.containsVariable(target.dimensions[dimensionKey], variable.name); }); return self.getExpandedVariables(target, dimensionKey, variable); } else { From 46866add7e0f9bef50a8a72ea808743730a0dc97 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 20 Sep 2016 08:13:53 +0200 Subject: [PATCH 28/63] docs(changelog): add note about closing #5003 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 505a5f433bb..92f4ddfc586 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * **Graphite**: Add support for groupByNode, closes [#5613](https://github.com/grafana/grafana/pull/5613) * **Influxdb**: Add support for elapsed(), closes [#5827](https://github.com/grafana/grafana/pull/5827) * **OAuth**: Add support for generic oauth, closes [#4718](https://github.com/grafana/grafana/pull/4718) +* **Cloudwatch**: Add support to expand multi select template variable, closes [#5003](https://github.com/grafana/grafana/pull/5003) ### Breaking changes * **SystemD**: Change systemd description, closes [#5971](https://github.com/grafana/grafana/pull/5971) From ec452dd70432fa675866afa0a65e6ebc2766d415 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 20 Sep 2016 08:18:15 +0200 Subject: [PATCH 29/63] docs(conf): remove internal options since its not working atm --- conf/defaults.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index 53a034bfeaa..49329a0a4ac 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -404,7 +404,7 @@ url = https://grafana.net #################################### External image storage ########################## [external_image_storage] -# You can choose between (s3, webdav or internal) +# You can choose between (s3, webdav) provider = s3 [external_image_storage.s3] From d2fb660557bb1bc1d60faa5cee5a401c7659c00e Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 20 Sep 2016 10:10:27 +0200 Subject: [PATCH 30/63] fix(cli): remove unused logging --- pkg/cmd/grafana-cli/services/services.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/cmd/grafana-cli/services/services.go b/pkg/cmd/grafana-cli/services/services.go index ed1a69d50a8..e2ccce8418f 100644 --- a/pkg/cmd/grafana-cli/services/services.go +++ b/pkg/cmd/grafana-cli/services/services.go @@ -141,8 +141,6 @@ func createRequest(repoUrl string, subPaths ...string) ([]byte, error) { req, err := http.NewRequest(http.MethodGet, u.String(), nil) - logger.Info("grafanaVersion ", grafanaVersion) - req.Header.Set("grafana-version", grafanaVersion) req.Header.Set("User-Agent", "grafana "+grafanaVersion) From e926b01185f63c5934b72b1e605e0b3659323150 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 20 Sep 2016 12:23:31 +0300 Subject: [PATCH 31/63] Bug fixes for flexible Y-Min and Y-Max settings (#6066) * fix(flexible_y-min/max): fixed negative values support and added tests for this. * fix(flexible_y-min/max): fixed issue with Y-Min and Y-Max values stored as numbers. Issue: panels with configured Y-Min and Y-Max don't display anything after upgrade. --- public/app/plugins/panel/graph/graph.js | 3 +- .../plugins/panel/graph/specs/graph_specs.ts | 47 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/public/app/plugins/panel/graph/graph.js b/public/app/plugins/panel/graph/graph.js index 69154ffed65..2e7353b6048 100755 --- a/public/app/plugins/panel/graph/graph.js +++ b/public/app/plugins/panel/graph/graph.js @@ -354,7 +354,8 @@ function (angular, $, moment, _, kbn, GraphTooltip, thresholdManExports) { function parseThresholdExpr(expr) { var match, operator, value, precision; - match = expr.match(/\s*([<=>~]*)\W*(\d+(\.\d+)?)/); + expr = String(expr); + match = expr.match(/\s*([<=>~]*)\s*(\-?\d+(\.\d+)?)/); if (match) { operator = match[1]; value = parseFloat(match[2]); diff --git a/public/app/plugins/panel/graph/specs/graph_specs.ts b/public/app/plugins/panel/graph/specs/graph_specs.ts index 10c81b7212d..2065bffb130 100644 --- a/public/app/plugins/panel/graph/specs/graph_specs.ts +++ b/public/app/plugins/panel/graph/specs/graph_specs.ts @@ -312,5 +312,52 @@ describe('grafanaGraph', function() { expect(ctx.plotOptions.yaxes[0].max).to.be(0); }); }); + describe('and negative values used', function() { + ctx.setup(function(ctrl, data) { + ctrl.panel.yaxes[0].min = '-10'; + ctrl.panel.yaxes[0].max = '-13.14'; + data[0] = new TimeSeries({ + datapoints: [[120,10],[160,20]], + alias: 'series1', + }); + }); + + it('should set min and max to negative', function() { + expect(ctx.plotOptions.yaxes[0].min).to.be(-10); + expect(ctx.plotOptions.yaxes[0].max).to.be(-13.14); + }); + }); + }); + graphScenario('when using Y-Min and Y-Max settings stored as number', function(ctx) { + describe('and Y-Min is 0 and Y-Max is 100', function() { + ctx.setup(function(ctrl, data) { + ctrl.panel.yaxes[0].min = 0; + ctrl.panel.yaxes[0].max = 100; + data[0] = new TimeSeries({ + datapoints: [[120,10],[160,20]], + alias: 'series1', + }); + }); + + it('should set min to 0 and max to 100', function() { + expect(ctx.plotOptions.yaxes[0].min).to.be(0); + expect(ctx.plotOptions.yaxes[0].max).to.be(100); + }); + }); + describe('and Y-Min is -100 and Y-Max is -10.5', function() { + ctx.setup(function(ctrl, data) { + ctrl.panel.yaxes[0].min = -100; + ctrl.panel.yaxes[0].max = -10.5; + data[0] = new TimeSeries({ + datapoints: [[120,10],[160,20]], + alias: 'series1', + }); + }); + + it('should set min to -100 and max to -10.5', function() { + expect(ctx.plotOptions.yaxes[0].min).to.be(-100); + expect(ctx.plotOptions.yaxes[0].max).to.be(-10.5); + }); + }); }); }); From 20f7eee8ccf84a44c03c41986817af2f025f6b38 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 20 Sep 2016 12:38:18 +0300 Subject: [PATCH 32/63] fix(graph): increase Y min and max range when series values are the same, (#6082) fixes #6070, #6050. --- public/vendor/flot/jquery.flot.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/public/vendor/flot/jquery.flot.js b/public/vendor/flot/jquery.flot.js index 45d76b440c3..7159bcee30a 100644 --- a/public/vendor/flot/jquery.flot.js +++ b/public/vendor/flot/jquery.flot.js @@ -1663,8 +1663,10 @@ Licensed under the MIT license. delta = max - min; if (delta == 0.0) { - // degenerate case - var widen = max == 0 ? 1 : 0.01; + // Grafana fix: wide Y min and max using increased wideFactor + // when all series values are the same + var wideFactor = 0.25; + var widen = max == 0 ? 1 : max * wideFactor; if (opts.min == null) min -= widen; From 6e429b8575edc2a6ec7c27bb276534676da21bae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 20 Sep 2016 11:57:43 +0200 Subject: [PATCH 33/63] feat(adhoc filters): good progress on ad hoc filters and sync from to url, #6038 --- .../app/features/dashboard/ad_hoc_filters.ts | 36 ++- public/app/features/dashboard/viewStateSrv.js | 20 -- .../app/features/templating/adhoc_variable.ts | 28 ++- .../features/templating/constant_variable.ts | 7 + .../features/templating/custom_variable.ts | 11 +- .../templating/datasource_variable.ts | 7 +- .../features/templating/interval_variable.ts | 7 +- .../app/features/templating/query_variable.ts | 12 +- .../templating/specs/template_srv_specs.ts | 237 ++++++++++++++++++ public/app/features/templating/templateSrv.js | 13 +- public/app/features/templating/variable.ts | 1 + .../app/features/templating/variable_srv.ts | 18 ++ .../plugins/datasource/influxdb/datasource.ts | 6 +- public/test/specs/templateSrv-specs.js | 235 ----------------- 14 files changed, 351 insertions(+), 287 deletions(-) create mode 100644 public/app/features/templating/specs/template_srv_specs.ts delete mode 100644 public/test/specs/templateSrv-specs.js diff --git a/public/app/features/dashboard/ad_hoc_filters.ts b/public/app/features/dashboard/ad_hoc_filters.ts index 426f6162467..f962f8ca2f4 100644 --- a/public/app/features/dashboard/ad_hoc_filters.ts +++ b/public/app/features/dashboard/ad_hoc_filters.ts @@ -21,7 +21,7 @@ export class AdHocFiltersCtrl { if (this.variable.value && !_.isArray(this.variable.value)) { } - for (let tag of this.variable.tags) { + for (let tag of this.variable.filters) { if (this.segments.length > 0) { this.segments.push(this.uiSegmentSrv.newCondition('AND')); } @@ -38,7 +38,11 @@ export class AdHocFiltersCtrl { getOptions(segment, index) { if (segment.type === 'operator') { - return this.$q.when(this.uiSegmentSrv.newOperators(['=', '!=', '<>', '<', '>', '=~', '!~'])); + return this.$q.when(this.uiSegmentSrv.newOperators(['=', '!=', '<', '>', '=~', '!~'])); + } + + if (segment.type === 'condition') { + return this.$q.when([this.uiSegmentSrv.newSegment('AND')]); } return this.datasourceSrv.get(this.variable.datasource).then(ds => { @@ -100,37 +104,45 @@ export class AdHocFiltersCtrl { } updateVariableModel() { - var tags = []; - var tagIndex = -1; - var tagOperator = ""; + var filters = []; + var filterIndex = -1; + var operator = ""; + var hasFakes = false; - this.segments.forEach((segment, index) => { - if (segment.fake) { + this.segments.forEach(segment => { + if (segment.type === 'value' && segment.fake) { + hasFakes = true; return; } switch (segment.type) { case 'key': { - tags.push({key: segment.value}); - tagIndex += 1; + filters.push({key: segment.value}); + filterIndex += 1; break; } case 'value': { - tags[tagIndex].value = segment.value; + filters[filterIndex].value = segment.value; break; } case 'operator': { - tags[tagIndex].operator = segment.value; + filters[filterIndex].operator = segment.value; break; } case 'condition': { + filters[filterIndex].condition = segment.value; break; } } }); + if (hasFakes) { + return; + } + + this.variable.setFilters(filters); + this.$rootScope.$emit('template-variable-value-updated'); this.$rootScope.$broadcast('refresh'); - this.variable.tags = tags; } } diff --git a/public/app/features/dashboard/viewStateSrv.js b/public/app/features/dashboard/viewStateSrv.js index 758d8ab7067..73f0c038c7f 100644 --- a/public/app/features/dashboard/viewStateSrv.js +++ b/public/app/features/dashboard/viewStateSrv.js @@ -34,10 +34,6 @@ function (angular, _, $) { $location.search(urlParams); }); - $scope.onAppEvent('template-variable-value-updated', function() { - self.updateUrlParamsWithCurrentVariables(); - }); - $scope.onAppEvent('$routeUpdate', function() { var urlState = self.getQueryStringState(); if (self.needsSync(urlState)) { @@ -57,22 +53,6 @@ function (angular, _, $) { this.expandRowForPanel(); } - DashboardViewState.prototype.updateUrlParamsWithCurrentVariables = function() { - // update url - var params = $location.search(); - // remove variable params - _.each(params, function(value, key) { - if (key.indexOf('var-') === 0) { - delete params[key]; - } - }); - - // add new values - templateSrv.fillVariableValuesForUrl(params); - // update url - $location.search(params); - }; - DashboardViewState.prototype.expandRowForPanel = function() { if (!this.state.panelId) { return; } diff --git a/public/app/features/templating/adhoc_variable.ts b/public/app/features/templating/adhoc_variable.ts index 579b0268efa..ea942beaef3 100644 --- a/public/app/features/templating/adhoc_variable.ts +++ b/public/app/features/templating/adhoc_variable.ts @@ -6,6 +6,7 @@ import {Variable, assignModelProperties, variableTypes} from './variable'; import {VariableSrv} from './variable_srv'; export class AdhocVariable implements Variable { + filters: any[]; defaults = { type: 'adhoc', @@ -13,9 +14,7 @@ export class AdhocVariable implements Variable { label: '', hide: 0, datasource: null, - options: [], - current: {}, - tags: {}, + filters: [], }; /** @ngInject **/ @@ -41,8 +40,31 @@ export class AdhocVariable implements Variable { } setValueFromUrl(urlValue) { + if (!_.isArray(urlValue)) { + urlValue = [urlValue]; + } + + this.filters = urlValue.map(item => { + var values = item.split('|'); + return { + key: values[0], + operator: values[1], + value: values[2], + }; + }); + return Promise.resolve(); } + + getValueForUrl() { + return this.filters.map(filter => { + return filter.key + '|' + filter.operator + '|' + filter.value; + }); + } + + setFilters(filters: any[]) { + this.filters = filters; + } } variableTypes['adhoc'] = { diff --git a/public/app/features/templating/constant_variable.ts b/public/app/features/templating/constant_variable.ts index f0a659e0c20..9fe2acfdfb2 100644 --- a/public/app/features/templating/constant_variable.ts +++ b/public/app/features/templating/constant_variable.ts @@ -7,6 +7,7 @@ import {VariableSrv} from './variable_srv'; export class ConstantVariable implements Variable { query: string; options: any[]; + current: any; defaults = { type: 'constant', @@ -14,6 +15,7 @@ export class ConstantVariable implements Variable { hide: 2, label: '', query: '', + current: {}, }; /** @ngInject */ @@ -43,6 +45,11 @@ export class ConstantVariable implements Variable { setValueFromUrl(urlValue) { return this.variableSrv.setOptionFromUrl(this, urlValue); } + + getValueForUrl() { + return this.current.value; + } + } variableTypes['constant'] = { diff --git a/public/app/features/templating/custom_variable.ts b/public/app/features/templating/custom_variable.ts index 77dfd57cae8..90ce08cf9e4 100644 --- a/public/app/features/templating/custom_variable.ts +++ b/public/app/features/templating/custom_variable.ts @@ -10,6 +10,7 @@ export class CustomVariable implements Variable { options: any; includeAll: boolean; multi: boolean; + current: any; defaults = { type: 'custom', @@ -17,10 +18,11 @@ export class CustomVariable implements Variable { label: '', hide: 0, options: [], - current: {text: '', value: ''}, + current: {}, query: '', includeAll: false, multi: false, + allValue: null, }; /** @ngInject **/ @@ -61,6 +63,13 @@ export class CustomVariable implements Variable { setValueFromUrl(urlValue) { return this.variableSrv.setOptionFromUrl(this, urlValue); } + + getValueForUrl() { + if (this.current.text === 'All') { + return 'All'; + } + return this.current.value; + } } variableTypes['custom'] = { diff --git a/public/app/features/templating/datasource_variable.ts b/public/app/features/templating/datasource_variable.ts index b23b5e04e42..96776c31163 100644 --- a/public/app/features/templating/datasource_variable.ts +++ b/public/app/features/templating/datasource_variable.ts @@ -9,13 +9,14 @@ export class DatasourceVariable implements Variable { regex: any; query: string; options: any; + current: any; defaults = { type: 'datasource', name: '', hide: 0, label: '', - current: {text: '', value: ''}, + current: {}, regex: '', options: [], query: '', @@ -73,6 +74,10 @@ export class DatasourceVariable implements Variable { setValueFromUrl(urlValue) { return this.variableSrv.setOptionFromUrl(this, urlValue); } + + getValueForUrl() { + return this.current.value; + } } variableTypes['datasource'] = { diff --git a/public/app/features/templating/interval_variable.ts b/public/app/features/templating/interval_variable.ts index 94bb608e7e3..d53e44ae533 100644 --- a/public/app/features/templating/interval_variable.ts +++ b/public/app/features/templating/interval_variable.ts @@ -12,6 +12,7 @@ export class IntervalVariable implements Variable { auto: boolean; query: string; refresh: number; + current: any; defaults = { type: 'interval', @@ -20,7 +21,7 @@ export class IntervalVariable implements Variable { label: '', refresh: 2, options: [], - current: {text: '', value: ''}, + current: {}, query: '1m,10m,30m,1h,6h,12h,1d,7d,14d,30d', auto: false, auto_min: '10s', @@ -75,6 +76,10 @@ export class IntervalVariable implements Variable { this.updateAutoValue(); return this.variableSrv.setOptionFromUrl(this, urlValue); } + + getValueForUrl() { + return this.current.value; + } } variableTypes['interval'] = { diff --git a/public/app/features/templating/query_variable.ts b/public/app/features/templating/query_variable.ts index 5d6492849f4..a9ac6aa02b9 100644 --- a/public/app/features/templating/query_variable.ts +++ b/public/app/features/templating/query_variable.ts @@ -33,8 +33,11 @@ export class QueryVariable implements Variable { name: '', multi: false, includeAll: false, + allValue: null, options: [], - current: {text: '', value: ''}, + current: {}, + tagsQuery: null, + tagValuesQuery: null, }; constructor(private model, private datasourceSrv, private templateSrv, private variableSrv, private $q) { @@ -56,6 +59,13 @@ export class QueryVariable implements Variable { return this.variableSrv.setOptionFromUrl(this, urlValue); } + getValueForUrl() { + if (this.current.text === 'All') { + return 'All'; + } + return this.current.value; + } + updateOptions() { return this.datasourceSrv.get(this.datasource) .then(this.updateOptionsFromMetricFindQuery.bind(this)) diff --git a/public/app/features/templating/specs/template_srv_specs.ts b/public/app/features/templating/specs/template_srv_specs.ts new file mode 100644 index 00000000000..94b1e211293 --- /dev/null +++ b/public/app/features/templating/specs/template_srv_specs.ts @@ -0,0 +1,237 @@ +import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; + +import '../all'; +import {Emitter} from 'app/core/core'; + +describe('templateSrv', function() { + var _templateSrv, _variableSrv; + + beforeEach(angularMocks.module('grafana.core')); + beforeEach(angularMocks.module('grafana.services')); + + beforeEach(angularMocks.inject(function(variableSrv, templateSrv) { + _templateSrv = templateSrv; + _variableSrv = variableSrv; + })); + + function initTemplateSrv(variables) { + _variableSrv.init({ + templating: {list: variables}, + events: new Emitter(), + }); + } + + describe('init', function() { + beforeEach(function() { + initTemplateSrv([{type: 'query', name: 'test', current: {value: 'oogle'}}]); + }); + + it('should initialize template data', function() { + var target = _templateSrv.replace('this.[[test]].filters'); + expect(target).to.be('this.oogle.filters'); + }); + }); + + describe('replace can pass scoped vars', function() { + beforeEach(function() { + initTemplateSrv([{type: 'query', name: 'test', current: {value: 'oogle' }}]); + }); + + it('should replace $test with scoped value', function() { + var target = _templateSrv.replace('this.$test.filters', {'test': {value: 'mupp', text: 'asd'}}); + expect(target).to.be('this.mupp.filters'); + }); + + it('should replace $test with scoped text', function() { + var target = _templateSrv.replaceWithText('this.$test.filters', {'test': {value: 'mupp', text: 'asd'}}); + expect(target).to.be('this.asd.filters'); + }); + }); + + describe('replace can pass multi / all format', function() { + beforeEach(function() { + initTemplateSrv([{type: 'query', name: 'test', current: {value: ['value1', 'value2'] }}]); + }); + + it('should replace $test with globbed value', function() { + var target = _templateSrv.replace('this.$test.filters', {}, 'glob'); + expect(target).to.be('this.{value1,value2}.filters'); + }); + + it('should replace $test with piped value', function() { + var target = _templateSrv.replace('this=$test', {}, 'pipe'); + expect(target).to.be('this=value1|value2'); + }); + + it('should replace $test with piped value', function() { + var target = _templateSrv.replace('this=$test', {}, 'pipe'); + expect(target).to.be('this=value1|value2'); + }); + }); + + describe('variable with all option', function() { + beforeEach(function() { + initTemplateSrv([{ + type: 'query', + name: 'test', + current: {value: '$__all' }, + options: [ + {value: '$__all'}, {value: 'value1'}, {value: 'value2'} + ] + }]); + }); + + it('should replace $test with formatted all value', function() { + var target = _templateSrv.replace('this.$test.filters', {}, 'glob'); + expect(target).to.be('this.{value1,value2}.filters'); + }); + }); + + describe('variable with all option and custom value', function() { + beforeEach(function() { + initTemplateSrv([{ + type: 'query', + name: 'test', + current: {value: '$__all' }, + allValue: '*', + options: [ + {value: 'value1'}, {value: 'value2'} + ] + }]); + }); + + it('should replace $test with formatted all value', function() { + var target = _templateSrv.replace('this.$test.filters', {}, 'glob'); + expect(target).to.be('this.*.filters'); + }); + + it('should not escape custom all value', function() { + var target = _templateSrv.replace('this.$test', {}, 'regex'); + expect(target).to.be('this.*'); + }); + }); + + describe('lucene format', function() { + it('should properly escape $test with lucene escape sequences', function() { + initTemplateSrv([{type: 'query', name: 'test', current: {value: 'value/4' }}]); + var target = _templateSrv.replace('this:$test', {}, 'lucene'); + expect(target).to.be("this:value\\\/4"); + }); + }); + + describe('format variable to string values', function() { + it('single value should return value', function() { + var result = _templateSrv.formatValue('test'); + expect(result).to.be('test'); + }); + + it('multi value and glob format should render glob string', function() { + var result = _templateSrv.formatValue(['test','test2'], 'glob'); + expect(result).to.be('{test,test2}'); + }); + + it('multi value and lucene should render as lucene expr', function() { + var result = _templateSrv.formatValue(['test','test2'], 'lucene'); + expect(result).to.be('("test" OR "test2")'); + }); + + it('multi value and regex format should render regex string', function() { + var result = _templateSrv.formatValue(['test.','test2'], 'regex'); + expect(result).to.be('(test\\.|test2)'); + }); + + it('multi value and pipe should render pipe string', function() { + var result = _templateSrv.formatValue(['test','test2'], 'pipe'); + expect(result).to.be('test|test2'); + }); + + it('slash should be properly escaped in regex format', function() { + var result = _templateSrv.formatValue('Gi3/14', 'regex'); + expect(result).to.be('Gi3\\/14'); + }); + + }); + + describe('can check if variable exists', function() { + beforeEach(function() { + initTemplateSrv([{type: 'query', name: 'test', current: { value: 'oogle' } }]); + }); + + it('should return true if exists', function() { + var result = _templateSrv.variableExists('$test'); + expect(result).to.be(true); + }); + }); + + describe('can hightlight variables in string', function() { + beforeEach(function() { + initTemplateSrv([{type: 'query', name: 'test', current: { value: 'oogle' } }]); + }); + + it('should insert html', function() { + var result = _templateSrv.highlightVariablesAsHtml('$test'); + expect(result).to.be('$test'); + }); + + it('should insert html anywhere in string', function() { + var result = _templateSrv.highlightVariablesAsHtml('this $test ok'); + expect(result).to.be('this $test ok'); + }); + + it('should ignore if variables does not exist', function() { + var result = _templateSrv.highlightVariablesAsHtml('this $google ok'); + expect(result).to.be('this $google ok'); + }); + }); + + describe('updateTemplateData with simple value', function() { + beforeEach(function() { + initTemplateSrv([{type: 'query', name: 'test', current: { value: 'muuuu' } }]); + }); + + it('should set current value and update template data', function() { + var target = _templateSrv.replace('this.[[test]].filters'); + expect(target).to.be('this.muuuu.filters'); + }); + }); + + describe('fillVariableValuesForUrl with multi value', function() { + beforeEach(function() { + initTemplateSrv([{type: 'query', name: 'test', current: { value: ['val1', 'val2'] }}]); + }); + + it('should set multiple url params', function() { + var params = {}; + _templateSrv.fillVariableValuesForUrl(params); + expect(params['var-test']).to.eql(['val1', 'val2']); + }); + }); + + describe('fillVariableValuesForUrl with multi value and scopedVars', function() { + beforeEach(function() { + initTemplateSrv([{type: 'query', name: 'test', current: { value: ['val1', 'val2'] }}]); + }); + + it('should set scoped value as url params', function() { + var params = {}; + _templateSrv.fillVariableValuesForUrl(params, {'test': {value: 'val1'}}); + expect(params['var-test']).to.eql('val1'); + }); + }); + + describe('replaceWithText', function() { + beforeEach(function() { + initTemplateSrv([ + {type: 'query', name: 'server', current: { value: '{asd,asd2}', text: 'All' } }, + {type: 'interval', name: 'period', current: { value: '$__auto_interval', text: 'auto' } } + ]); + _templateSrv.setGrafanaVariable('$__auto_interval', '13m'); + _templateSrv.updateTemplateData(); + }); + + it('should replace with text except for grafanaVariables', function() { + var target = _templateSrv.replaceWithText('Server: $server, period: $period'); + expect(target).to.be('Server: All, period: 13m'); + }); + }); +}); diff --git a/public/app/features/templating/templateSrv.js b/public/app/features/templating/templateSrv.js index 7615aa7da0f..bca814d29a4 100644 --- a/public/app/features/templating/templateSrv.js +++ b/public/app/features/templating/templateSrv.js @@ -180,18 +180,11 @@ function (angular, _, kbn) { this.fillVariableValuesForUrl = function(params, scopedVars) { _.each(this.variables, function(variable) { - var current = variable.current; - var value = current.value; - - if (current.text === 'All') { - value = 'All'; - } - if (scopedVars && scopedVars[variable.name] !== void 0) { - value = scopedVars[variable.name].value; + params['var-' + variable.name] = scopedVars[variable.name].value; + } else { + params['var-' + variable.name] = variable.getValueForUrl(); } - - params['var-' + variable.name] = value; }); }; diff --git a/public/app/features/templating/variable.ts b/public/app/features/templating/variable.ts index 9a478b50840..3e12b65ec16 100644 --- a/public/app/features/templating/variable.ts +++ b/public/app/features/templating/variable.ts @@ -8,6 +8,7 @@ export interface Variable { updateOptions(); dependsOn(variable); setValueFromUrl(urlValue); + getValueForUrl(); getModel(); } diff --git a/public/app/features/templating/variable_srv.ts b/public/app/features/templating/variable_srv.ts index d2efdeb971e..b7013d517f4 100644 --- a/public/app/features/templating/variable_srv.ts +++ b/public/app/features/templating/variable_srv.ts @@ -14,6 +14,7 @@ export class VariableSrv { constructor(private $rootScope, private $q, private $location, private $injector, private templateSrv) { // update time variant variables $rootScope.$on('refresh', this.onDashboardRefresh.bind(this), $rootScope); + $rootScope.$on('template-variable-value-updated', this.updateUrlParamsWithCurrentVariables.bind(this), $rootScope); } init(dashboard) { @@ -210,6 +211,23 @@ export class VariableSrv { this.selectOptionsForCurrentValue(variable); return this.variableUpdated(variable); } + + updateUrlParamsWithCurrentVariables() { + // update url + var params = this.$location.search(); + + // remove variable params + _.each(params, function(value, key) { + if (key.indexOf('var-') === 0) { + delete params[key]; + } + }); + + // add new values + this.templateSrv.fillVariableValuesForUrl(params); + // update url + this.$location.search(params); + } } coreModule.service('variableSrv', VariableSrv); diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index 4bb183474fb..81cffd18364 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -56,9 +56,9 @@ export default class InfluxDatasource { // apply add hoc filters for (let variable of this.templateSrv.variables) { if (variable.type === 'adhoc' && variable.datasource === this.name) { - for (let tag of variable.tags) { - if (tag.key !== undefined && tag.value !== undefined) { - target.tags.push({key: tag.key, value: tag.value, condition: 'AND'}); + for (let filter of variable.filters) { + if (filter.key !== undefined && filter.value !== undefined) { + target.tags.push({key: filter.key, value: filter.value, condition: filter.condition, operator: filter.operator}); } } } diff --git a/public/test/specs/templateSrv-specs.js b/public/test/specs/templateSrv-specs.js deleted file mode 100644 index 3dac01fbdf2..00000000000 --- a/public/test/specs/templateSrv-specs.js +++ /dev/null @@ -1,235 +0,0 @@ -define([ - '../mocks/dashboard-mock', - 'lodash', - 'app/features/templating/templateSrv' -], function(dashboardMock) { - 'use strict'; - - describe('templateSrv', function() { - var _templateSrv; - var _dashboard; - - beforeEach(module('grafana.services')); - beforeEach(module(function() { - _dashboard = dashboardMock.create(); - })); - - beforeEach(inject(function(templateSrv) { - _templateSrv = templateSrv; - })); - - describe('init', function() { - beforeEach(function() { - _templateSrv.init([{ name: 'test', current: { value: 'oogle' } }]); - }); - - it('should initialize template data', function() { - var target = _templateSrv.replace('this.[[test]].filters'); - expect(target).to.be('this.oogle.filters'); - }); - }); - - describe('replace can pass scoped vars', function() { - beforeEach(function() { - _templateSrv.init([{ name: 'test', current: { value: 'oogle' } }]); - }); - - it('should replace $test with scoped value', function() { - var target = _templateSrv.replace('this.$test.filters', {'test': {value: 'mupp', text: 'asd'}}); - expect(target).to.be('this.mupp.filters'); - }); - - it('should replace $test with scoped text', function() { - var target = _templateSrv.replaceWithText('this.$test.filters', {'test': {value: 'mupp', text: 'asd'}}); - expect(target).to.be('this.asd.filters'); - }); - }); - - describe('replace can pass multi / all format', function() { - beforeEach(function() { - _templateSrv.init([{name: 'test', current: {value: ['value1', 'value2'] }}]); - }); - - it('should replace $test with globbed value', function() { - var target = _templateSrv.replace('this.$test.filters', {}, 'glob'); - expect(target).to.be('this.{value1,value2}.filters'); - }); - - it('should replace $test with piped value', function() { - var target = _templateSrv.replace('this=$test', {}, 'pipe'); - expect(target).to.be('this=value1|value2'); - }); - - it('should replace $test with piped value', function() { - var target = _templateSrv.replace('this=$test', {}, 'pipe'); - expect(target).to.be('this=value1|value2'); - }); - }); - - describe('variable with all option', function() { - beforeEach(function() { - _templateSrv.init([{ - name: 'test', - current: {value: '$__all' }, - options: [ - {value: '$__all'}, {value: 'value1'}, {value: 'value2'} - ] - }]); - }); - - it('should replace $test with formatted all value', function() { - var target = _templateSrv.replace('this.$test.filters', {}, 'glob'); - expect(target).to.be('this.{value1,value2}.filters'); - }); - }); - - describe('variable with all option and custom value', function() { - beforeEach(function() { - _templateSrv.init([{ - name: 'test', - current: {value: '$__all' }, - allValue: '*', - options: [ - {value: 'value1'}, {value: 'value2'} - ] - }]); - }); - - it('should replace $test with formatted all value', function() { - var target = _templateSrv.replace('this.$test.filters', {}, 'glob'); - expect(target).to.be('this.*.filters'); - }); - - it('should not escape custom all value', function() { - var target = _templateSrv.replace('this.$test', {}, 'regex'); - expect(target).to.be('this.*'); - }); - }); - - describe('lucene format', function() { - it('should properly escape $test with lucene escape sequences', function() { - _templateSrv.init([{name: 'test', current: {value: 'value/4' }}]); - var target = _templateSrv.replace('this:$test', {}, 'lucene'); - expect(target).to.be("this:value\\\/4"); - }); - }); - - describe('format variable to string values', function() { - it('single value should return value', function() { - var result = _templateSrv.formatValue('test'); - expect(result).to.be('test'); - }); - - it('multi value and glob format should render glob string', function() { - var result = _templateSrv.formatValue(['test','test2'], 'glob'); - expect(result).to.be('{test,test2}'); - }); - - it('multi value and lucene should render as lucene expr', function() { - var result = _templateSrv.formatValue(['test','test2'], 'lucene'); - expect(result).to.be('("test" OR "test2")'); - }); - - it('multi value and regex format should render regex string', function() { - var result = _templateSrv.formatValue(['test.','test2'], 'regex'); - expect(result).to.be('(test\\.|test2)'); - }); - - it('multi value and pipe should render pipe string', function() { - var result = _templateSrv.formatValue(['test','test2'], 'pipe'); - expect(result).to.be('test|test2'); - }); - - it('slash should be properly escaped in regex format', function() { - var result = _templateSrv.formatValue('Gi3/14', 'regex'); - expect(result).to.be('Gi3\\/14'); - }); - - }); - - describe('can check if variable exists', function() { - beforeEach(function() { - _templateSrv.init([{ name: 'test', current: { value: 'oogle' } }]); - }); - - it('should return true if exists', function() { - var result = _templateSrv.variableExists('$test'); - expect(result).to.be(true); - }); - }); - - describe('can hightlight variables in string', function() { - beforeEach(function() { - _templateSrv.init([{ name: 'test', current: { value: 'oogle' } }]); - }); - - it('should insert html', function() { - var result = _templateSrv.highlightVariablesAsHtml('$test'); - expect(result).to.be('$test'); - }); - - it('should insert html anywhere in string', function() { - var result = _templateSrv.highlightVariablesAsHtml('this $test ok'); - expect(result).to.be('this $test ok'); - }); - - it('should ignore if variables does not exist', function() { - var result = _templateSrv.highlightVariablesAsHtml('this $google ok'); - expect(result).to.be('this $google ok'); - }); - }); - - describe('updateTemplateData with simple value', function() { - beforeEach(function() { - _templateSrv.init([{ name: 'test', current: { value: 'muuuu' } }]); - }); - - it('should set current value and update template data', function() { - var target = _templateSrv.replace('this.[[test]].filters'); - expect(target).to.be('this.muuuu.filters'); - }); - }); - - describe('fillVariableValuesForUrl with multi value', function() { - beforeEach(function() { - _templateSrv.init([{ name: 'test', current: { value: ['val1', 'val2'] }}]); - }); - - it('should set multiple url params', function() { - var params = {}; - _templateSrv.fillVariableValuesForUrl(params); - expect(params['var-test']).to.eql(['val1', 'val2']); - }); - }); - - describe('fillVariableValuesForUrl with multi value and scopedVars', function() { - beforeEach(function() { - _templateSrv.init([{ name: 'test', current: { value: ['val1', 'val2'] }}]); - }); - - it('should set multiple url params', function() { - var params = {}; - _templateSrv.fillVariableValuesForUrl(params, {'test': {value: 'val1'}}); - expect(params['var-test']).to.eql('val1'); - }); - }); - - describe('replaceWithText', function() { - beforeEach(function() { - _templateSrv.init([ - { name: 'server', current: { value: '{asd,asd2}', text: 'All' } }, - { name: 'period', current: { value: '$__auto_interval', text: 'auto' } } - ]); - _templateSrv.setGrafanaVariable('$__auto_interval', '13m'); - _templateSrv.updateTemplateData(); - }); - - it('should replace with text except for grafanaVariables', function() { - var target = _templateSrv.replaceWithText('Server: $server, period: $period'); - expect(target).to.be('Server: All, period: 13m'); - }); - }); - - }); - -}); From 159a8bf7346abb9e98219a6724b39099b3e3904f Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 20 Sep 2016 16:29:10 +0300 Subject: [PATCH 34/63] fix(singlestat): update singlestat options tab styles. (#6084) --- .../app/plugins/panel/singlestat/editor.html | 351 +++++++++--------- 1 file changed, 168 insertions(+), 183 deletions(-) diff --git a/public/app/plugins/panel/singlestat/editor.html b/public/app/plugins/panel/singlestat/editor.html index c0c522abac9..a4fd27bd080 100644 --- a/public/app/plugins/panel/singlestat/editor.html +++ b/public/app/plugins/panel/singlestat/editor.html @@ -1,207 +1,192 @@
    -
    -
    -
      -
    • - Big value -
    • -
    • - Prefix -
    • -
    • - -
    • -
    • - Value -
    • -
    • - -
    • -
    • - Postfix -
    • -
    • - -
    • -
    -
    +
    +
    +
    + +
    +
    + + +
    +
    + +
    + +
    +
    +
    + + +
    -
    -
      -
    • - Font size -
    • -
    • - Prefix -
    • -
    • - -
    • -
    • - Value -
    • -
    • - -
    • -
    • - Postfix -
    • -
    • - -
    • -
    -
    +
    +
    + +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    -
    -
      -
    • - Unit -
    • - -
    • Decimals
    • -
    • - -
    • -
    -
    +
    +
    +
    + + +
    -
    -
    -
      -
    • - Coloring -
    • -
    • - Background  - - -
    • -
    • - Value  - - -
    • -
    • - ThresholdsDefine two threshold values<br /> 50,80 will produce: <50 = Green, 50:80 = Yellow, >80 = Red -
    • -
    • - -
    • -
    • - Colors -
    • -
    • - - - -
    • -
    • - invert order -
    • -
    -
    +
    Coloring
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + + + + +
    + +
    +
    -
    -
    -
      -
    • - Spark lines -
    • -
    • - Show  - - -
    • -
    • - Background mode  - - -
    • -
    • - Line Color -
    • -
    • - -
    • -
    • - Fill Color -
    • -
    • - -
    • -
    -
    +
    Spark lines
    +
    +
    + + +
    + + + + +
    +
    + + + + +
    -
    -
    -
      -
    • - Gauge -
    • -
    • - Show  - - -
    • -
    • - Min -
    • -
    • - -
    • -
    • - Max -
    • -
    • - - -   - Min value is bigger than max. -   - -
    • -
    -
    +
    Gauge
    +
    +
    + +
    + + +
    +
    + + +
    +
    + +
    -
    -
  • - Threshold labels  - - -
  • -
  • - Threshold markers  - - -
  • -
    +
    + +
    From dfe0f911053705e394457bab2919f659eada7021 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 20 Sep 2016 16:29:48 +0200 Subject: [PATCH 35/63] feat(adhoc): adhoc filters progress --- .../app/features/templating/adhoc_variable.ts | 6 +-- .../templating/specs/adhoc_variable_specs.ts | 40 +++++++++++++++++++ public/app/features/templating/templateSrv.js | 18 +++++++++ .../plugins/datasource/influxdb/datasource.ts | 33 +++++++-------- .../datasource/influxdb/influx_query.ts | 7 ++++ .../influxdb/specs/influx_query_specs.ts | 13 ++++++ 6 files changed, 96 insertions(+), 21 deletions(-) create mode 100644 public/app/features/templating/specs/adhoc_variable_specs.ts diff --git a/public/app/features/templating/adhoc_variable.ts b/public/app/features/templating/adhoc_variable.ts index ea942beaef3..5ebffb0b0e6 100644 --- a/public/app/features/templating/adhoc_variable.ts +++ b/public/app/features/templating/adhoc_variable.ts @@ -18,7 +18,7 @@ export class AdhocVariable implements Variable { }; /** @ngInject **/ - constructor(private model, private timeSrv, private templateSrv, private variableSrv) { + constructor(private model) { assignModelProperties(this, model, this.defaults); } @@ -68,7 +68,7 @@ export class AdhocVariable implements Variable { } variableTypes['adhoc'] = { - name: 'Ad hoc', + name: 'Ad hoc filters', ctor: AdhocVariable, - description: 'Ad hoc filters', + description: 'Add key/value filters on the fly', }; diff --git a/public/app/features/templating/specs/adhoc_variable_specs.ts b/public/app/features/templating/specs/adhoc_variable_specs.ts new file mode 100644 index 00000000000..15856940540 --- /dev/null +++ b/public/app/features/templating/specs/adhoc_variable_specs.ts @@ -0,0 +1,40 @@ +import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; + +import {AdhocVariable} from '../adhoc_variable'; + +describe('AdhocVariable', function() { + + describe('when serializing to url', function() { + + it('should set return key value and op seperated by pipe', function() { + var variable = new AdhocVariable({ + filters: [ + {key: 'key1', operator: '=', value: 'value1'}, + {key: 'key2', operator: '!=', value: 'value2'}, + ] + }); + var urlValue = variable.getValueForUrl(); + expect(urlValue).to.eql(["key1|=|value1", "key2|!=|value2"]); + }); + + }); + + describe('when deserializing from url', function() { + + it('should restore filters', function() { + var variable = new AdhocVariable({}); + variable.setValueFromUrl(["key1|=|value1", "key2|!=|value2"]); + + expect(variable.filters[0].key).to.be('key1'); + expect(variable.filters[0].operator).to.be('='); + expect(variable.filters[0].value).to.be('value1'); + + expect(variable.filters[1].key).to.be('key2'); + expect(variable.filters[1].operator).to.be('!='); + expect(variable.filters[1].value).to.be('value2'); + }); + + }); + +}); + diff --git a/public/app/features/templating/templateSrv.js b/public/app/features/templating/templateSrv.js index bca814d29a4..e5ef5a0f6b7 100644 --- a/public/app/features/templating/templateSrv.js +++ b/public/app/features/templating/templateSrv.js @@ -15,6 +15,7 @@ function (angular, _, kbn) { this._index = {}; this._texts = {}; this._grafanaVariables = {}; + this._adhocVariables = {}; this.init = function(variables) { this.variables = variables; @@ -23,16 +24,33 @@ function (angular, _, kbn) { this.updateTemplateData = function() { this._index = {}; + this._filters = {}; for (var i = 0; i < this.variables.length; i++) { var variable = this.variables[i]; + + // add adhoc filters to it's own index + if (variable.type === 'adhoc') { + this._adhocVariables[variable.datasource] = variable; + continue; + } + if (!variable.current || !variable.current.isNone && !variable.current.value) { continue; } + this._index[variable.name] = variable; } }; + this.getAdhocFilters = function(datasourceName) { + var variable = this._adhocVariables[datasourceName]; + if (variable) { + return variable.filters || []; + } + return [] + }; + function luceneEscape(value) { return value.replace(/([\!\*\+\-\=<>\s\&\|\(\)\[\]\{\}\^\~\?\:\\/"])/g, "\\$1"); } diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index 81cffd18364..dd35f42f02e 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -44,36 +44,23 @@ export default class InfluxDatasource { query(options) { var timeFilter = this.getTimeFilter(options); - var scopedVars = _.extend({}, options.scopedVars); + var scopedVars = options.scopedVars ? _.cloneDeep(options.scopedVars) : {}; var targets = _.cloneDeep(options.targets); var queryTargets = []; + var queryModel; var i, y; var allQueries = _.map(targets, target => { if (target.hide) { return ""; } - if (!target.rawQuery) { - // apply add hoc filters - for (let variable of this.templateSrv.variables) { - if (variable.type === 'adhoc' && variable.datasource === this.name) { - for (let filter of variable.filters) { - if (filter.key !== undefined && filter.value !== undefined) { - target.tags.push({key: filter.key, value: filter.value, condition: filter.condition, operator: filter.operator}); - } - } - } - } - } - queryTargets.push(target); // build query scopedVars.interval = {value: target.interval || options.interval}; - var queryModel = new InfluxQuery(target, this.templateSrv, scopedVars); - var query = queryModel.render(true); + queryModel = new InfluxQuery(target, this.templateSrv, scopedVars); + return queryModel.render(true); - return query; }).reduce((acc, current) => { if (current !== "") { acc += ";" + current; @@ -81,6 +68,16 @@ export default class InfluxDatasource { return acc; }); + if (allQueries === '') { + return this.$q.when({data: []}); + } + + // add global adhoc filters to timeFilter + var adhocFilters = this.templateSrv.getAdhocFilters(this.name); + if (adhocFilters.length > 0 ) { + timeFilter += ' AND ' + queryModel.renderAdhocFilters(adhocFilters); + } + // replace grafana variables scopedVars.timeFilter = {value: timeFilter}; @@ -120,7 +117,7 @@ export default class InfluxDatasource { } } - return { data: seriesList }; + return {data: seriesList}; }); }; diff --git a/public/app/plugins/datasource/influxdb/influx_query.ts b/public/app/plugins/datasource/influxdb/influx_query.ts index fddb452767a..2f03f37a0a1 100644 --- a/public/app/plugins/datasource/influxdb/influx_query.ts +++ b/public/app/plugins/datasource/influxdb/influx_query.ts @@ -251,4 +251,11 @@ export default class InfluxQuery { return query; } + + renderAdhocFilters(filters) { + var conditions = _.map(filters, (tag, index) => { + return this.renderTagCondition(tag, index, false); + }); + return conditions.join(' '); + } } diff --git a/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts b/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts index 557c626a7cc..52beed1d080 100644 --- a/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts +++ b/public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts @@ -237,6 +237,19 @@ describe('InfluxQuery', function() { expect(query.target.select[0][2].type).to.be('math'); }); + describe('when render adhoc filters', function() { + it('should generate correct query segment', function() { + var query = new InfluxQuery({measurement: 'cpu', }, templateSrv, {}); + + var queryText = query.renderAdhocFilters([ + {key: 'key1', operator: '=', value: 'value1'}, + {key: 'key2', operator: '!=', value: 'value2'}, + ]); + + expect(queryText).to.be('"key1" = \'value1\' AND "key2" != \'value2\''); + }); + }); + }); }); From 15423e6e51313e071ac64df8a313f813e397c610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 20 Sep 2016 17:34:38 +0200 Subject: [PATCH 36/63] feat(adhoc filters): initial base mvp for adhoc filters are donecloses #6038 --- public/app/features/templating/editor_ctrl.ts | 22 ++++++-- .../features/templating/partials/editor.html | 51 ++++++++++--------- .../plugins/datasource/influxdb/datasource.ts | 2 + 3 files changed, 47 insertions(+), 28 deletions(-) diff --git a/public/app/features/templating/editor_ctrl.ts b/public/app/features/templating/editor_ctrl.ts index 25ddec62cce..81bf2d4d71c 100644 --- a/public/app/features/templating/editor_ctrl.ts +++ b/public/app/features/templating/editor_ctrl.ts @@ -9,6 +9,7 @@ export class VariableEditorCtrl { /** @ngInject */ constructor(private $scope, private datasourceSrv, private variableSrv, templateSrv) { $scope.variableTypes = variableTypes; + $scope.ctrl = {}; $scope.refreshOptions = [ {value: 0, text: "Never"}, @@ -60,9 +61,8 @@ export class VariableEditorCtrl { }; $scope.isValid = function() { - if (!$scope.current.name) { - $scope.appEvent('alert-warning', ['Validation', 'Template variable requires a name']); - return false; + if (!$scope.ctrl.form.$valid) { + return; } if (!$scope.current.name.match(/^\w+$/)) { @@ -79,6 +79,18 @@ export class VariableEditorCtrl { return true; }; + $scope.validate = function() { + $scope.infoText = ''; + if ($scope.current.type === 'adhoc' && $scope.current.datasource !== null) { + $scope.infoText = 'Adhoc filters are applied automatically to all queries that target this datasource'; + datasourceSrv.get($scope.current.datasource).then(ds => { + if (!ds.supportAdhocFilters) { + $scope.infoText = 'This datasource does not support adhoc filters yet.'; + } + }); + } + }; + $scope.runQuery = function() { return variableSrv.updateOptions($scope.current).then(null, function(err) { if (err.data && err.data.message) { err.message = err.data.message; } @@ -90,6 +102,7 @@ export class VariableEditorCtrl { $scope.current = variable; $scope.currentIsNew = false; $scope.mode = 'edit'; + $scope.validate(); }; $scope.duplicate = function(variable) { @@ -126,6 +139,8 @@ export class VariableEditorCtrl { if (oldIndex !== -1) { this.variables[oldIndex] = $scope.current; } + + $scope.validate(); }; $scope.removeVariable = function(variable) { @@ -133,7 +148,6 @@ export class VariableEditorCtrl { $scope.variables.splice(index, 1); $scope.updateSubmenuVisibility(); }; - } } diff --git a/public/app/features/templating/partials/editor.html b/public/app/features/templating/partials/editor.html index c74245ae6be..e485072eed0 100644 --- a/public/app/features/templating/partials/editor.html +++ b/public/app/features/templating/partials/editor.html @@ -70,13 +70,13 @@
    -
    +
    Variable
    Name - +
    @@ -102,15 +102,14 @@
    -
    -
    +
    Interval Options
    Values - +
    Auto option @@ -134,15 +133,15 @@
    -
    +
    Custom Options
    Values separated by comma - +
    -
    +
    Constant options
    Value @@ -150,14 +149,14 @@
    -
    +
    Query Options
    Data source
    - +
    @@ -174,7 +173,7 @@
    Query - +
    @@ -223,19 +222,18 @@
    -
    +
    Options
    -
    Data source
    - +
    -
    +
    -
    -
    Selection Options
    +
    +
    Selection Options
    -
    +
    Preview of values (shows max 20)
    @@ -280,12 +278,17 @@
    -
    -
    - - -
    -
    +
    + {{infoText}} +
    + +
    + + +
    + + +
    diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index dd35f42f02e..4c707dd59d1 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -21,6 +21,7 @@ export default class InfluxDatasource { interval: any; supportAnnotations: boolean; supportMetrics: boolean; + supportAdhocFilters: boolean; responseParser: any; /** @ngInject */ @@ -39,6 +40,7 @@ export default class InfluxDatasource { this.interval = (instanceSettings.jsonData || {}).timeInterval; this.supportAnnotations = true; this.supportMetrics = true; + this.supportAdhocFilters = true; this.responseParser = new ResponseParser(); } From 1b02632de83c8cf04e3be40f52d980c1baa0ba75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 20 Sep 2016 17:36:20 +0200 Subject: [PATCH 37/63] fix(jslint): fixed lint issue --- public/app/features/templating/templateSrv.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/templating/templateSrv.js b/public/app/features/templating/templateSrv.js index e5ef5a0f6b7..f7784e2cb50 100644 --- a/public/app/features/templating/templateSrv.js +++ b/public/app/features/templating/templateSrv.js @@ -48,7 +48,7 @@ function (angular, _, kbn) { if (variable) { return variable.filters || []; } - return [] + return []; }; function luceneEscape(value) { From 392976981c836ca5c697910f1d59aebc5774e5b7 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 21 Sep 2016 08:40:12 +0300 Subject: [PATCH 38/63] fix(table panel): update Table Panel options tab styles. (#6087) --- public/app/plugins/panel/table/editor.html | 294 ++++++++++----------- 1 file changed, 145 insertions(+), 149 deletions(-) diff --git a/public/app/plugins/panel/table/editor.html b/public/app/plugins/panel/table/editor.html index d5565aa7ba1..f2f0f3e7394 100644 --- a/public/app/plugins/panel/table/editor.html +++ b/public/app/plugins/panel/table/editor.html @@ -1,173 +1,169 @@
    -
    -
    Data
    -
    -
    -
      -
    • - To Table Transform -
    • -
    • - -
    • -
    -
    +
    -
    -
      -
    • - Columns -
    • -
    • - - - {{column.text}} - -
    • -
    • - -
    • -
    -
    +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    -
    -
    Table Display
    -
    -
    -
      -
    • - Pagination (Page size) -
    • -
    • - -
    • -
    • - -
    • -
    • - Font size -
    • -
    • - -
    • -
    -
    +
    +
    Table Display
    +
    +
    + + +
    + +
    + +
    + +
    -
    -
    Column Styles
    - -
    +
    +
    +
    Column Styles
    -
    -
      -
    • - -
    • -
    - -
      -
    • - Name or regex -
    • -
    • - -
    • -
    • - Type -
    • -
    • - -
    • -
    -
      -
    • - Format -
    • -
    • - -
    • -
    -
      -
    • - -
    • -
    -
    +
    +
    + + +
    +
    + +
    + +
    +
    +
    + + +
    + +
    +
    +
    +
    + +
    -
    -
      -
    • - Coloring -
    • -
    • - -
    • -
    • - ThresholdsComma separated values -
    • -
    • - -
    • -
    • - Colors -
    • -
    • - - - -
    • -
    • +
      +
      + +
      + +
      +
      +
      + + +
      +
      + + + + + + + + + + + invert order -
    • -
    -
    + +
    +
    -
    -
      -
    • - Unit -
    • - -
    • - Decimals -
    • -
    • - -
    • -
    -
    +
    +
    +
    + + +
    +
    +
    +
    -
    - - +
    + +
    - From a01836e86d20d2012b03bfe722a16a85bdc9d33d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 21 Sep 2016 08:46:59 +0200 Subject: [PATCH 39/63] feat(adhoc filters): basic implementation for ad hoc filters for elasticsearch, #6038 --- public/app/features/templating/editor_ctrl.ts | 2 +- .../datasource/elasticsearch/datasource.js | 15 ++++++++++++-- .../datasource/elasticsearch/query_builder.js | 20 ++++++++++++++++++- .../specs/query_builder_specs.ts | 12 +++++++++++ .../plugins/datasource/influxdb/datasource.ts | 3 +-- public/test/specs/helpers.js | 1 + 6 files changed, 47 insertions(+), 6 deletions(-) diff --git a/public/app/features/templating/editor_ctrl.ts b/public/app/features/templating/editor_ctrl.ts index 81bf2d4d71c..3c0410e1316 100644 --- a/public/app/features/templating/editor_ctrl.ts +++ b/public/app/features/templating/editor_ctrl.ts @@ -84,7 +84,7 @@ export class VariableEditorCtrl { if ($scope.current.type === 'adhoc' && $scope.current.datasource !== null) { $scope.infoText = 'Adhoc filters are applied automatically to all queries that target this datasource'; datasourceSrv.get($scope.current.datasource).then(ds => { - if (!ds.supportAdhocFilters) { + if (!ds.getTagKeys) { $scope.infoText = 'This datasource does not support adhoc filters yet.'; } }); diff --git a/public/app/plugins/datasource/elasticsearch/datasource.js b/public/app/plugins/datasource/elasticsearch/datasource.js index 8cfe7d6129a..0889c078082 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.js +++ b/public/app/plugins/datasource/elasticsearch/datasource.js @@ -177,11 +177,14 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes var target; var sentTargets = []; + // add global adhoc filters to timeFilter + var adhocFilters = templateSrv.getAdhocFilters(this.name); + for (var i = 0; i < options.targets.length; i++) { target = options.targets[i]; if (target.hide) {continue;} - var queryObj = this.queryBuilder.build(target); + var queryObj = this.queryBuilder.build(target, adhocFilters); var esQuery = angular.toJson(queryObj); var luceneQuery = target.query || '*'; luceneQuery = templateSrv.replace(luceneQuery, options.scopedVars, 'lucene'); @@ -247,7 +250,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes // Hide meta-fields and check field type if (key[0] !== '_' && (!query.type || - query.type && typeMap[subObj.type] === query.type)) { + query.type && typeMap[subObj.type] === query.type)) { fields[fieldName] = { text: fieldName, @@ -314,6 +317,14 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes return this.getTerms(query); } }; + + this.getTagKeys = function() { + return this.getFields({}); + }; + + this.getTagValues = function(options) { + return this.getTerms({field: options.key, query: '*'}); + }; } return { diff --git a/public/app/plugins/datasource/elasticsearch/query_builder.js b/public/app/plugins/datasource/elasticsearch/query_builder.js index 9c8217102aa..d256c6d1438 100644 --- a/public/app/plugins/datasource/elasticsearch/query_builder.js +++ b/public/app/plugins/datasource/elasticsearch/query_builder.js @@ -98,7 +98,23 @@ function (queryDef) { return query; }; - ElasticQueryBuilder.prototype.build = function(target) { + ElasticQueryBuilder.prototype.addAdhocFilters = function(query, adhocFilters) { + if (!adhocFilters) { + return; + } + + var i, filter, condition; + var must = query.query.filtered.filter.bool.must; + + for (i = 0; i < adhocFilters.length; i++) { + filter = adhocFilters[i]; + condition = {}; + condition[filter.key] = filter.value; + must.push({"term": condition}); + } + }; + + ElasticQueryBuilder.prototype.build = function(target, adhocFilters) { // make sure query has defaults; target.metrics = target.metrics || [{ type: 'count', id: '1' }]; target.dsType = 'elasticsearch'; @@ -125,6 +141,8 @@ function (queryDef) { } }; + this.addAdhocFilters(query, adhocFilters); + // handle document query if (target.bucketAggs.length === 0) { metric = target.metrics[0]; 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 bbd5711fd1f..d9174e73969 100644 --- a/public/app/plugins/datasource/elasticsearch/specs/query_builder_specs.ts +++ b/public/app/plugins/datasource/elasticsearch/specs/query_builder_specs.ts @@ -238,4 +238,16 @@ describe('ElasticQueryBuilder', function() { expect(firstLevel.aggs["2"].derivative.buckets_path).to.be("3"); }); + it('with adhoc filters', function() { + var query = builder.build({ + metrics: [{type: 'Count', id: '0'}], + timeField: '@timestamp', + bucketAggs: [{type: 'date_histogram', field: '@timestamp', id: '3'}], + }, [ + {key: 'key1', operator: '=', value: 'value1'} + ]); + + expect(query.query.filtered.filter.bool.must[1].term["key1"]).to.be("value1"); + }); + }); diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index 4c707dd59d1..76a66d18e3c 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -9,6 +9,7 @@ import InfluxQuery from './influx_query'; import ResponseParser from './response_parser'; import InfluxQueryBuilder from './query_builder'; + export default class InfluxDatasource { type: string; urls: any; @@ -21,7 +22,6 @@ export default class InfluxDatasource { interval: any; supportAnnotations: boolean; supportMetrics: boolean; - supportAdhocFilters: boolean; responseParser: any; /** @ngInject */ @@ -40,7 +40,6 @@ export default class InfluxDatasource { this.interval = (instanceSettings.jsonData || {}).timeInterval; this.supportAnnotations = true; this.supportMetrics = true; - this.supportAdhocFilters = true; this.responseParser = new ResponseParser(); } diff --git a/public/test/specs/helpers.js b/public/test/specs/helpers.js index 0e5f20a853c..424b190b6dd 100644 --- a/public/test/specs/helpers.js +++ b/public/test/specs/helpers.js @@ -158,6 +158,7 @@ define([ return _.template(text, this.templateSettings)(this.data); }; this.init = function() {}; + this.getAdhocFilters = function() { return []; }; this.fillVariableValuesForUrl = function() {}; this.updateTemplateData = function() { }; this.variableExists = function() { return false; }; From 7b7ba46f123cff0517d110594f045bc6727a101d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 21 Sep 2016 09:10:25 +0200 Subject: [PATCH 40/63] fix(styleguide): fixed theme switching in style guide --- public/app/features/styleguide/styleguide.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/public/app/features/styleguide/styleguide.ts b/public/app/features/styleguide/styleguide.ts index 52ccd20ac59..c297f12ab4c 100644 --- a/public/app/features/styleguide/styleguide.ts +++ b/public/app/features/styleguide/styleguide.ts @@ -13,7 +13,7 @@ class StyleGuideCtrl { pages = ['colors', 'buttons']; /** @ngInject **/ - constructor(private $http, $routeParams) { + constructor(private $http, private $routeParams, private $location) { this.theme = config.bootData.user.lightTheme ? 'light': 'dark'; this.page = {}; @@ -37,8 +37,11 @@ class StyleGuideCtrl { } switchTheme() { - var other = this.theme === 'dark' ? 'light' : 'dark'; - window.location.href = window.location.href + '?theme=' + other; + this.$routeParams.theme = this.theme === 'dark' ? 'light' : 'dark'; + this.$location.search(this.$routeParams); + setTimeout(() => { + window.location.href = window.location.href; + }); } } From 7c10af012eb40bcc6a7e7c3e8d8fec70accc7464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 21 Sep 2016 10:56:17 +0200 Subject: [PATCH 41/63] feat(singlestat): slight layout changes to options tab --- .../app/plugins/panel/singlestat/editor.html | 307 +++++++----------- 1 file changed, 120 insertions(+), 187 deletions(-) diff --git a/public/app/plugins/panel/singlestat/editor.html b/public/app/plugins/panel/singlestat/editor.html index a4fd27bd080..e4e8446934a 100644 --- a/public/app/plugins/panel/singlestat/editor.html +++ b/public/app/plugins/panel/singlestat/editor.html @@ -1,192 +1,125 @@
    -
    -
    -
    - -
    -
    - - -
    -
    - -
    - -
    -
    -
    - - -
    -
    -
    -
    - -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    - - -
    -
    -
    -
    +
    +
    Value
    -
    -
    Coloring
    -
    -
    - - -
    -
    -
    - - -
    -
    - - - - - - - - - - -
    - -
    -
    -
    -
    -
    +
    +
    + +
    + +
    + +
    + +
    +
    +
    -
    -
    Spark lines
    -
    -
    - - -
    - - - - -
    -
    - - - - -
    -
    -
    -
    +
    +
    + + + +
    + +
    +
    +
    -
    -
    Gauge
    -
    -
    - -
    - - -
    -
    - - -
    -
    - -
    -
    -
    - - -
    -
    +
    + + + +
    + +
    +
    +
    + +
    +
    Unit
    +
    +
    +
    +
    +
    + + +
    +
    +
    + +
    +
    Coloring
    +
    + + +
    +
    +
    + + +
    +
    +
    + + + + + + + + + + + + + Invert + + +
    +
    + +
    +
    Spark lines
    + +
    + +
    + + + + +
    +
    + + + + +
    +
    +
    + +
    +
    Gauge
    + +
    +
    + + + +
    +
    + + +
    + + +
    +
    From 8b90eedb35f57a226d9f8ca90cca8cb4282c5848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 21 Sep 2016 11:23:39 +0200 Subject: [PATCH 42/63] feat(table): table options tab form layout changes --- public/app/plugins/panel/table/editor.html | 172 +++++++++------------ 1 file changed, 71 insertions(+), 101 deletions(-) diff --git a/public/app/plugins/panel/table/editor.html b/public/app/plugins/panel/table/editor.html index f2f0f3e7394..6e7281dd78c 100644 --- a/public/app/plugins/panel/table/editor.html +++ b/public/app/plugins/panel/table/editor.html @@ -1,24 +1,16 @@
    Data
    -
    -
    - -
    - -
    -
    -
    -
    -
    -
    -
    -
    - -
    +
    + +
    + +
    +
    +
    +
    + +