Просмотр исходного кода

Merge branch 'variable-value-formatting-rethink'

Torkel Ödegaard 9 лет назад
Родитель
Сommit
2cf1193b56
25 измененных файлов с 310 добавлено и 334 удалено
  1. 2 2
      .jshintrc
  2. 1 1
      docker/blocks/prometheus/prometheus.yml
  3. 2 2
      package.json
  4. 1 2
      public/app/core/directives/value_select_dropdown.js
  5. 8 10
      public/app/features/dashboard/partials/import.html
  6. 15 14
      public/app/features/dashboard/partials/shareModal.html
  7. 36 50
      public/app/features/templating/partials/editor.html
  8. 83 41
      public/app/features/templating/templateSrv.js
  9. 4 48
      public/app/features/templating/templateValuesSrv.js
  10. 8 5
      public/app/plugins/datasource/elasticsearch/datasource.js
  11. 17 1
      public/app/plugins/datasource/elasticsearch/elastic_response.js
  12. 1 1
      public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts
  13. 2 2
      public/app/plugins/datasource/influxdb/datasource.ts
  14. 19 8
      public/app/plugins/datasource/influxdb/influx_query.ts
  15. 2 1
      public/app/plugins/datasource/influxdb/query_ctrl.ts
  16. 18 17
      public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts
  17. 2 2
      public/app/plugins/datasource/opentsdb/datasource.js
  18. 1 1
      public/app/plugins/datasource/prometheus/datasource.ts
  19. 1 2
      public/app/plugins/datasource/prometheus/plugin.json
  20. 0 1
      public/sass/base/_type.scss
  21. 3 0
      public/sass/components/_infobox.scss
  22. 1 5
      public/sass/pages/_dashboard.scss
  23. 2 2
      public/test/.jshintrc
  24. 72 29
      public/test/specs/templateSrv-specs.js
  25. 9 87
      public/test/specs/templateValuesSrv-specs.js

+ 2 - 2
.jshintrc

@@ -4,7 +4,7 @@
   "bitwise":false,
   "curly": true,
   "eqnull": true,
-  "globalstrict": true,
+  "strict": true,
   "devel": true,
   "eqeqeq": true,
   "forin": false,
@@ -12,7 +12,7 @@
   "supernew": true,
   "expr": true,
   "indent": 2,
-  "latedef": true,
+  "latedef": false,
   "newcap": true,
   "noarg": true,
   "noempty": true,

+ 1 - 1
docker/blocks/prometheus/prometheus.yml

@@ -23,4 +23,4 @@ scrape_configs:
     # scheme defaults to 'http'.
 
     target_groups:
-      - targets: ['localhost:9090', '172.17.42.1:9091']
+      - targets: ['localhost:9090', '172.17.0.1:9091']

+ 2 - 2
package.json

@@ -24,7 +24,7 @@
     "grunt-contrib-copy": "~0.8.2",
     "grunt-contrib-cssmin": "~0.14.0",
     "grunt-contrib-htmlmin": "~0.6.0",
-    "grunt-contrib-jshint": "~0.11.3",
+    "grunt-contrib-jshint": "~1.0.0",
     "grunt-contrib-less": "~0.7.0",
     "grunt-contrib-uglify": "~0.11.0",
     "grunt-contrib-watch": "^0.6.1",
@@ -39,7 +39,7 @@
     "grunt-tslint": "^3.0.2",
     "grunt-typescript": "^0.8.0",
     "grunt-usemin": "3.0.0",
-    "jshint-stylish": "~0.1.5",
+    "jshint-stylish": "~2.1.0",
     "karma": "~0.13.15",
     "karma-chrome-launcher": "~0.2.2",
     "karma-coverage": "0.5.3",

+ 1 - 2
public/app/core/directives/value_select_dropdown.js

@@ -179,8 +179,7 @@ function (angular, _, coreModule) {
       vm.variable.current.text = _.pluck(vm.selectedValues, 'text').join(' + ');
       vm.variable.current.tags = vm.selectedTags;
 
-      // only single value
-      if (vm.selectedValues.length === 1) {
+      if (!vm.variable.multi) {
         vm.variable.current.value = vm.selectedValues[0].value;
       }
 

+ 8 - 10
public/app/features/dashboard/partials/import.html

@@ -15,26 +15,24 @@
 			</form>
 	</div>
 
-	<h5 class="page-heading">
+	<h5 class="section-heading">
 		Migrate dashboards
 		<em style="font-size: 14px;padding-left: 10px;"><i class="fa fa-info-circle"></i> Import dashboards from Elasticsearch or InfluxDB</em>
 	</h5>
 
-	<div class="gf-form-group last">
+	<div class="gf-form-inline gf-form-group">
 		<div class="gf-form">
 			<div class="gf-form-label">Dashboard source</div>
-			<div>
-				<div class="gf-form-select-wrapper">
-					<select class="gf-form-input gf-size-auto" ng-model="sourceName" ng-options="f for f in datasources"></select>
-				</div>
-			</div>
-			<div class="gf-form-btn">
-				<button class="btn btn-success" ng-click="startImport()">Import</button>
+			<div class="gf-form-select-wrapper">
+				<select class="gf-form-input gf-size-auto" ng-model="sourceName" ng-options="f for f in datasources"></select>
 			</div>
 		</div>
+		<div class="gf-form">
+			<button class="btn btn-success gf-form-btn" ng-click="startImport()">Import</button>
+		</div>
 	</div>
 
-	<h5 class="page-heading" ng-if="importing">{{infoText}}</h5>
+	<h5 class="section-heading" ng-if="importing">{{infoText}}</h5>
 	<div class="editor-row" ng-if="importing">
 		<div class="editor-row row">
 			<table class="grafana-options-table span5">

+ 15 - 14
public/app/features/dashboard/partials/shareModal.html

@@ -136,22 +136,23 @@
 					<button class="btn btn-inverse btn-large" data-clipboard-text="{{snapshotUrl}}" clipboard-button><i class="fa fa-clipboard"></i> Copy Link</button>
 				</div>
 			</div>
+		</div>
 
-			<div ng-if="step === 1" class="gf-form-buttons-row">
-				<button class="btn btn-success btn-large" ng-click="createSnapshot()" ng-disabled="loading">
-					<i class="fa fa-save"></i>
-					Local Snapshot
-				</button>
-				<button class="btn btn-primary btn-large" ng-if="externalEnabled" ng-click="createSnapshot(true)" ng-disabled="loading">
-					<i class="fa fa-cloud-upload"></i>
-					{{sharingButtonText}}
-				</button>
-			</div>
-
-			<div class="pull-right" ng-if="step === 2" style="padding: 5px">
-				Did you make a mistake? <a class="pointer" ng-click="deleteSnapshot()" target="_blank">delete snapshot.</a>
-			</div>
+		<div ng-if="step === 1" class="gf-form-buttons-row">
+			<button class="btn btn-success btn-large" ng-click="createSnapshot()" ng-disabled="loading">
+				<i class="fa fa-save"></i>
+				Local Snapshot
+			</button>
+			<button class="btn btn-primary btn-large" ng-if="externalEnabled" ng-click="createSnapshot(true)" ng-disabled="loading">
+				<i class="fa fa-cloud-upload"></i>
+				{{sharingButtonText}}
+			</button>
 		</div>
 
+		<div class="pull-right" ng-if="step === 2" style="padding: 5px">
+			Did you make a mistake? <a class="pointer" ng-click="deleteSnapshot()" target="_blank">delete snapshot.</a>
+		</div>
 	</div>
+
+</div>
 </script>

+ 36 - 50
public/app/features/templating/partials/editor.html

@@ -12,7 +12,7 @@
 			</li>
 			<li class="gf-tabs-item" ng-show="mode === 'edit'">
 				<a class="gf-tabs-link" ng-class="{active: mode === 'edit'}">
-					{{current.name}}
+					Edit
 				</a>
 			</li>
 			<li class="gf-tabs-item" ng-show="mode === 'new'">
@@ -79,7 +79,7 @@
 						<input type="text" class="gf-form-input max-width-14" placeholder="name" ng-model='current.name'></input>
 					</div>
 					<div class="gf-form">
-						<span class="gf-form-label width-7">Type</span>
+						<span class="gf-form-label width-4">Type</span>
 						<div class="gf-form-select-wrapper max-width-10">
 							<select class="gf-form-input  max-width-10" ng-model="current.type" ng-options="f for f in ['query', 'interval', 'custom']" ng-change="typeChanged()"></select>
 						</div>
@@ -97,8 +97,9 @@
 						<input type="text" class="gf-form-input max-width-14" ng-model='current.label' placeholder="optional display name"></input>
 					</div>
 					<div class="gf-form">
-						<editor-checkbox class="width-10" text="Hide label" model="current.hideLabel" change="runQuery()"></editor-checkbox>
-						<editor-checkbox class="width-11" text="Hide variable" model="current.hideVariable" change="runQuery()"></editor-checkbox>
+						<span class="gf-form-label width-4">Hide</span>
+						<editor-checkbox text="Label" model="current.hideLabel" change="runQuery()"></editor-checkbox>
+						<editor-checkbox text="Variable" model="current.hideVariable" change="runQuery()"></editor-checkbox>
 					</div>
 				</div>
 
@@ -107,40 +108,35 @@
 			<h5 class="section-heading">Value Options</h5>
 			<div ng-show="current.type === 'interval'" class="gf-form-group">
 				<div class="gf-form">
-					<span class="gf-form-label width-7">Values</span>
-					<input type="text" class="gf-form-input max-width-28" placeholder="name" ng-model='current.query' placeholder="1m,10m,1h,6h,1d,7d" ng-model-onblur ng-change="runQuery()"></input>
+					<span class="gf-form-label width-9">Values</span>
+					<input type="text" class="gf-form-input" placeholder="name" ng-model='current.query' placeholder="1m,10m,1h,6h,1d,7d" ng-model-onblur ng-change="runQuery()"></input>
 				</div>
 				<div class="gf-form">
-					<editor-checkbox text="Include auto interval" model="current.auto" change="runQuery()"></editor-checkbox>
-					<span class="gf-form-label" ng-show="current.auto">
-						Auto interval steps <tip>How many times should the current time range be divided to calculate the value</tip>
-					</span>
-					<div class="gf-form-select-wrapper max-width-10" ng-show="current.auto">
-						<select class="gf-form-input" ng-model="current.auto_count" ng-options="f for f in [2,3,4,5,10,20,30,40,50,100,200,300,400,500]" ng-change="runQuery()"></select>
-					</div>
+					<span class="gf-form-label width-9">Auto option</span>
+					<editor-checkbox text="Enable" model="current.auto" change="runQuery()"></editor-checkbox>
 				</div>
-				<div class="gf-form">
-					<span class="gf-form-label" ng-show="current.auto">
-						Auto interval min value <tip>The calculated value will not go below this threshold</tip>
-					</span>
-					<input type="text" class="gf-form-input max-width-10" ng-show="current.auto" ng-model="current.auto_min" ng-change="runQuery()"></input>
+				<div class="gf-form-inline">
+					<div class="gf-form">
+						<span class="gf-form-label width-9" ng-show="current.auto">
+							Auto steps <tip>How many times should the current time range be divided to calculate the value</tip>
+						</span>
+						<div class="gf-form-select-wrapper max-width-10" ng-show="current.auto">
+							<select class="gf-form-input" ng-model="current.auto_count" ng-options="f for f in [2,3,4,5,10,20,30,40,50,100,200,300,400,500]" ng-change="runQuery()"></select>
+						</div>
+					</div>
+					<div class="gf-form">
+						<span class="gf-form-label" ng-show="current.auto">
+							Min interval <tip>The calculated value will not go below this threshold</tip>
+						</span>
+						<input type="text" class="gf-form-input max-width-10" ng-show="current.auto" ng-model="current.auto_min" ng-change="runQuery()" placeholder="10s"></input>
+					</div>
 				</div>
 			</div>
 
 			<div ng-show="current.type === 'custom'" class="gf-form-group">
 				<div class="gf-form">
 					<span class="gf-form-label width-13">Values seperated by comma</span>
-					<input type="text" class="gf-form-input max-width-22" ng-model='current.query' ng-blur="runQuery()" placeholder="1, 10, 20, myvalue"></input>
-				</div>
-				<div class="gf-form ">
-					<editor-checkbox class="width-13" text="All value" model="current.includeAll" change="runQuery()"></editor-checkbox>
-					<input ng-show="current.includeAll" type="text" class="gf-form-input max-width-22" ng-model='current.options[0].value' style="margin-left: 4px;"></input>
-				</div>
-				<div class="gf-form">
-					<span class="gf-form-label width-13" ng-show="current.includeAll">All format</span>
-					<div class="gf-form-select-wrapper max-width-10" ng-show="current.includeAll">
-						<select class="gf-form-input" ng-model="current.allFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'wildcard', 'regex wildcard', 'regex values', 'lucene', 'pipe']"></select>
-					</div>
+					<input type="text" class="gf-form-input" ng-model='current.query' ng-blur="runQuery()" placeholder="1, 10, 20, myvalue"></input>
 				</div>
 			</div>
 
@@ -156,22 +152,6 @@
 					</span>
 					<input type="text" class="gf-form-input" ng-model='current.regex' placeholder="/.*-(.*)-.*/" ng-model-onblur ng-change="runQuery()"></input>
 				</div>
-				<div class="gf-form">
-					<span class="gf-form-label width-7">All value</span>
-					<editor-checkbox class="width-13" text="Enable" model="current.includeAll" change="runQuery()"></editor-checkbox>
-				</div>
-				<div class="gf-form-inline" ng-show="current.includeAll">
-					<div class="gf-form">
-						<span class="gf-form-label width-7">All format</span>
-						<div class="gf-form-select-wrapper">
-							<select class="gf-form-input" ng-model="current.allFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'wildcard', 'regex wildcard', 'regex values', 'lucene', 'pipe']"></select>
-						</div>
-					</div>
-					<div class="gf-form max-width-30">
-						<span class="gf-form-label width-7">All value</span>
-						<input type="text" class="gf-form-input" ng-model='current.options[0].value'></input>
-					</div>
-				</div>
 				<div class="gf-form">
 					<span class="gf-form-label width-7">Update</span>
 					<editor-checkbox text="On Dashboard Load" model="current.refresh"></editor-checkbox>
@@ -180,13 +160,19 @@
 			</div>
 
 			<div class="gf-form-group" >
-				<h5 class="section-heading">Multi-value selection <tip>Enables multiple values to be selected at the same time</tip></h5>
+				<h5 class="section-heading">Selection Options</h5>
 				<div class="gf-form">
+					<span class="gf-form-label width-10">Multi-value</span>
 					<editor-checkbox text="Enable" model="current.multi" change="runQuery()"></editor-checkbox>
-					<span class="gf-form-label" ng-show="current.multi">Multi format</span>
-					<div class="gf-form-select-wrapper max-width-10" ng-show="current.multi">
-						<select class="gf-form-input" ng-model="current.multiFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'regex values', 'lucene', 'pipe']"></select>
-					</div>
+					<tip>Enables multiple values to be selected at the same time</tip>
+				</div>
+				<div class="gf-form">
+					<span class="gf-form-label width-10">Include All option</span>
+					<editor-checkbox class="width-13" text="Enable" model="current.includeAll" change="runQuery()"></editor-checkbox>
+				</div>
+				<div class="gf-form" ng-if="current.includeAll">
+					<span class="gf-form-label width-10">Custom all value</span>
+					<input type="text" class="gf-form-input max-width-15" g-model='current.allValue' placeholder="blank = auto"></input>
 				</div>
 			</div>
 

+ 83 - 41
public/app/features/templating/templateSrv.js

@@ -13,7 +13,7 @@ function (angular, _) {
     var self = this;
 
     this._regex = /\$(\w+)|\[\[([\s\S]+?)\]\]/g;
-    this._values = {};
+    this._index = {};
     this._texts = {};
     this._grafanaVariables = {};
 
@@ -23,38 +23,52 @@ function (angular, _) {
     };
 
     this.updateTemplateData = function() {
-      this._values = {};
-      this._texts = {};
+      this._index = {};
 
-      _.each(this.variables, function(variable) {
-        if (!variable.current || !variable.current.isNone && !variable.current.value) { return; }
-
-        this._values[variable.name] = this.renderVariableValue(variable);
-        this._texts[variable.name] = variable.current.text;
-      }, this);
+      for (var i = 0; i < this.variables.length; i++) {
+        var variable = this.variables[i];
+        if (!variable.current || !variable.current.isNone && !variable.current.value) {
+          continue;
+        }
+        this._index[variable.name] = variable;
+      }
     };
 
-    this.renderVariableValue = function(variable) {
-      var value = variable.current.value;
-      if (_.isString(value)) {
-        return value;
-      } else {
-        switch(variable.multiFormat) {
-          case "regex values": {
-            return '(' + value.join('|') + ')';
-          }
-          case "lucene": {
-            var quotedValues = _.map(value, function(val) {
-              return '\\\"' + val + '\\\"';
-            });
-            return '(' + quotedValues.join(' OR ') + ')';
+    function regexEscape(value) {
+      return value.replace(/[-[\]{}()*+!<=:?.\/\\^$|#\s,]/g, '\\$&');
+    }
+
+    function luceneEscape(value) {
+      return value.replace(/([\!\*\+\-\=<>\s\&\|\(\)\[\]\{\}\^\~\?\:\\/"])/g, "\\$1");
+    }
+
+    this.formatValue = function(value, format) {
+      switch(format) {
+        case "regex": {
+          if (typeof value === 'string') {
+            return regexEscape(value);
           }
-          case "pipe": {
-            return value.join('|');
+
+          var escapedValues = _.map(value, regexEscape);
+          return escapedValues.join('|');
+        }
+        case "lucene": {
+          if (typeof value === 'string') {
+            return luceneEscape(value);
           }
-          default:  {
-            return '{' + value.join(',') + '}';
+          var quotedValues = _.map(value, function(val) {
+            return '\"' + luceneEscape(val) + '\"';
+          });
+          return '(' + quotedValues.join(' OR ') + ')';
+        }
+        case "pipe": {
+          return value.join('|');
+        }
+        default:  {
+          if (typeof value === 'string') {
+            return value;
           }
+          return '{' + value.join(',') + '}';
         }
       }
     };
@@ -66,7 +80,7 @@ function (angular, _) {
     this.variableExists = function(expression) {
       this._regex.lastIndex = 0;
       var match = this._regex.exec(expression);
-      return match && (self._values[match[1] || match[2]] !== void 0);
+      return match && (self._index[match[1] || match[2]] !== void 0);
     };
 
     this.containsVariable = function(str, variableName) {
@@ -82,37 +96,66 @@ function (angular, _) {
       str = _.escape(str);
       this._regex.lastIndex = 0;
       return str.replace(this._regex, function(match, g1, g2) {
-        if (self._values[g1 || g2]) {
+        if (self._index[g1 || g2]) {
           return '<span class="template-variable">' + match + '</span>';
         }
         return match;
       });
     };
 
-    this.replace = function(target, scopedVars) {
+    this.getAllValue = function(variable) {
+      if (variable.allValue) {
+        return variable.allValue;
+      }
+      var values = [];
+      for (var i = 1; i < variable.options.length; i++) {
+        values.push(variable.options[i].value);
+      }
+      return values;
+    };
+
+    this.replace = function(target, scopedVars, format) {
       if (!target) { return target; }
 
-      var value;
+      var variable, systemValue, value;
       this._regex.lastIndex = 0;
 
       return target.replace(this._regex, function(match, g1, g2) {
         if (scopedVars) {
           value = scopedVars[g1 || g2];
-          if (value) { return value.value; }
+          if (value) {
+            return self.formatValue(value.value);
+          }
         }
 
-        value = self._values[g1 || g2];
-        if (!value) { return match; }
+        variable = self._index[g1 || g2];
+        if (!variable) {
+          return match;
+        }
 
-        return self._grafanaVariables[value] || value;
+        systemValue = self._grafanaVariables[variable.current.value];
+        if (systemValue) {
+          return self.formatValue(systemValue);
+        }
+
+        value = variable.current.value;
+        if (self.isAllValue(value)) {
+          value = self.getAllValue(variable);
+        }
+
+        var res = self.formatValue(value, format);
+        return res;
       });
     };
 
+    this.isAllValue = function(value) {
+      return value === '$__all' || Array.isArray(value) && value[0] === '$__all';
+    };
+
     this.replaceWithText = function(target, scopedVars) {
       if (!target) { return target; }
 
-      var value;
-      var text;
+      var variable;
       this._regex.lastIndex = 0;
 
       return target.replace(this._regex, function(match, g1, g2) {
@@ -121,11 +164,10 @@ function (angular, _) {
           if (option) { return option.text; }
         }
 
-        value = self._values[g1 || g2];
-        text = self._texts[g1 || g2];
-        if (!value) { return match; }
+        variable = self._index[g1 || g2];
+        if (!variable) { return match; }
 
-        return self._grafanaVariables[value] || text;
+        return self._grafanaVariables[variable.current.value] || variable.current.text;
       });
     };
 

+ 4 - 48
public/app/features/templating/templateValuesSrv.js

@@ -108,7 +108,6 @@ function (angular, _, kbn) {
       if (variable.type === 'custom' && variable.includeAll) {
         self.addAllOption(variable);
       }
-
     };
 
     this.updateOptions = function(variable) {
@@ -226,60 +225,17 @@ function (angular, _, kbn) {
 
       return _.map(_.keys(options).sort(), function(key) {
         var option = { text: key, value: key };
-
-        // check if values need to be regex escaped
-        if (self.shouldRegexEscape(variable)) {
-          option.value = self.regexEscape(option.value);
-        }
-
         return option;
       });
     };
 
-    this.shouldRegexEscape = function(variable) {
-      return (variable.includeAll || variable.multi) && variable.allFormat.indexOf('regex') !== -1;
-    };
-
-    this.regexEscape = function(value) {
-      return value.replace(/[-[\]{}()*+!<=:?.\/\\^$|#\s,]/g, '\\$&');
-    };
-
     this.addAllOption = function(variable) {
-      var allValue = '';
-      switch(variable.allFormat) {
-        case 'wildcard': {
-          allValue = '*';
-          break;
-        }
-        case 'regex wildcard': {
-          allValue = '.*';
-          break;
-        }
-        case 'lucene': {
-          var quotedValues = _.map(variable.options, function(val) {
-            return '\\\"' + val.text + '\\\"';
-          });
-          allValue = '(' + quotedValues.join(' OR ') + ')';
-          break;
-        }
-        case 'regex values': {
-          allValue = '(' + _.map(variable.options, function(option) {
-            return self.regexEscape(option.text);
-          }).join('|') + ')';
-          break;
-        }
-        case 'pipe': {
-          allValue = _.pluck(variable.options, 'text').join('|');
-          break;
-        }
-        default: {
-          allValue = '{';
-          allValue += _.pluck(variable.options, 'text').join(',');
-          allValue += '}';
-        }
+      if (variable.allValue) {
+        variable.options.unshift({text: 'All', value: variable.allValue});
+        return;
       }
 
-      variable.options.unshift({text: 'All', value: allValue});
+      variable.options.unshift({text: 'All', value: "$__all"});
     };
 
   });

+ 8 - 5
public/app/plugins/datasource/elasticsearch/datasource.js

@@ -47,15 +47,15 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
     };
 
     this._get = function(url) {
-      return this._request('GET', this.indexPattern.getIndexForToday() + url)
-      .then(function(results) {
+      return this._request('GET', this.indexPattern.getIndexForToday() + url).then(function(results) {
+        results.data.$$config = results.config;
         return results.data;
       });
     };
 
     this._post = function(url, data) {
-      return this._request('POST', url, data)
-      .then(function(results) {
+      return this._request('POST', url, data).then(function(results) {
+        results.data.$$config = results.config;
         return results.data;
       });
     };
@@ -170,7 +170,10 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
 
         var queryObj = this.queryBuilder.build(target);
         var esQuery = angular.toJson(queryObj);
-        var luceneQuery = angular.toJson(target.query || '*');
+        var luceneQuery = target.query || '*';
+        luceneQuery = templateSrv.replace(luceneQuery, options.scopedVars, 'lucene');
+        luceneQuery = angular.toJson(luceneQuery);
+
         // remove inner quotes
         luceneQuery = luceneQuery.substr(1, luceneQuery.length - 2);
         esQuery = esQuery.replace("$lucene_query", luceneQuery);

+ 17 - 1
public/app/plugins/datasource/elasticsearch/elastic_response.js

@@ -284,13 +284,29 @@ function (_, queryDef) {
     }
   };
 
+  ElasticResponse.prototype.getErrorFromElasticResponse = function(response, err) {
+    var result = {};
+    result.data = JSON.stringify(err, null, 4);
+    if (err.root_cause && err.root_cause.length > 0 && err.root_cause[0].reason) {
+      result.message = err.root_cause[0].reason;
+    } else {
+      result.message = err.reason || 'Unkown elatic error response';
+    }
+
+    if (response.$$config) {
+      result.config = response.$$config;
+    }
+
+    return result;
+  };
+
   ElasticResponse.prototype.getTimeSeries = function() {
     var seriesList = [];
 
     for (var i = 0; i < this.response.responses.length; i++) {
       var response = this.response.responses[i];
       if (response.error) {
-        throw { message: response.error };
+        throw this.getErrorFromElasticResponse(this.response, response.error);
       }
 
       if (response.hits && response.hits.hits.length > 0) {

+ 1 - 1
public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts

@@ -33,7 +33,7 @@ describe('ElasticDatasource', function() {
       var requestOptions;
       ctx.backendSrv.datasourceRequest = function(options) {
         requestOptions = options;
-        return ctx.$q.when({});
+        return ctx.$q.when({data: {}});
       };
 
       ctx.ds.testDatasource();

+ 2 - 2
public/app/plugins/datasource/influxdb/datasource.ts

@@ -34,8 +34,8 @@ export function InfluxDatasource(instanceSettings, $q, backendSrv, templateSrv)
       queryTargets.push(target);
 
       // build query
-      var queryModel = new InfluxQuery(target);
-      var query =  queryModel.render();
+      var queryModel = new InfluxQuery(target, templateSrv, options.scopedVars);
+      var query =  queryModel.render(true);
       query = query.replace(/\$interval/g, (target.interval || options.interval));
       return query;
 

+ 19 - 8
public/app/plugins/datasource/influxdb/influx_query.ts

@@ -8,9 +8,13 @@ export default class InfluxQuery {
   selectModels: any[];
   queryBuilder: any;
   groupByParts: any;
+  templateSrv: any;
+  scopedVars: any;
 
-  constructor(target) {
+  constructor(target, templateSrv?, scopedVars?) {
     this.target = target;
+    this.templateSrv = templateSrv;
+    this.scopedVars = scopedVars;
 
     target.policy = target.policy || 'default';
     target.dsType = 'influxdb';
@@ -126,7 +130,7 @@ export default class InfluxQuery {
     this.updatePersistedParts();
   }
 
-  private renderTagCondition(tag, index) {
+  private renderTagCondition(tag, index, interpolate) {
     var str = "";
     var operator = tag.operator;
     var value = tag.value;
@@ -135,7 +139,7 @@ export default class InfluxQuery {
     }
 
     if (!operator) {
-      if (/^\/.*\/$/.test(tag.value)) {
+      if (/^\/.*\/$/.test(value)) {
         operator = '=~';
       } else {
         operator = '=';
@@ -144,7 +148,12 @@ export default class InfluxQuery {
 
     // quote value unless regex
     if (operator !== '=~' && operator !== '!~') {
+      if (interpolate) {
+        value = this.templateSrv.replace(value, this.scopedVars);
+      }
       value = "'" + value.replace('\\', '\\\\') + "'";
+    } else if (interpolate){
+      value = this.templateSrv.replace(value, this.scopedVars, 'regex');
     }
 
     return str + '"' + tag.key + '" ' + operator + ' ' + value;
@@ -167,11 +176,15 @@ export default class InfluxQuery {
     return policy + measurement;
   }
 
-  render() {
+  render(interpolate?) {
     var target = this.target;
 
     if (target.rawQuery) {
-      return target.query;
+      if (interpolate) {
+        return this.templateSrv.replace(target.query, this.scopedVars, 'regex');
+      } else {
+        return target.query;
+      }
     }
 
     if (!target.measurement) {
@@ -196,7 +209,7 @@ export default class InfluxQuery {
 
     query += ' FROM ' + this.getMeasurementAndPolicy() + ' WHERE ';
     var conditions = _.map(target.tags, (tag, index) => {
-      return this.renderTagCondition(tag, index);
+      return this.renderTagCondition(tag, index, interpolate);
     });
 
     query += conditions.join(' ');
@@ -220,8 +233,6 @@ export default class InfluxQuery {
       query += ' fill(' + target.fill + ')';
     }
 
-    target.query = query;
-
     return query;
   }
 }

+ 2 - 1
public/app/plugins/datasource/influxdb/query_ctrl.ts

@@ -28,7 +28,7 @@ export class InfluxQueryCtrl extends QueryCtrl {
     super($scope, $injector);
 
     this.target = this.target;
-    this.queryModel = new InfluxQuery(this.target);
+    this.queryModel = new InfluxQuery(this.target, templateSrv, this.panel.scopedVars);
     this.queryBuilder = new InfluxQueryBuilder(this.target, this.datasource.database);
     this.groupBySegment = this.uiSegmentSrv.newPlusButton();
     this.resultFormats = [
@@ -154,6 +154,7 @@ export class InfluxQueryCtrl extends QueryCtrl {
   }
 
   toggleEditorMode() {
+    this.target.query = this.queryModel.render(false);
     this.target.rawQuery = !this.target.rawQuery;
   }
 

+ 18 - 17
public/app/plugins/datasource/influxdb/specs/influx_query_specs.ts

@@ -3,12 +3,13 @@ import {describe, beforeEach, it, sinon, expect} from 'test/lib/common';
 import InfluxQuery from '../influx_query';
 
 describe('InfluxQuery', function() {
+  var templateSrv = {replace: val => val};
 
   describe('render series with mesurement only', function() {
     it('should generate correct query', function() {
       var query = new InfluxQuery({
         measurement: 'cpu',
-      });
+      }, templateSrv, {});
 
       var queryText = query.render();
       expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE $timeFilter GROUP BY time($interval) fill(null)');
@@ -20,7 +21,7 @@ describe('InfluxQuery', function() {
       var query = new InfluxQuery({
         measurement: 'cpu',
         policy: '5m_avg'
-      });
+      }, templateSrv, {});
 
       var queryText = query.render();
       expect(queryText).to.be('SELECT mean("value") FROM "5m_avg"."cpu" WHERE $timeFilter GROUP BY time($interval) fill(null)');
@@ -39,7 +40,7 @@ describe('InfluxQuery', function() {
             {type: 'alias', params: ['text']},
           ]
         ]
-      });
+      }, templateSrv, {});
 
       var queryText = query.render();
       expect(queryText).to.be('SELECT mean("value") /100 AS "text" FROM "cpu" WHERE $timeFilter GROUP BY time($interval) fill(null)');
@@ -52,7 +53,7 @@ describe('InfluxQuery', function() {
         measurement: 'cpu',
         groupBy: [{type: 'time', params: ['auto']}],
         tags: [{key: 'hostname', value: 'server\\1'}]
-      });
+      }, templateSrv, {});
 
       var queryText = query.render();
 
@@ -65,7 +66,7 @@ describe('InfluxQuery', function() {
         measurement: 'cpu',
         groupBy: [{type: 'time', params: ['auto']}],
         tags: [{key: 'app', value: '/e.*/'}]
-      });
+      }, templateSrv, {});
 
       var queryText = query.render();
       expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE "app" =~ /e.*/ AND $timeFilter GROUP BY time($interval)');
@@ -78,7 +79,7 @@ describe('InfluxQuery', function() {
         measurement: 'cpu',
         groupBy: [{type: 'time', params: ['auto']}],
         tags: [{key: 'hostname', value: 'server1'}, {key: 'app', value: 'email', condition: "AND"}]
-      });
+      }, templateSrv, {});
 
       var queryText = query.render();
       expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE "hostname" = \'server1\' AND "app" = \'email\' AND ' +
@@ -92,7 +93,7 @@ describe('InfluxQuery', function() {
         measurement: 'cpu',
         groupBy: [{type: 'time', params: ['auto']}],
         tags: [{key: 'hostname', value: 'server1'}, {key: 'hostname', value: 'server2', condition: "OR"}]
-      });
+      }, templateSrv, {});
 
       var queryText = query.render();
       expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE "hostname" = \'server1\' OR "hostname" = \'server2\' AND ' +
@@ -106,7 +107,7 @@ describe('InfluxQuery', function() {
         measurement: 'cpu',
         tags: [],
         groupBy: [{type: 'time', interval: 'auto'}, {type: 'tag', params: ['host']}],
-      });
+      }, templateSrv, {});
 
       var queryText = query.render();
       expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE $timeFilter ' +
@@ -120,7 +121,7 @@ describe('InfluxQuery', function() {
         measurement: 'cpu',
         select: [[{type: 'field', params: ['value']}]],
         groupBy: [],
-      });
+      }, templateSrv, {});
       var queryText = query.render();
       expect(queryText).to.be('SELECT "value" FROM "cpu" WHERE $timeFilter');
     });
@@ -132,7 +133,7 @@ describe('InfluxQuery', function() {
         measurement: 'cpu',
         select: [[{type: 'field', params: ['value']}]],
         groupBy: [{type: 'time'}, {type: 'fill', params: ['0']}],
-      });
+      }, templateSrv, {});
       var queryText = query.render();
       expect(queryText).to.be('SELECT "value" FROM "cpu" WHERE $timeFilter GROUP BY time($interval) fill(0)');
     });
@@ -144,7 +145,7 @@ describe('InfluxQuery', function() {
       var query = new InfluxQuery({
         measurement: 'cpu',
         groupBy: [{type: 'time'}, {type: 'fill'}]
-      });
+      }, templateSrv, {});
 
       query.addGroupBy('tag(host)');
       expect(query.target.groupBy.length).to.be(3);
@@ -157,7 +158,7 @@ describe('InfluxQuery', function() {
       var query = new InfluxQuery({
         measurement: 'cpu',
         groupBy: []
-      });
+      }, templateSrv, {});
 
       query.addGroupBy('tag(host)');
       expect(query.target.groupBy.length).to.be(1);
@@ -172,7 +173,7 @@ describe('InfluxQuery', function() {
       var query = new InfluxQuery({
         measurement: 'cpu',
         select: [[{type: 'field', params: ['value']}]]
-      });
+      }, templateSrv, {});
 
       query.addSelectPart(query.selectModels[0], 'mean');
       expect(query.target.select[0].length).to.be(2);
@@ -183,7 +184,7 @@ describe('InfluxQuery', function() {
       var query = new InfluxQuery({
         measurement: 'cpu',
         select: [[{type: 'field', params: ['value']}, {type: 'mean'}]]
-      });
+      }, templateSrv, {});
 
       query.addSelectPart(query.selectModels[0], 'sum');
       expect(query.target.select[0].length).to.be(2);
@@ -194,7 +195,7 @@ describe('InfluxQuery', function() {
       var query = new InfluxQuery({
         measurement: 'cpu',
         select: [[{type: 'field', params: ['value']}, {type: 'mean'}, {type: 'alias'}]]
-      });
+      }, templateSrv, {});
 
       query.addSelectPart(query.selectModels[0], 'math');
       expect(query.target.select[0].length).to.be(4);
@@ -205,7 +206,7 @@ describe('InfluxQuery', function() {
       var query = new InfluxQuery({
         measurement: 'cpu',
         select: [[{type: 'field', params: ['value']}, {type: 'mean'}]]
-      });
+      }, templateSrv, {});
 
       query.addSelectPart(query.selectModels[0], 'math');
       expect(query.target.select[0].length).to.be(3);
@@ -216,7 +217,7 @@ describe('InfluxQuery', function() {
       var query = new InfluxQuery({
         measurement: 'cpu',
         select: [[{type: 'field', params: ['value']}, {type: 'mean'}, {type: 'math'}]]
-      });
+      }, templateSrv, {});
 
       query.addSelectPart(query.selectModels[0], 'math');
       expect(query.target.select[0].length).to.be(3);

+ 2 - 2
public/app/plugins/datasource/opentsdb/datasource.js

@@ -335,7 +335,7 @@ function (angular, _, dateMath) {
         query.tags = angular.copy(target.tags);
         if(query.tags){
           for(var key in query.tags){
-            query.tags[key] = templateSrv.replace(query.tags[key], options.scopedVars);
+            query.tags[key] = templateSrv.replace(query.tags[key], options.scopedVars, 'pipe');
           }
         }
       }
@@ -355,7 +355,7 @@ function (angular, _, dateMath) {
           } else {
             return target.metric === metricData.metric &&
             _.all(target.tags, function(tagV, tagK) {
-              interpolatedTagValue = templateSrv.replace(tagV, options.scopedVars);
+              interpolatedTagValue = templateSrv.replace(tagV, options.scopedVars, 'pipe');
               return metricData.tags[tagK] === interpolatedTagValue || interpolatedTagValue === "*";
             });
           }

+ 1 - 1
public/app/plugins/datasource/prometheus/datasource.ts

@@ -52,7 +52,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
       }
 
       var query: any = {};
-      query.expr = templateSrv.replace(target.expr, options.scopedVars);
+      query.expr = templateSrv.replace(target.expr, options.scopedVars, 'regex');
 
       var interval = target.interval || options.interval;
       var intervalFactor = target.intervalFactor || 1;

+ 1 - 2
public/app/plugins/datasource/prometheus/plugin.json

@@ -4,6 +4,5 @@
   "id": "prometheus",
 
   "metrics": true,
-  "annotations": true,
-  "defaultMatchFormat": "pipe"
+  "annotations": true
 }

+ 0 - 1
public/sass/base/_type.scss

@@ -131,7 +131,6 @@ mark,
 // Unordered and Ordered lists
 ul, ol {
   padding: 0;
-  padding-left: $spacer;
 }
 ul ul,
 ul ol,

+ 3 - 0
public/sass/components/_infobox.scss

@@ -16,5 +16,8 @@
   h5 {
     margin-top: 5px;
   }
+  ul {
+    padding-left: $spacer;
+  }
 }
 

+ 1 - 5
public/sass/pages/_dashboard.scss

@@ -13,10 +13,6 @@
   color: $variable;
 }
 
-.grafana-row {
-  margin-bottom: 5px;
-}
-
 .row-tab {
   .dropdown-menu-right {
     top: 0;
@@ -121,7 +117,7 @@ div.flot-text {
 }
 
 .panel-margin {
-  margin: 0 0.4rem 0.4rem 0.4rem;
+  margin: 0 0.4rem 0.8rem 0.4rem;
   display: block;
 }
 

+ 2 - 2
public/test/.jshintrc

@@ -3,7 +3,7 @@
   "bitwise":false,
   "curly": true,
   "eqnull": true,
-  "globalstrict": true,
+  "strict": true,
   "devel": true,
   "eqeqeq": true,
   "forin": false,
@@ -11,7 +11,7 @@
   "supernew": true,
   "expr": true,
   "indent": 2,
-  "latedef": true,
+  "latedef": false,
   "newcap": true,
   "noarg": true,
   "noempty": true,

+ 72 - 29
public/test/specs/templateSrv-specs.js

@@ -45,49 +45,93 @@ define([
       });
     });
 
-    describe('render variable to string values', function() {
+    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');
+      });
+    });
+
+    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.renderVariableValue({current: {value: 'test'}});
+        var result = _templateSrv.formatValue('test');
         expect(result).to.be('test');
       });
 
       it('multi value and glob format should render glob string', function() {
-        var result = _templateSrv.renderVariableValue({
-          multiFormat: 'glob',
-          current: {
-            value: ['test','test2'],
-          }
-        });
+        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.renderVariableValue({
-          multiFormat: 'lucene',
-          current: {
-            value: ['test','test2'],
-          }
-        });
-        expect(result).to.be('(\\\"test\\\" OR \\\"test2\\\")');
+        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.renderVariableValue({
-          multiFormat: 'regex values',
-          current: {
-            value: ['test','test2'],
-          }
-        });
-        expect(result).to.be('(test|test2)');
+        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.renderVariableValue({
-          multiFormat: 'pipe',
-          current: {
-            value: ['test','test2'],
-          }
-        });
+        var result = _templateSrv.formatValue(['test','test2'], 'pipe');
         expect(result).to.be('test|test2');
       });
 
@@ -194,7 +238,6 @@ define([
       });
     });
 
-
   });
 
 });

+ 9 - 87
public/test/specs/templateValuesSrv-specs.js

@@ -247,7 +247,7 @@ define([
       });
     });
 
-   describeUpdateVariable('regex pattern remove duplicates', function(scenario) {
+    describeUpdateVariable('regex pattern remove duplicates', function(scenario) {
       scenario.setup(function() {
         scenario.variable = { type: 'query', query: 'apps.*', name: 'test' };
         scenario.variable.regex = 'backend_01';
@@ -259,107 +259,29 @@ define([
       });
     });
 
-    describeUpdateVariable('with include All glob syntax', function(scenario) {
+    describeUpdateVariable('with include All', function(scenario) {
       scenario.setup(function() {
-        scenario.variable = { type: 'query', query: 'apps.*', name: 'test', includeAll: true, allFormat: 'glob' };
+        scenario.variable = {type: 'query', query: 'apps.*', name: 'test', includeAll: true};
         scenario.queryResult = [{text: 'backend1'}, {text: 'backend2'}, { text: 'backend3'}];
       });
 
-      it('should add All Glob option', function() {
-        expect(scenario.variable.options[0].value).to.be('{backend1,backend2,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 wildcard', function(scenario) {
+    describeUpdateVariable('with include all and custom value', function(scenario) {
       scenario.setup(function() {
-        scenario.variable = { type: 'query', query: 'apps.*', name: 'test', includeAll: true, allFormat: 'wildcard' };
+        scenario.variable = { type: 'query', query: 'apps.*', name: 'test', includeAll: true, allValue: '*' };
         scenario.queryResult = [{text: 'backend1'}, {text: 'backend2'}, { text: 'backend3'}];
       });
 
-      it('should add All wildcard option', function() {
+      it('should add All option with custom value', function() {
         expect(scenario.variable.options[0].value).to.be('*');
       });
     });
 
-    describeUpdateVariable('with include all wildcard', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = { type: 'query', query: 'apps.*', name: 'test', includeAll: true, allFormat: 'regex wildcard' };
-        scenario.queryResult = [{text: 'backend1'}, {text: 'backend2'}, { text: 'backend3'}];
-      });
-
-      it('should add All wildcard option', function() {
-        expect(scenario.variable.options[0].value).to.be('.*');
-      });
-    });
-
-    describeUpdateVariable('with include all regex values', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = { type: 'query', query: 'apps.*', name: 'test', includeAll: true, allFormat: 'wildcard' };
-        scenario.queryResult = [{text: 'backend1'}, {text: 'backend2'}, { text: 'backend3'}];
-      });
-
-      it('should add All wildcard option', function() {
-        expect(scenario.variable.options[0].value).to.be('*');
-      });
-    });
-
-    describeUpdateVariable('with include all glob no values', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = { type: 'query', query: 'apps.*', name: 'test', includeAll: true, allFormat: 'glob' };
-        scenario.queryResult = [];
-      });
-
-      it('should add empty glob', function() {
-        expect(scenario.variable.options[0].value).to.be('{}');
-      });
-    });
-
-    describeUpdateVariable('with include all lucene and values', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = { type: 'query', query: 'apps.*', name: 'test', includeAll: true, allFormat: 'lucene' };
-        scenario.queryResult = [{text: 'backend1'}, { text: 'backend2'}];
-      });
-
-      it('should add lucene glob', function() {
-        expect(scenario.variable.options[0].value).to.be('(\\\"backend1\\\" OR \\\"backend2\\\")');
-      });
-    });
-
-    describeUpdateVariable('with include all regex all values', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = { type: 'query', query: 'apps.*', name: 'test', includeAll: true, allFormat: 'regex values' };
-        scenario.queryResult = [{text: 'backend1'}, {text: 'backend2'}, { text: 'backend3'}];
-      });
-
-      it('should add empty glob', function() {
-        expect(scenario.variable.options[0].value).to.be('(backend1|backend2|backend3)');
-      });
-    });
-
-    describeUpdateVariable('with include all regex values and values require escaping', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = { type: 'query', query: 'apps.*', name: 'test', includeAll: true, allFormat: 'regex values' };
-        scenario.queryResult = [{text: '/root'}, {text: '/var'}, { text: '/lib'}];
-      });
-
-      it('should regex escape options', function() {
-        expect(scenario.variable.options[0].value).to.be('(\\/lib|\\/root|\\/var)');
-        expect(scenario.variable.options[1].value).to.be('\\/lib');
-        expect(scenario.variable.options[1].text).to.be('/lib');
-      });
-    });
-
-    describeUpdateVariable('with include all pipe all values', function(scenario) {
-      scenario.setup(function() {
-        scenario.variable = { type: 'query', query: 'apps.*', name: 'test', includeAll: true, allFormat: 'pipe' };
-        scenario.queryResult = [{text: 'backend1'}, {text: 'backend2'}, { text: 'backend3'}];
-      });
-
-      it('should add pipe delimited string', function() {
-        expect(scenario.variable.options[0].value).to.be('backend1|backend2|backend3');
-      });
-    });
-
   });
 
 });