Torkel Ödegaard 10 лет назад
Родитель
Сommit
35888c814c

+ 71 - 2
src/app/directives/templateParamSelector.js

@@ -91,26 +91,85 @@ function (angular, app, _, $) {
       return {
         scope: {
           variable: "=",
+          onUpdated: "&"
         },
         templateUrl: 'app/features/dashboard/partials/variableValueSelect.html',
         link: function(scope, elem) {
           var bodyEl = angular.element($window.document.body);
+          var variable = scope.variable;
 
           scope.show = function() {
             scope.selectorOpen = true;
             scope.giveFocus = 1;
+            scope.oldCurrentText = variable.current.text;
+
+            var currentValues = variable.current.value;
+
+            if (_.isString(currentValues)) {
+              currentValues  = [currentValues];
+            }
+
+            scope.options = _.map(variable.options, function(option) {
+              var op = {text: option.text, value: option.value};
+              if (_.indexOf(currentValues, option.value) >= 0) {
+                op.selected = true;
+              }
+              return op;
+            });
 
             $timeout(function() {
               bodyEl.on('click', scope.bodyOnClick);
             }, 0, false);
           };
 
+          scope.optionSelected = function(option) {
+            if (!variable.multi) {
+              _.each(scope.options, function(other) {
+                if (option !== other) {
+                  other.selected = false;
+                }
+              });
+            }
+
+            var selected = _.filter(scope.options, {selected: true});
+
+            if (selected.length === 0) {
+              // encode the first selected if no option is selected
+              scope.options[0].selected = true;
+              $timeout(function() {
+                scope.optionSelected(scope.options[0]);
+              });
+              return;
+            }
+
+            if (selected.length > 1) {
+              if (selected[0].text === 'All') {
+                selected = selected.slice(1, selected.length);
+              }
+            }
+
+            variable.current = {
+              text: _.pluck(selected, 'text').join(', '),
+              value: _.pluck(selected, 'value'),
+            };
+
+            // only single value
+            if (variable.current.value.length === 1) {
+              variable.current.value = selected[0].value;
+            }
+
+            scope.updateLinkText();
+          };
+
           scope.hide = function() {
             scope.selectorOpen = false;
+            if (scope.oldCurrentText !== variable.current.text) {
+              scope.onUpdated();
+            }
+
             bodyEl.off('click', scope.bodyOnClick);
           };
 
-
           scope.bodyOnClick = function(e) {
             var dropdown = elem.find('.variable-value-dropdown');
             if (dropdown.has(e.target).length === 0) {
@@ -118,7 +177,17 @@ function (angular, app, _, $) {
             }
           };
 
-          scope.$on('$destroy', function() {
+          scope.updateLinkText = function() {
+            scope.linkText = "";
+            if (!variable.hideLabel) {
+              scope.linkText = (variable.label || variable.name) + ': ';
+            }
+
+            scope.linkText += variable.current.text;
+          };
+
+          scope.$watchGroup(['variable.hideLabel', 'variable.name', 'variable.label'], function() {
+            scope.updateLinkText();
           });
         },
       };

+ 4 - 3
src/app/features/dashboard/partials/variableValueSelect.html

@@ -1,5 +1,6 @@
 <a ng-click="show()" class="variable-value-link">
-	{{variable.name}}: {{variable.current.text}} <i class="fa fa-caret-down"></i>
+	{{linkText}}
+	<i class="fa fa-caret-down"></i>
 </a>
 
 <div ng-if="selectorOpen" class="variable-value-dropdown">
@@ -11,9 +12,9 @@
 	</div>
 
 	<div class="variable-options-container" ng-if="!query.tagcloud">
-		<div class="variable-option pointer" bindonce ng-repeat="option in variable.options"
+		<div class="variable-option pointer" bindonce ng-repeat="option in options"
 				ng-class="{'selected': $index === selectedIndex }" ng-href="{{row.url}}">
-				<input class="cr1" id="var.option.{{$id}}" type="checkbox" ng-model="option.selected" ng-checked="asd">
+				<input class="cr1" id="var.option.{{$id}}" type="checkbox" ng-model="option.selected" ng-checked="option.selected" ng-change="optionSelected(option)">
 				<label for="var.option.{{$id}}" class="cr1"></label>
 				<label for="var.option.{{$id}}" class="checkbox-label">{{option.text}}</label>
 			</div>

+ 4 - 2
src/app/features/dashboard/submenuCtrl.js

@@ -26,8 +26,10 @@ function (angular, _) {
       $rootScope.$broadcast('refresh');
     };
 
-    $scope.setVariableValue = function(param, option) {
-      templateValuesSrv.setVariableValue(param, option);
+    $scope.variableUpdated = function(variable) {
+      templateValuesSrv.variableUpdated(variable).then(function() {
+        $rootScope.$broadcast('refresh');
+      });
     };
 
     $scope.init();

+ 3 - 1
src/app/features/templating/editorCtrl.js

@@ -17,6 +17,8 @@ function (angular, _) {
       options: [],
       includeAll: false,
       allFormat: 'glob',
+      multi: false,
+      multiFormat: 'glob',
     };
 
     $scope.init = function() {
@@ -75,7 +77,7 @@ function (angular, _) {
       if ($scope.current.datasource === void 0) {
         $scope.current.datasource = null;
         $scope.current.type = 'query';
-        $scope.current.allFormat = 'Glob';
+        $scope.current.allFormat = 'glob';
       }
     };
 

+ 13 - 1
src/app/features/templating/templateSrv.js

@@ -29,11 +29,23 @@ function (angular, _) {
       _.each(this.variables, function(variable) {
         if (!variable.current || !variable.current.value) { return; }
 
-        this._values[variable.name] = variable.current.value;
+        this._values[variable.name] = this.renderVariableValue(variable);
         this._texts[variable.name] = variable.current.text;
       }, this);
     };
 
+    this.renderVariableValue = function(variable) {
+      var value = variable.current.value;
+      if (_.isString(value)) {
+        return value;
+      } else {
+        if (variable.multiFormat === 'regex values') {
+          return '(' + value.join('|') + ')';
+        }
+        return '{' + value.join(',') + '}';
+      }
+    };
+
     this.setGrafanaVariable = function (name, value) {
       this._grafanaVariables[name] = value;
     };

+ 5 - 0
src/app/features/templating/templateValuesSrv.js

@@ -73,6 +73,11 @@ function (angular, _, kbn) {
         });
     };
 
+    this.variableUpdated = function(variable) {
+      templateSrv.updateTemplateData();
+      return this.updateOptionsInChildVariables(variable);
+    };
+
     this.updateOptionsInChildVariables = function(updatedVariable) {
       var promises = _.map(self.variables, function(otherVariable) {
         if (otherVariable === updatedVariable) {

+ 1 - 2
src/app/partials/submenu.html

@@ -1,11 +1,10 @@
 <div class="submenu-controls" ng-controller="SubmenuCtrl">
 	<div class="tight-form borderless">
 
-
 		<ul class="tight-form-list" ng-if="dashboard.templating.list.length > 0">
 
 			<li ng-repeat="variable in variables" class="tight-form-item template-param-name dropdown">
-				<variable-value-select variable="variable"></variable-value-select>
+				<variable-value-select variable="variable" on-updated="variableUpdated(variable)"></variable-value-select>
 			</li>
 
 			<!-- <li class="dropdown" ng&#45;repeat&#45;end> -->

+ 92 - 70
src/app/partials/templating_editor.html

@@ -37,7 +37,7 @@
 								{{variable.query}}
 							</td>
 							<td style="width: 1%">
-								<a ng-click="edit(variable)" class="btn btn-success btn-small">
+								<a ng-click="edit(variable)" class="btn btn-inverse btn-small">
 									<i class="fa fa-edit"></i>
 									Edit
 								</a>
@@ -56,97 +56,119 @@
 		</div>
 
 		<div ng-if="editor.index == 1 || (editor.index == 2 && !currentIsNew)">
-			<div class="editor-option">
-				<div class="editor-row">
-					<div class="editor-option">
-						<label class="small">Variable name</label>
-						<input type="text" class="input-medium" ng-model='current.name' placeholder="name" required></input>
-					</div>
-					<div class="editor-option">
-						<label class="small">Type</label>
-						<select class="input-medium" ng-model="current.type" ng-options="f for f in ['query', 'interval', 'custom']" ng-change="typeChanged()"></select>
-					</div>
-					<div class="editor-option" ng-show="current.type === 'query'">
-						<label class="small">Datasource</label>
-						<select class="input input-medium" ng-model="current.datasource" ng-options="f.value as f.name for f in datasources"></select>
-					</div>
-
-					<editor-opt-bool text="Refresh on load" show-if="current.type === 'query'"
-						tip="Check if you want values to be updated on dashboard load, will slow down dashboard load time"
-						model="current.refresh"></editor-opt-bool>
-				</div>
-
-				<div ng-show="current.type === 'interval'">
+			<div class="editor-row">
+				<div class="section">
+					<h5>General</h5>
 					<div class="editor-row">
 						<div class="editor-option">
-							<label class="small">Values</label>
-							<input type="text" class="input-xxlarge" ng-model='current.query' ng-blur="runQuery()" placeholder="name"></input>
+							<label class="small">Variable name</label>
+							<input type="text" class="input-medium" ng-model='current.name' placeholder="name" required></input>
+						</div>
+						<div class="editor-option">
+							<label class="small">Type</label>
+							<select class="input-small" ng-model="current.type" ng-options="f for f in ['query', 'interval', 'custom']" ng-change="typeChanged()"></select>
+						</div>
+						<div class="editor-option" ng-show="current.type === 'query'">
+							<label class="small">Datasource</label>
+							<select class="input input-medium" ng-model="current.datasource" ng-options="f.value as f.name for f in datasources"></select>
 						</div>
 					</div>
-					<div class="editor-row">
-						<editor-opt-bool text="Include auto interval" model="current.auto" change="runQuery()"></editor-opt-bool>
-						<div class="editor-option" ng-show="current.auto">
-							<label class="small">Auto interval steps <tip>How many steps, roughly, the interval is rounded and will not always match this count<tip></label>
-							<select class="input-mini" ng-model="current.auto_count" ng-options="f for f in [3,5,10,30,50,100,200]" ng-change="runQuery()"></select>
+
+					<div ng-show="current.type === 'interval'">
+						<div class="editor-row">
+							<div class="editor-option">
+								<label class="small">Values</label>
+								<input type="text" class="input-large" ng-model='current.query' ng-blur="runQuery()" placeholder="name"></input>
+							</div>
+							<editor-opt-bool text="Include auto interval" model="current.auto" change="runQuery()"></editor-opt-bool>
+							<div class="editor-option" ng-show="current.auto">
+								<label class="small">Auto interval steps <tip>How many steps, roughly, the interval is rounded and will not always match this count<tip></label>
+								<select class="input-mini" ng-model="current.auto_count" ng-options="f for f in [3,5,10,30,50,100,200]" ng-change="runQuery()"></select>
+							</div>
 						</div>
 					</div>
-				</div>
 
-				<div ng-show="current.type === 'custom'">
-					<div class="editor-row">
-						<div class="editor-option">
-							<label class="small">Values seperated by comma</label>
-							<input type="text" class="input-xxlarge" ng-model='current.query' ng-blur="runQuery()" placeholder="1, 10, 20, myvalue"></input>
+					<div ng-show="current.type === 'custom'">
+						<div class="editor-row">
+							<div class="editor-option">
+								<label class="small">Values seperated by comma</label>
+								<input type="text" class="input-xxlarge" ng-model='current.query' ng-blur="runQuery()" placeholder="1, 10, 20, myvalue"></input>
+							</div>
+						</div>
+					</div>
+
+					<div ng-show="current.type === 'query'">
+						<h5>Values Query</h5>
+						<div class="editor-row">
+							<div class="editor-option form-inline">
+								<label class="small">Variable values query</label>
+								<input type="text" class="input-xxlarge" ng-model='current.query' placeholder="apps.servers.*"></input>
+								<button class="btn btn-small btn-success" ng-click="runQuery()" bs-tooltip="'Execute query'" data-placement="right"><i class="fa fa-play"></i></button>
+							</div>
+						</div>
+
+						<div class="editor-row" style="margin: 15px 0">
+							<div class="editor-option form-inline">
+								<label class="small">regex (optional, if you want to extract part of a series name or metric node segment)</label>
+								<input type="text" class="input-xxlarge" ng-model='current.regex' placeholder="/.*-(.*)-.*/"></input>
+								<button class="btn btn-small btn-success" ng-click="runQuery()" bs-tooltip="'execute query'" data-placement="right"><i class="fa fa-play"></i></button>
+							</div>
+						</div>
+
+						<div class="editor-row" style="margin: 15px 0">
+							<editor-opt-bool text="Refresh on load" show-if="current.type === 'query'"
+								tip="Check if you want values to be updated on dashboard load, will slow down dashboard load time"
+								model="current.refresh"></editor-opt-bool>
+
+							<editor-opt-bool text="All option" model="current.includeAll" change="runQuery()"></editor-opt-bool>
+							<div class="editor-option" ng-show="current.includeAll">
+								<label class="small">All format</label>
+								<select class="input-medium" ng-model="current.allFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'wildcard', 'regex wildcard', 'regex values']"></select>
+							</div>
+							<div class="editor-option" ng-show="current.includeAll">
+								<label class="small">All value</label>
+								<input type="text" class="input-xlarge" ng-model='current.options[0].value'></input>
+							</div>
 						</div>
 					</div>
 				</div>
 
-				<div ng-show="current.type === 'query'">
-					<div class="editor-row">
-						<div class="editor-option form-inline">
-							<label class="small">Variable values query</label>
-							<input type="text" class="input-xxlarge" ng-model='current.query' placeholder="apps.servers.*"></input>
-							<button class="btn btn-small btn-success" ng-click="runQuery()" bs-tooltip="'Execute query'" data-placement="right"><i class="fa fa-play"></i></button>
+				<div class="section">
+					<div class="section">
+						<h5>Display options</h5>
+						<div class="editor-option">
+							<label class="small">Variable label</label>
+							<input type="text" class="input-medium" ng-model='current.label' placeholder=""></input>
 						</div>
+						<editor-opt-bool text="Hide Label" model="current.hideLabel"></editor-opt-bool>
 					</div>
 
-					<div class="editor-row" style="margin: 15px 0">
-						<div class="editor-option form-inline">
-							<label class="small">regex (optional, if you want to extract part of a series name or metric node segment)</label>
-							<input type="text" class="input-xxlarge" ng-model='current.regex' placeholder="/.*-(.*)-.*/"></input>
-							<button class="btn btn-small btn-success" ng-click="runQuery()" bs-tooltip="'execute query'" data-placement="right"><i class="fa fa-play"></i></button>
+					<div class="section">
+						<h5>Multi-value selection <tip>Enables multiple values to be selected at the same time</tip></h5>
+						<editor-opt-bool text="Enable" model="current.multi"></editor-opt-bool>
+						<div class="editor-option" ng-show="current.multi">
+							<label class="small">Multi value format</label>
+							<select class="input-medium" ng-model="current.multiFormat" ng-options="f for f in ['glob', 'regex values']"></select>
 						</div>
 					</div>
 
 					<div class="editor-row" style="margin: 15px 0">
-						<editor-opt-bool text="All option" model="current.includeAll" change="runQuery()"></editor-opt-bool>
-						<div class="editor-option" ng-show="current.includeAll">
-							<label class="small">All format</label>
-							<select class="input-medium" ng-model="current.allFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'wildcard', 'regex wildcard', 'regex values']"></select>
+						<div class="editor-option" >
+							<label class="small">Variable values (shows max 20)</label>
+							<ul class="grafana-options-list">
+								<li ng-repeat="option in current.options | limitTo: 20">
+									{{option.text}}
+								</li>
+							</ul>
 						</div>
-						<div class="editor-option" ng-show="current.includeAll">
-							<label class="small">All value</label>
-							<input type="text" class="input-xlarge" ng-model='current.options[0].value'></input>
-						</div>
-					</div>
-				</div>
-			</div>
-			<div class="editor-option">
-				<div class="editor-row">
-					<div class="editor-option" >
-						<label class="small">Variable values (showing 20/{{current.options.length}})</label>
-						<ul class="grafana-options-list">
-							<li ng-repeat="option in current.options | limitTo: 20">
-								{{option.text}}
-							</li>
-						</ul>
 					</div>
 				</div>
 			</div>
-		</div>
 
-		<button type="button" class="btn btn-success" ng-show="editor.index === 2" ng-click="update();">Update</button>
-		<button type="button" class="btn btn-success" ng-show="editor.index === 1" ng-click="add();">Add</button>
+			<button type="button" class="btn btn-success pull-right" ng-show="editor.index === 2" ng-click="update();">Update</button>
+			<button type="button" class="btn btn-success pull-right" ng-show="editor.index === 1" ng-click="add();">Add</button>
+
+			<div class="clearfix"></div>
+		</div>
 	</div>
-</div>
 

+ 5 - 0
src/css/less/forms.less

@@ -11,6 +11,11 @@ input[type="checkbox"].cr1 {
   display: none;
 }
 
+.editor-option label.cr1 {
+  display: inline-block;
+  margin: 5px 0 1px 0;
+}
+
 label.cr1 {
   display: inline-block;
   height: 19px;

+ 3 - 3
src/css/less/submenu.less

@@ -5,7 +5,7 @@
 }
 
 .submenu-controls {
-  margin: 20px 0px 15px 20px;
+  margin: 15px 0px 10px 13px;
 }
 
 .annotation-disabled, .annotation-disabled a {
@@ -25,8 +25,8 @@
 
 .variable-value-dropdown {
   position: absolute;
-  top: 30px;
-  width: 300px;
+  top: 43px;
+  min-width: 200px;
   height: 400px;
   background: @grafanaPanelBackground;
   box-shadow: 0px 0px 55px 0px black;

+ 28 - 0
src/test/specs/templateSrv-specs.js

@@ -29,6 +29,34 @@ define([
       });
     });
 
+    describe('render variable to string values', function() {
+      it('single value should return value', function() {
+        var result = _templateSrv.renderVariableValue({current: {value: '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'],
+          }
+        });
+        expect(result).to.be('{test,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)');
+      });
+
+    });
+
     describe('can check if variable exists', function() {
       beforeEach(function() {
         _templateSrv.init([{ name: 'test', current: { value: 'oogle' } }]);