Procházet zdrojové kódy

feat(templating): progress on template system rewrite #6048

Torkel Ödegaard před 9 roky
rodič
revize
46ebae7304

+ 1 - 1
public/app/core/utils/kbn.js

@@ -10,7 +10,7 @@ function($, _, moment) {
   kbn.valueFormats = {};
   kbn.valueFormats = {};
 
 
   kbn.regexEscape = function(value) {
   kbn.regexEscape = function(value) {
-    return value.replace(/[\\^$*+?.()|[\]{}]/g, '\\\\$&');
+    return value.replace(/[\\^$*+?.()|[\]{}\/]/g, '\\$&')
   };
   };
 
 
   ///// HELPER FUNCTIONS /////
   ///// HELPER FUNCTIONS /////

+ 4 - 0
public/app/features/templating/all.ts

@@ -5,9 +5,13 @@ import './editorCtrl';
 import {VariableSrv} from './variable_srv';
 import {VariableSrv} from './variable_srv';
 import {IntervalVariable} from './interval_variable';
 import {IntervalVariable} from './interval_variable';
 import {QueryVariable} from './query_variable';
 import {QueryVariable} from './query_variable';
+import {DatasourceVariable} from './datasource_variable';
+import {CustomVariable} from './custom_variable';
 
 
 export {
 export {
   VariableSrv,
   VariableSrv,
   IntervalVariable,
   IntervalVariable,
   QueryVariable,
   QueryVariable,
+  DatasourceVariable,
+  CustomVariable,
 }
 }

+ 41 - 0
public/app/features/templating/custom_variable.ts

@@ -0,0 +1,41 @@
+///<reference path="../../headers/common.d.ts" />
+
+import _ from 'lodash';
+import kbn from 'app/core/utils/kbn';
+import {Variable} from './variable';
+import {VariableSrv, variableConstructorMap} from './variable_srv';
+
+export class CustomVariable implements Variable {
+  query: string;
+  options: any;
+  includeAll: boolean;
+
+  /** @ngInject */
+  constructor(private model, private timeSrv, private templateSrv) {
+    _.extend(this, model);
+  }
+
+  setValue(option) {
+  }
+
+  updateOptions() {
+    // extract options in comma separated string
+    this.options = _.map(this.query.split(/[,]+/), function(text) {
+      return { text: text.trim(), value: text.trim() };
+    });
+
+    if (this.includeAll) {
+      this.addAllOption();
+    }
+  }
+
+  addAllOption() {
+    this.options.unshift({text: 'All', value: "$__all"});
+  }
+
+  dependsOn(variableName) {
+    return false;
+  }
+}
+
+variableConstructorMap['custom'] = CustomVariable;

+ 56 - 0
public/app/features/templating/datasource_variable.ts

@@ -0,0 +1,56 @@
+///<reference path="../../headers/common.d.ts" />
+
+import _ from 'lodash';
+import kbn from 'app/core/utils/kbn';
+import {Variable} from './variable';
+import {VariableSrv, variableConstructorMap} from './variable_srv';
+
+export class DatasourceVariable implements Variable {
+  regex: any;
+  query: string;
+  options: any;
+
+  /** @ngInject */
+  constructor(private model, private datasourceSrv) {
+    _.extend(this, model);
+  }
+
+  setValue(option) {
+  }
+
+  updateOptions() {
+    var options = [];
+    var sources = this.datasourceSrv.getMetricSources({skipVariables: true});
+    var regex;
+
+    if (this.regex) {
+      regex = kbn.stringToJsRegex(this.regex);
+    }
+
+    for (var i = 0; i < sources.length; i++) {
+      var source = sources[i];
+      // must match on type
+      if (source.meta.id !== this.query) {
+        continue;
+      }
+
+      if (regex && !regex.exec(source.name)) {
+        continue;
+      }
+
+      options.push({text: source.name, value: source.name});
+    }
+
+    if (options.length === 0) {
+      options.push({text: 'No data sources found', value: ''});
+    }
+
+    this.options = options;
+  }
+
+  dependsOn(variableName) {
+    return false;
+  }
+}
+
+variableConstructorMap['datasource'] = DatasourceVariable;

+ 4 - 0
public/app/features/templating/interval_variable.ts

@@ -43,6 +43,10 @@ export class IntervalVariable implements Variable {
       this.updateAutoValue();
       this.updateAutoValue();
     }
     }
   }
   }
+
+  dependsOn(variableName) {
+    return false;
+  }
 }
 }
 
 
 variableConstructorMap['interval'] = IntervalVariable;
 variableConstructorMap['interval'] = IntervalVariable;

+ 32 - 8
public/app/features/templating/query_variable.ts

@@ -2,7 +2,7 @@
 
 
 import _ from 'lodash';
 import _ from 'lodash';
 import kbn from 'app/core/utils/kbn';
 import kbn from 'app/core/utils/kbn';
-import {Variable} from './variable';
+import {Variable, containsVariable} from './variable';
 import {VariableSrv, variableConstructorMap} from './variable_srv';
 import {VariableSrv, variableConstructorMap} from './variable_srv';
 
 
 function getNoneOption() {
 function getNoneOption() {
@@ -17,8 +17,9 @@ export class QueryVariable implements Variable {
   options: any;
   options: any;
   current: any;
   current: any;
   includeAll: boolean;
   includeAll: boolean;
+  refresh: number;
 
 
-  constructor(private model, private datasourceSrv, private templateSrv, private variableSrv)  {
+  constructor(private model, private datasourceSrv, private templateSrv, private variableSrv, private $q)  {
     _.extend(this, model);
     _.extend(this, model);
   }
   }
 
 
@@ -33,6 +34,23 @@ export class QueryVariable implements Variable {
     return this.variableSrv.variableUpdated(this);
     return this.variableSrv.variableUpdated(this);
   }
   }
 
 
+  setValueFromUrl(urlValue) {
+    var promise = this.$q.when();
+
+    if (this.refresh) {
+      promise = this.updateOptions();
+    }
+
+    return promise.then(() => {
+      var option = _.find(this.options, op => {
+        return op.text === urlValue || op.value === urlValue;
+      });
+
+      option = option || { text: urlValue, value: urlValue };
+      return this.setValue(option);
+    });
+  }
+
   updateOptions() {
   updateOptions() {
     return this.datasourceSrv.get(this.datasource)
     return this.datasourceSrv.get(this.datasource)
     .then(this.updateOptionsFromMetricFindQuery.bind(this))
     .then(this.updateOptionsFromMetricFindQuery.bind(this))
@@ -102,24 +120,30 @@ export class QueryVariable implements Variable {
 
 
     var sortType = Math.ceil(sortOrder / 2);
     var sortType = Math.ceil(sortOrder / 2);
     var reverseSort = (sortOrder % 2 === 0);
     var reverseSort = (sortOrder % 2 === 0);
+
     if (sortType === 1) {
     if (sortType === 1) {
       options = _.sortBy(options, 'text');
       options = _.sortBy(options, 'text');
     } else if (sortType === 2) {
     } else if (sortType === 2) {
       options = _.sortBy(options, function(opt) {
       options = _.sortBy(options, function(opt) {
-        var matches = opt.text.match(new RegExp(".*?(\d+).*"));
-        if (!matches) {
-          return 0;
-        } else {
-          return parseInt(matches[1], 10);
-        }
+        var matches = opt.text.match(/.*?(\d+).*/);
+if (!matches) {
+  return 0;
+} else {
+  return parseInt(matches[1], 10);
+}
       });
       });
     }
     }
+
     if (reverseSort) {
     if (reverseSort) {
       options = options.reverse();
       options = options.reverse();
     }
     }
 
 
     return options;
     return options;
   }
   }
+
+  dependsOn(variableName) {
+    return containsVariable(this.query, variableName) || containsVariable(this.datasource, variableName);
+  }
 }
 }
 
 
 variableConstructorMap['query'] = QueryVariable;
 variableConstructorMap['query'] = QueryVariable;

+ 36 - 0
public/app/features/templating/specs/variable_specs.ts

@@ -0,0 +1,36 @@
+import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common';
+
+import {containsVariable} from '../variable';
+
+describe('containsVariable', function() {
+
+  describe('when checking if a string contains a variable', function() {
+
+    it('should find it with $var syntax', function() {
+      var contains = containsVariable('this.$test.filters', 'test');
+      expect(contains).to.be(true);
+    });
+
+    it('should not find it if only part matches with $var syntax', function() {
+      var contains = containsVariable('this.$ServerDomain.filters', 'Server');
+      expect(contains).to.be(false);
+    });
+
+    it('should find it with [[var]] syntax', function() {
+      var contains = containsVariable('this.[[test]].filters', 'test');
+      expect(contains).to.be(true);
+    });
+
+    it('should find it when part of segment', function() {
+      var contains = containsVariable('metrics.$env.$group-*', 'group');
+      expect(contains).to.be(true);
+    });
+
+    it('should find it its the only thing', function() {
+      var contains = containsVariable('$env', 'env');
+      expect(contains).to.be(true);
+    });
+  });
+
+});
+

+ 159 - 2
public/app/features/templating/specs/variable_srv_specs.ts

@@ -4,7 +4,7 @@ import moment from 'moment';
 import helpers from 'test/specs/helpers';
 import helpers from 'test/specs/helpers';
 import '../all';
 import '../all';
 
 
-describe.only('VariableSrv', function() {
+describe('VariableSrv', function() {
   var ctx = new helpers.ControllerTestContext();
   var ctx = new helpers.ControllerTestContext();
 
 
   beforeEach(angularMocks.module('grafana.core'));
   beforeEach(angularMocks.module('grafana.core'));
@@ -15,11 +15,58 @@ describe.only('VariableSrv', function() {
   beforeEach(angularMocks.inject(($rootScope, $q, $location, $injector) => {
   beforeEach(angularMocks.inject(($rootScope, $q, $location, $injector) => {
     ctx.$q = $q;
     ctx.$q = $q;
     ctx.$rootScope = $rootScope;
     ctx.$rootScope = $rootScope;
+    ctx.$location = $location;
     ctx.variableSrv = $injector.get('variableSrv');
     ctx.variableSrv = $injector.get('variableSrv');
     ctx.variableSrv.init({templating: {list: []}});
     ctx.variableSrv.init({templating: {list: []}});
     ctx.$rootScope.$digest();
     ctx.$rootScope.$digest();
   }));
   }));
 
 
+  function describeInitSceneario(desc, fn) {
+    describe(desc, function() {
+      var scenario: any = {
+        urlParams: {},
+        setup: setupFn => {
+          scenario.setupFn = setupFn;
+        }
+      };
+
+      beforeEach(function() {
+        scenario.setupFn();
+        var ds: any = {};
+        ds.metricFindQuery = sinon.stub().returns(ctx.$q.when(scenario.queryResult));
+        ctx.datasourceSrv.get = sinon.stub().returns(ctx.$q.when(ds));
+        ctx.datasourceSrv.getMetricSources = sinon.stub().returns(scenario.metricSources);
+
+        ctx.$location.search = sinon.stub().returns(scenario.urlParams);
+
+        ctx.dashboard = {templating: {list: scenario.variables}};
+        ctx.variableSrv.init(ctx.dashboard);
+        ctx.$rootScope.$digest();
+
+        scenario.variables = ctx.variableSrv.variables;
+      });
+
+      fn(scenario);
+    });
+  }
+
+  describeInitSceneario('when setting simple variable via url', scenario => {
+    scenario.setup(() => {
+      scenario.variables = [{
+        name: 'apps',
+        type: 'query',
+        current: {text: "test", value: "test"},
+        options: [{text: "test", value: "test"}]
+      }];
+      scenario.urlParams["var-apps"] = "new";
+    });
+
+    it('should update current value', () => {
+      expect(scenario.variables[0].current.value).to.be("new");
+      expect(scenario.variables[0].current.text).to.be("new");
+    });
+  });
+
   function describeUpdateVariable(desc, fn) {
   function describeUpdateVariable(desc, fn) {
     describe(desc, function() {
     describe(desc, function() {
       var scenario: any = {};
       var scenario: any = {};
@@ -32,6 +79,8 @@ describe.only('VariableSrv', function() {
         var ds: any = {};
         var ds: any = {};
         ds.metricFindQuery = sinon.stub().returns(ctx.$q.when(scenario.queryResult));
         ds.metricFindQuery = sinon.stub().returns(ctx.$q.when(scenario.queryResult));
         ctx.datasourceSrv.get = sinon.stub().returns(ctx.$q.when(ds));
         ctx.datasourceSrv.get = sinon.stub().returns(ctx.$q.when(ds));
+        ctx.datasourceSrv.getMetricSources = sinon.stub().returns(scenario.metricSources);
+
 
 
         scenario.variable = ctx.variableSrv.addVariable(scenario.variableModel);
         scenario.variable = ctx.variableSrv.addVariable(scenario.variableModel);
         ctx.variableSrv.updateOptions(scenario.variable);
         ctx.variableSrv.updateOptions(scenario.variable);
@@ -274,6 +323,114 @@ describe.only('VariableSrv', function() {
       });
       });
     });
     });
 
 
-});
+    describeUpdateVariable('without sort', function(scenario) {
+      scenario.setup(function() {
+        scenario.variableModel = {type: 'query', query: 'apps.*', name: 'test', sort: 0};
+        scenario.queryResult = [{text: 'bbb2'}, {text: 'aaa10'}, { text: 'ccc3'}];
+      });
 
 
+      it('should return options without sort', function() {
+        expect(scenario.variable.options[0].text).to.be('bbb2');
+        expect(scenario.variable.options[1].text).to.be('aaa10');
+        expect(scenario.variable.options[2].text).to.be('ccc3');
+      });
+    });
+
+    describeUpdateVariable('with alphabetical sort (asc)', function(scenario) {
+      scenario.setup(function() {
+        scenario.variableModel = {type: 'query', query: 'apps.*', name: 'test', sort: 1};
+        scenario.queryResult = [{text: 'bbb2'}, {text: 'aaa10'}, { text: 'ccc3'}];
+      });
 
 
+      it('should return options with alphabetical sort', function() {
+        expect(scenario.variable.options[0].text).to.be('aaa10');
+        expect(scenario.variable.options[1].text).to.be('bbb2');
+        expect(scenario.variable.options[2].text).to.be('ccc3');
+      });
+    });
+
+    describeUpdateVariable('with alphabetical sort (desc)', function(scenario) {
+      scenario.setup(function() {
+        scenario.variableModel = {type: 'query', query: 'apps.*', name: 'test', sort: 2};
+        scenario.queryResult = [{text: 'bbb2'}, {text: 'aaa10'}, { text: 'ccc3'}];
+      });
+
+      it('should return options with alphabetical sort', function() {
+        expect(scenario.variable.options[0].text).to.be('ccc3');
+        expect(scenario.variable.options[1].text).to.be('bbb2');
+        expect(scenario.variable.options[2].text).to.be('aaa10');
+      });
+    });
+
+    describeUpdateVariable('with numerical sort (asc)', function(scenario) {
+      scenario.setup(function() {
+        scenario.variableModel = {type: 'query', query: 'apps.*', name: 'test', sort: 3};
+        scenario.queryResult = [{text: 'bbb2'}, {text: 'aaa10'}, { text: 'ccc3'}];
+      });
+
+      it('should return options with numerical sort', function() {
+        expect(scenario.variable.options[0].text).to.be('bbb2');
+        expect(scenario.variable.options[1].text).to.be('ccc3');
+        expect(scenario.variable.options[2].text).to.be('aaa10');
+      });
+    });
+
+    describeUpdateVariable('with numerical sort (desc)', function(scenario) {
+      scenario.setup(function() {
+        scenario.variableModel = {type: 'query', query: 'apps.*', name: 'test', sort: 4};
+        scenario.queryResult = [{text: 'bbb2'}, {text: 'aaa10'}, { text: 'ccc3'}];
+      });
+
+      it('should return options with numerical sort', function() {
+        expect(scenario.variable.options[0].text).to.be('aaa10');
+        expect(scenario.variable.options[1].text).to.be('ccc3');
+        expect(scenario.variable.options[2].text).to.be('bbb2');
+      });
+    });
+
+    //
+    // datasource variable update
+    //
+    describeUpdateVariable('datasource variable with regex filter', function(scenario) {
+      scenario.setup(function() {
+        scenario.variableModel = {
+          type: 'datasource',
+          query: 'graphite',
+          name: 'test',
+          current: {value: 'backend4_pee', text: 'backend4_pee'},
+          regex: '/pee$/'
+        };
+        scenario.metricSources = [
+          {name: 'backend1', meta: {id: 'influx'}},
+          {name: 'backend2_pee', meta: {id: 'graphite'}},
+          {name: 'backend3', meta: {id: 'graphite'}},
+          {name: 'backend4_pee', meta: {id: 'graphite'}},
+        ];
+      });
+
+      it('should set only contain graphite ds and filtered using regex', function() {
+        expect(scenario.variable.options.length).to.be(2);
+        expect(scenario.variable.options[0].value).to.be('backend2_pee');
+        expect(scenario.variable.options[1].value).to.be('backend4_pee');
+      });
+
+      it('should keep current value if available', function() {
+        expect(scenario.variable.current.value).to.be('backend4_pee');
+      });
+    });
+
+    //
+    // Custom variable update
+    //
+    describeUpdateVariable('update custom variable', function(scenario) {
+      scenario.setup(function() {
+        scenario.variableModel = {type: 'custom', query: 'hej, hop, asd', name: 'test'};
+      });
+
+      it('should update options array', function() {
+        expect(scenario.variable.options.length).to.be(3);
+        expect(scenario.variable.options[0].text).to.be('hej');
+        expect(scenario.variable.options[1].value).to.be('hop');
+      });
+    });
+});

+ 4 - 18
public/app/features/templating/templateSrv.js

@@ -1,8 +1,9 @@
 define([
 define([
   'angular',
   'angular',
   'lodash',
   'lodash',
+  'app/core/utils/kbn',
 ],
 ],
-function (angular, _) {
+function (angular, _, kbn) {
   'use strict';
   'use strict';
 
 
   var module = angular.module('grafana.services');
   var module = angular.module('grafana.services');
@@ -32,10 +33,6 @@ function (angular, _) {
       }
       }
     };
     };
 
 
-    function regexEscape(value) {
-      return value.replace(/[\\^$*+?.()|[\]{}\/]/g, '\\$&');
-    }
-
     function luceneEscape(value) {
     function luceneEscape(value) {
       return value.replace(/([\!\*\+\-\=<>\s\&\|\(\)\[\]\{\}\^\~\?\:\\/"])/g, "\\$1");
       return value.replace(/([\!\*\+\-\=<>\s\&\|\(\)\[\]\{\}\^\~\?\:\\/"])/g, "\\$1");
     }
     }
@@ -61,10 +58,10 @@ function (angular, _) {
       switch(format) {
       switch(format) {
         case "regex": {
         case "regex": {
           if (typeof value === 'string') {
           if (typeof value === 'string') {
-            return regexEscape(value);
+            return kbn.regexEscape(value);
           }
           }
 
 
-          var escapedValues = _.map(value, regexEscape);
+          var escapedValues = _.map(value, kbn.regexEscape);
           return '(' + escapedValues.join('|') + ')';
           return '(' + escapedValues.join('|') + ')';
         }
         }
         case "lucene": {
         case "lucene": {
@@ -95,17 +92,6 @@ function (angular, _) {
       return match && (self._index[match[1] || match[2]] !== void 0);
       return match && (self._index[match[1] || match[2]] !== void 0);
     };
     };
 
 
-    this.containsVariable = function(str, variableName) {
-      if (!str) {
-        return false;
-      }
-
-      variableName = regexEscape(variableName);
-      var findVarRegex = new RegExp('\\$(' + variableName + ')(?:\\W|$)|\\[\\[(' + variableName + ')\\]\\]', 'g');
-      var match = findVarRegex.exec(str);
-      return match !== null;
-    };
-
     this.highlightVariablesAsHtml = function(str) {
     this.highlightVariablesAsHtml = function(str) {
       if (!str || !_.isString(str)) { return str; }
       if (!str || !_.isString(str)) { return str; }
 
 

+ 18 - 0
public/app/features/templating/variable.ts

@@ -1,5 +1,23 @@
 
 
+import kbn from 'app/core/utils/kbn';
+
+export function containsVariable(str, variableName) {
+  if (!str) {
+    return false;
+  }
+
+  variableName = kbn.regexEscape(variableName);
+  var findVarRegex = new RegExp('\\$(' + variableName + ')(?:\\W|$)|\\[\\[(' + variableName + ')\\]\\]', 'g');
+  var match = findVarRegex.exec(str);
+  return match !== null;
+}
+
 export interface Variable {
 export interface Variable {
   setValue(option);
   setValue(option);
+  updateOptions();
+  dependsOn(variableName);
 }
 }
 
 
+
+
+

+ 50 - 5
public/app/features/templating/variable_srv.ts

@@ -34,7 +34,52 @@ export class VariableSrv {
       dashboard.templating.list.map(this.addVariable.bind(this));
       dashboard.templating.list.map(this.addVariable.bind(this));
       this.templateSrv.init(this.variables);
       this.templateSrv.init(this.variables);
 
 
-      return this.$q.when();
+      var queryParams = this.$location.search();
+
+      for (let variable of this.variables) {
+        this.variableLock[variable.name] = this.$q.defer();
+      }
+
+      var promises = [];
+
+      for (let variable of this.variables) {
+        promises.push(this.processVariable(variable, queryParams));
+      }
+
+      return this.$q.all(this.variables.map(variable => {
+        return this.processVariable(variable, queryParams);
+      }));
+    }
+
+    processVariable(variable, queryParams) {
+      var dependencies = [];
+      var lock = this.variableLock[variable.name];
+
+      for (let otherVariable of this.variables) {
+        if (variable.dependsOn(otherVariable)) {
+          dependencies.push(this.variableLock[otherVariable.name].promise);
+        }
+      }
+
+      return this.$q.all(dependencies).then(() => {
+        var urlValue = queryParams['var-' + variable.name];
+        if (urlValue !== void 0) {
+          return variable.setValueFromUrl(urlValue).then(lock.resolve);
+        }
+
+        if (variable.refresh === 1 || variable.refresh === 2) {
+          return variable.updateOptions().then(() => {
+            // if (_.isEmpty(variable.current) && variable.options.length) {
+            //   self.setVariableValue(variable, variable.options[0]);
+            // }
+            lock.resolve();
+          });
+        }
+
+        lock.resolve();
+      }).finally(() => {
+        delete this.variableLock[variable.name];
+      });
     }
     }
 
 
     addVariable(model) {
     addVariable(model) {
@@ -60,14 +105,13 @@ export class VariableSrv {
         return this.$q.when();
         return this.$q.when();
       }
       }
 
 
+      // cascade updates to variables that use this variable
       var promises = _.map(this.variables, otherVariable => {
       var promises = _.map(this.variables, otherVariable => {
         if (otherVariable === variable) {
         if (otherVariable === variable) {
           return;
           return;
         }
         }
 
 
-        if (this.templateSrv.containsVariable(otherVariable.regex, variable.name) ||
-            this.templateSrv.containsVariable(otherVariable.query, variable.name) ||
-              this.templateSrv.containsVariable(otherVariable.datasource, variable.name)) {
+        if (otherVariable.dependsOn(variable)) {
           return this.updateOptions(otherVariable);
           return this.updateOptions(otherVariable);
         }
         }
       });
       });
@@ -101,7 +145,7 @@ export class VariableSrv {
 
 
     validateVariableSelectionState(variable) {
     validateVariableSelectionState(variable) {
       if (!variable.current) {
       if (!variable.current) {
-        if (!variable.options.length) { return Promise.resolve(); }
+        if (!variable.options.length) { return this.$q.when(); }
         return variable.setValue(variable.options[0]);
         return variable.setValue(variable.options[0]);
       }
       }
 
 
@@ -129,6 +173,7 @@ export class VariableSrv {
         }
         }
       }
       }
     }
     }
+
 }
 }
 
 
 coreModule.service('variableSrv', VariableSrv);
 coreModule.service('variableSrv', VariableSrv);

+ 6 - 3
public/app/plugins/datasource/prometheus/datasource.ts

@@ -3,7 +3,6 @@
 import angular from 'angular';
 import angular from 'angular';
 import _ from 'lodash';
 import _ from 'lodash';
 import moment from 'moment';
 import moment from 'moment';
-import kbn from 'app/core/utils/kbn';
 
 
 import * as dateMath from 'app/core/utils/datemath';
 import * as dateMath from 'app/core/utils/datemath';
 import PrometheusMetricFindQuery from './metric_find_query';
 import PrometheusMetricFindQuery from './metric_find_query';
@@ -41,6 +40,10 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
     return backendSrv.datasourceRequest(options);
     return backendSrv.datasourceRequest(options);
   };
   };
 
 
+  function prometheusSpecialRegexEscape(value) {
+    return value.replace(/[\\^$*+?.()|[\]{}]/g, '\\\\$&');
+  }
+
   this.interpolateQueryExpr = function(value, variable, defaultFormatFn) {
   this.interpolateQueryExpr = function(value, variable, defaultFormatFn) {
     // if no multi or include all do not regexEscape
     // if no multi or include all do not regexEscape
     if (!variable.multi && !variable.includeAll) {
     if (!variable.multi && !variable.includeAll) {
@@ -48,10 +51,10 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
     }
     }
 
 
     if (typeof value === 'string') {
     if (typeof value === 'string') {
-      return kbn.regexEscape(value);
+      return prometheusSpecialRegexEscape(value);
     }
     }
 
 
-    var escapedValues = _.map(value, kbn.regexEscape);
+    var escapedValues = _.map(value, prometheusSpecialRegexEscape);
     return escapedValues.join('|');
     return escapedValues.join('|');
   };
   };
 
 

+ 0 - 32
public/test/specs/templateSrv-specs.js

@@ -177,38 +177,6 @@ define([
         var result = _templateSrv.highlightVariablesAsHtml('this $google ok');
         var result = _templateSrv.highlightVariablesAsHtml('this $google ok');
         expect(result).to.be('this $google ok');
         expect(result).to.be('this $google ok');
       });
       });
-
-    });
-
-    describe('when checking if a string contains a variable', function() {
-      beforeEach(function() {
-        _templateSrv.init([{ name: 'test', current: { value: 'muuuu' } }]);
-      });
-
-      it('should find it with $var syntax', function() {
-        var contains = _templateSrv.containsVariable('this.$test.filters', 'test');
-        expect(contains).to.be(true);
-      });
-
-      it('should not find it if only part matches with $var syntax', function() {
-        var contains = _templateSrv.containsVariable('this.$ServerDomain.filters', 'Server');
-        expect(contains).to.be(false);
-      });
-
-      it('should find it with [[var]] syntax', function() {
-        var contains = _templateSrv.containsVariable('this.[[test]].filters', 'test');
-        expect(contains).to.be(true);
-      });
-
-      it('should find it when part of segment', function() {
-        var contains = _templateSrv.containsVariable('metrics.$env.$group-*', 'group');
-        expect(contains).to.be(true);
-      });
-
-      it('should find it its the only thing', function() {
-        var contains = _templateSrv.containsVariable('$env', 'env');
-        expect(contains).to.be(true);
-      });
     });
     });
 
 
     describe('updateTemplateData with simple value', function() {
     describe('updateTemplateData with simple value', function() {

+ 1 - 385
public/test/specs/templateValuesSrv-specs.js

@@ -1,9 +1,8 @@
 define([
 define([
   '../mocks/dashboard-mock',
   '../mocks/dashboard-mock',
   './helpers',
   './helpers',
-  'moment',
   'app/features/templating/templateValuesSrv'
   'app/features/templating/templateValuesSrv'
-], function(dashboardMock, helpers, moment) {
+], function(dashboardMock, helpers) {
   'use strict';
   'use strict';
 
 
   describe('templateValuesSrv', function() {
   describe('templateValuesSrv', function() {
@@ -13,20 +12,6 @@ define([
     beforeEach(ctx.providePhase(['datasourceSrv', 'timeSrv', 'templateSrv', '$location']));
     beforeEach(ctx.providePhase(['datasourceSrv', 'timeSrv', 'templateSrv', '$location']));
     beforeEach(ctx.createService('templateValuesSrv'));
     beforeEach(ctx.createService('templateValuesSrv'));
 
 
-    describe('update interval variable options', function() {
-      var variable = { type: 'interval', query: 'auto,1s,2h,5h,1d', name: 'test' };
-
-      beforeEach(function() {
-        ctx.service.updateOptions(variable);
-      });
-
-      it('should update options array', function() {
-        expect(variable.options.length).to.be(5);
-        expect(variable.options[1].text).to.be('1s');
-        expect(variable.options[1].value).to.be('1s');
-      });
-    });
-
     describe('when template variable is present in url', function() {
     describe('when template variable is present in url', function() {
       describe('and setting simple variable', function() {
       describe('and setting simple variable', function() {
         var variable = {
         var variable = {
@@ -99,375 +84,6 @@ define([
       });
       });
     });
     });
 
 
-    function describeUpdateVariable(desc, fn) {
-      describe(desc, function() {
-        var scenario = {};
-        scenario.setup = function(setupFn) {
-          scenario.setupFn = setupFn;
-        };
-
-        beforeEach(function() {
-          scenario.setupFn();
-          var ds = {};
-          ds.metricFindQuery = sinon.stub().returns(ctx.$q.when(scenario.queryResult));
-          ctx.datasourceSrv.get = sinon.stub().returns(ctx.$q.when(ds));
-          ctx.datasourceSrv.getMetricSources = sinon.stub().returns(scenario.metricSources);
-
-          ctx.service.updateOptions(scenario.variable);
-          ctx.$rootScope.$digest();
-        });
-
-        fn(scenario);
-      });
-    }
-
-    describeUpdateVariable('interval variable without auto', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = { type: 'interval', query: '1s,2h,5h,1d', name: 'test' };
-      });
-
-      it('should update options array', function() {
-        expect(scenario.variable.options.length).to.be(4);
-        expect(scenario.variable.options[0].text).to.be('1s');
-        expect(scenario.variable.options[0].value).to.be('1s');
-      });
-    });
-
-    describeUpdateVariable('query variable with empty current object and refresh', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = { type: 'query', query: '', name: 'test', current: {} };
-        scenario.queryResult = [{text: 'backend1'}, {text: 'backend2'}];
-      });
-
-      it('should set current value to first option', function() {
-        expect(scenario.variable.options.length).to.be(2);
-        expect(scenario.variable.current.value).to.be('backend1');
-      });
-    });
-
-    describeUpdateVariable('query variable with multi select and new options does not contain some selected values', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = {
-          type: 'query',
-          query: '',
-          name: 'test',
-          current: {
-            value: ['val1', 'val2', 'val3'],
-            text: 'val1 + val2 + val3'
-          }
-        };
-        scenario.queryResult = [{text: 'val2'}, {text: 'val3'}];
-      });
-
-      it('should update current value', function() {
-        expect(scenario.variable.current.value).to.eql(['val2', 'val3']);
-        expect(scenario.variable.current.text).to.eql('val2 + val3');
-      });
-    });
-
-    describeUpdateVariable('query variable with multi select and new options does not contain any selected values', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = {
-          type: 'query',
-          query: '',
-          name: 'test',
-          current: {
-            value: ['val1', 'val2', 'val3'],
-            text: 'val1 + val2 + val3'
-          }
-        };
-        scenario.queryResult = [{text: 'val5'}, {text: 'val6'}];
-      });
-
-      it('should update current value with first one', function() {
-        expect(scenario.variable.current.value).to.eql('val5');
-        expect(scenario.variable.current.text).to.eql('val5');
-      });
-    });
-
-    describeUpdateVariable('query variable with multi select and $__all selected', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = {
-          type: 'query',
-          query: '',
-          name: 'test',
-          includeAll: true,
-          current: {
-            value: ['$__all'],
-            text: 'All'
-          }
-        };
-        scenario.queryResult = [{text: 'val5'}, {text: 'val6'}];
-      });
-
-      it('should keep current All value', function() {
-        expect(scenario.variable.current.value).to.eql(['$__all']);
-        expect(scenario.variable.current.text).to.eql('All');
-      });
-    });
-
-    describeUpdateVariable('query variable with numeric results', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = { type: 'query', query: '', name: 'test', current: {} };
-        scenario.queryResult = [{text: 12, value: 12}];
-      });
-
-      it('should set current value to first option', function() {
-        expect(scenario.variable.current.value).to.be('12');
-        expect(scenario.variable.options[0].value).to.be('12');
-        expect(scenario.variable.options[0].text).to.be('12');
-      });
-    });
-
-    describeUpdateVariable('interval variable without auto', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = { type: 'interval', query: '1s,2h,5h,1d', name: 'test' };
-      });
-
-      it('should update options array', function() {
-        expect(scenario.variable.options.length).to.be(4);
-        expect(scenario.variable.options[0].text).to.be('1s');
-        expect(scenario.variable.options[0].value).to.be('1s');
-      });
-    });
-
-    describeUpdateVariable('interval variable with auto', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = { type: 'interval', query: '1s,2h,5h,1d', name: 'test', auto: true, auto_count: 10 };
-
-        var range = {
-          from: moment(new Date()).subtract(7, 'days').toDate(),
-          to: new Date()
-        };
-
-        ctx.timeSrv.timeRange = sinon.stub().returns(range);
-        ctx.templateSrv.setGrafanaVariable = sinon.spy();
-      });
-
-      it('should update options array', function() {
-        expect(scenario.variable.options.length).to.be(5);
-        expect(scenario.variable.options[0].text).to.be('auto');
-        expect(scenario.variable.options[0].value).to.be('$__auto_interval');
-      });
-
-      it('should set $__auto_interval', function() {
-        var call = ctx.templateSrv.setGrafanaVariable.getCall(0);
-        expect(call.args[0]).to.be('$__auto_interval');
-        expect(call.args[1]).to.be('12h');
-      });
-    });
-
-    describeUpdateVariable('update custom variable', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = {type: 'custom', query: 'hej, hop, asd', name: 'test'};
-      });
-
-      it('should update options array', function() {
-        expect(scenario.variable.options.length).to.be(3);
-        expect(scenario.variable.options[0].text).to.be('hej');
-        expect(scenario.variable.options[1].value).to.be('hop');
-      });
 
 
-      it('should set $__auto_interval', function() {
-        var call = ctx.templateSrv.setGrafanaVariable.getCall(0);
-        expect(call.args[0]).to.be('$__auto_interval');
-        expect(call.args[1]).to.be('12h');
-      });
-    });
-
-    describeUpdateVariable('basic query variable', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = { type: 'query', query: 'apps.*', name: 'test' };
-        scenario.queryResult = [{text: 'backend1'}, {text: 'backend2'}];
-      });
-
-      it('should update options array', function() {
-        expect(scenario.variable.options.length).to.be(2);
-        expect(scenario.variable.options[0].text).to.be('backend1');
-        expect(scenario.variable.options[0].value).to.be('backend1');
-        expect(scenario.variable.options[1].value).to.be('backend2');
-      });
-
-      it('should select first option as value', function() {
-        expect(scenario.variable.current.value).to.be('backend1');
-      });
-    });
-
-    describeUpdateVariable('and existing value still exists in options', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = { type: 'query', query: 'apps.*', name: 'test' };
-        scenario.variable.current = { value: 'backend2', text: 'backend2'};
-        scenario.queryResult = [{text: 'backend1'}, {text: 'backend2'}];
-      });
-
-      it('should keep variable value', function() {
-        expect(scenario.variable.current.text).to.be('backend2');
-      });
-    });
-
-    describeUpdateVariable('and regex pattern exists', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = { type: 'query', query: 'apps.*', name: 'test' };
-        scenario.variable.regex = '/apps.*(backend_[0-9]+)/';
-        scenario.queryResult = [{text: 'apps.backend.backend_01.counters.req'}, {text: 'apps.backend.backend_02.counters.req'}];
-      });
-
-      it('should extract and use match group', function() {
-        expect(scenario.variable.options[0].value).to.be('backend_01');
-      });
-    });
-
-    describeUpdateVariable('and regex pattern exists and no match', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = { type: 'query', query: 'apps.*', name: 'test' };
-        scenario.variable.regex = '/apps.*(backendasd[0-9]+)/';
-        scenario.queryResult = [{text: 'apps.backend.backend_01.counters.req'}, {text: 'apps.backend.backend_02.counters.req'}];
-      });
-
-      it('should not add non matching items, None option should be added instead', function() {
-        expect(scenario.variable.options.length).to.be(1);
-        expect(scenario.variable.options[0].isNone).to.be(true);
-      });
-    });
-
-    describeUpdateVariable('regex pattern without slashes', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = { type: 'query', query: 'apps.*', name: 'test' };
-        scenario.variable.regex = 'backend_01';
-        scenario.queryResult = [{text: 'apps.backend.backend_01.counters.req'}, {text: 'apps.backend.backend_02.counters.req'}];
-      });
-
-      it('should return matches options', function() {
-        expect(scenario.variable.options.length).to.be(1);
-      });
-    });
-
-    describeUpdateVariable('regex pattern remove duplicates', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = { type: 'query', query: 'apps.*', name: 'test' };
-        scenario.variable.regex = 'backend_01';
-        scenario.queryResult = [{text: 'apps.backend.backend_01.counters.req'}, {text: 'apps.backend.backend_01.counters.req'}];
-      });
-
-      it('should return matches options', function() {
-        expect(scenario.variable.options.length).to.be(1);
-      });
-    });
-
-    describeUpdateVariable('with include All', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = {type: 'query', query: 'apps.*', name: 'test', includeAll: true};
-        scenario.queryResult = [{text: 'backend1'}, {text: 'backend2'}, { text: 'backend3'}];
-      });
-
-      it('should add All option', function() {
-        expect(scenario.variable.options[0].text).to.be('All');
-        expect(scenario.variable.options[0].value).to.be('$__all');
-      });
-    });
-
-    describeUpdateVariable('with include all and custom value', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = { type: 'query', query: 'apps.*', name: 'test', includeAll: true, allValue: '*' };
-        scenario.queryResult = [{text: 'backend1'}, {text: 'backend2'}, { text: 'backend3'}];
-      });
-
-      it('should add All option with custom value', function() {
-        expect(scenario.variable.options[0].value).to.be('$__all');
-      });
-    });
-
-    describeUpdateVariable('datasource variable with regex filter', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = {
-          type: 'datasource',
-          query: 'graphite',
-          name: 'test',
-          current: {value: 'backend4_pee', text: 'backend4_pee'},
-          regex: '/pee$/'
-        };
-        scenario.metricSources = [
-          {name: 'backend1', meta: {id: 'influx'}},
-          {name: 'backend2_pee', meta: {id: 'graphite'}},
-          {name: 'backend3', meta: {id: 'graphite'}},
-          {name: 'backend4_pee', meta: {id: 'graphite'}},
-        ];
-      });
-
-      it('should set only contain graphite ds and filtered using regex', function() {
-        expect(scenario.variable.options.length).to.be(2);
-        expect(scenario.variable.options[0].value).to.be('backend2_pee');
-        expect(scenario.variable.options[1].value).to.be('backend4_pee');
-      });
-
-      it('should keep current value if available', function() {
-        expect(scenario.variable.current.value).to.be('backend4_pee');
-      });
-    });
-
-    describeUpdateVariable('without sort', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = {type: 'query', query: 'apps.*', name: 'test', sort: 0};
-        scenario.queryResult = [{text: 'bbb2'}, {text: 'aaa10'}, { text: 'ccc3'}];
-      });
-
-      it('should return options without sort', function() {
-        expect(scenario.variable.options[0].text).to.be('bbb2');
-        expect(scenario.variable.options[1].text).to.be('aaa10');
-        expect(scenario.variable.options[2].text).to.be('ccc3');
-      });
-    });
-
-    describeUpdateVariable('with alphabetical sort (asc)', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = {type: 'query', query: 'apps.*', name: 'test', sort: 1};
-        scenario.queryResult = [{text: 'bbb2'}, {text: 'aaa10'}, { text: 'ccc3'}];
-      });
-
-      it('should return options with alphabetical sort', function() {
-        expect(scenario.variable.options[0].text).to.be('aaa10');
-        expect(scenario.variable.options[1].text).to.be('bbb2');
-        expect(scenario.variable.options[2].text).to.be('ccc3');
-      });
-    });
-
-    describeUpdateVariable('with alphabetical sort (desc)', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = {type: 'query', query: 'apps.*', name: 'test', sort: 2};
-        scenario.queryResult = [{text: 'bbb2'}, {text: 'aaa10'}, { text: 'ccc3'}];
-      });
-
-      it('should return options with alphabetical sort', function() {
-        expect(scenario.variable.options[0].text).to.be('ccc3');
-        expect(scenario.variable.options[1].text).to.be('bbb2');
-        expect(scenario.variable.options[2].text).to.be('aaa10');
-      });
-    });
-
-    describeUpdateVariable('with numerical sort (asc)', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = {type: 'query', query: 'apps.*', name: 'test', sort: 3};
-        scenario.queryResult = [{text: 'bbb2'}, {text: 'aaa10'}, { text: 'ccc3'}];
-      });
-
-      it('should return options with numerical sort', function() {
-        expect(scenario.variable.options[0].text).to.be('bbb2');
-        expect(scenario.variable.options[1].text).to.be('ccc3');
-        expect(scenario.variable.options[2].text).to.be('aaa10');
-      });
-    });
-
-    describeUpdateVariable('with numerical sort (desc)', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = {type: 'query', query: 'apps.*', name: 'test', sort: 4};
-        scenario.queryResult = [{text: 'bbb2'}, {text: 'aaa10'}, { text: 'ccc3'}];
-      });
-
-      it('should return options with numerical sort', function() {
-        expect(scenario.variable.options[0].text).to.be('aaa10');
-        expect(scenario.variable.options[1].text).to.be('ccc3');
-        expect(scenario.variable.options[2].text).to.be('bbb2');
-      });
-    });
   });
   });
 });
 });