Bläddra i källkod

Merge branch 'query-editor-breakout'

Conflicts:
	CHANGELOG.md
Torkel Ödegaard 10 år sedan
förälder
incheckning
9a9c9b2b12
53 ändrade filer med 1701 tillägg och 1383 borttagningar
  1. 11 1
      CHANGELOG.md
  2. 40 0
      docs/sources/datasources/plugin_api.md
  3. 9 1
      pkg/api/datasources.go
  4. 7 1
      pkg/api/frontendsettings.go
  5. 5 2
      public/app/directives/giveFocus.js
  6. 1 1
      public/app/directives/metric.segment.js
  7. 1 2
      public/app/features/annotations/partials/editor.html
  8. 102 29
      public/app/features/panel/panelDirective.js
  9. 32 3
      public/app/features/panel/panelSrv.js
  10. 4 1
      public/app/features/templating/editorCtrl.js
  11. 37 13
      public/app/partials/metrics.html
  12. 2 1
      public/app/plugins/datasource/elasticsearch/datasource.js
  13. 13 0
      public/app/plugins/datasource/elasticsearch/directives.js
  14. 1 40
      public/app/plugins/datasource/grafana/datasource.js
  15. 13 0
      public/app/plugins/datasource/grafana/directives.js
  16. 53 13
      public/app/plugins/datasource/grafana/partials/query.editor.html
  17. 2 6
      public/app/plugins/datasource/grafana/plugin.json
  18. 12 15
      public/app/plugins/datasource/graphite/datasource.js
  19. 21 0
      public/app/plugins/datasource/graphite/directives.js
  20. 61 210
      public/app/plugins/datasource/graphite/partials/query.editor.html
  21. 132 0
      public/app/plugins/datasource/graphite/partials/query.options.html
  22. 1 3
      public/app/plugins/datasource/graphite/plugin.json
  23. 5 20
      public/app/plugins/datasource/graphite/queryCtrl.js
  24. 1 0
      public/app/plugins/datasource/influxdb/datasource.js
  25. 21 0
      public/app/plugins/datasource/influxdb/directives.js
  26. 55 160
      public/app/plugins/datasource/influxdb/partials/query.editor.html
  27. 87 0
      public/app/plugins/datasource/influxdb/partials/query.options.html
  28. 1 3
      public/app/plugins/datasource/influxdb/plugin.json
  29. 4 0
      public/app/plugins/datasource/influxdb/queryCtrl.js
  30. 1 0
      public/app/plugins/datasource/influxdb_08/datasource.js
  31. 21 0
      public/app/plugins/datasource/influxdb_08/directives.js
  32. 159 250
      public/app/plugins/datasource/influxdb_08/partials/query.editor.html
  33. 85 0
      public/app/plugins/datasource/influxdb_08/partials/query.options.html
  34. 1 3
      public/app/plugins/datasource/influxdb_08/plugin.json
  35. 1 0
      public/app/plugins/datasource/kairosdb/datasource.js
  36. 17 0
      public/app/plugins/datasource/kairosdb/directives.js
  37. 306 362
      public/app/plugins/datasource/kairosdb/partials/query.editor.html
  38. 37 0
      public/app/plugins/datasource/kairosdb/partials/query.options.html
  39. 1 2
      public/app/plugins/datasource/kairosdb/plugin.json
  40. 35 0
      public/app/plugins/datasource/mixed/datasource.js
  41. 12 0
      public/app/plugins/datasource/mixed/plugin.json
  42. 1 0
      public/app/plugins/datasource/opentsdb/datasource.js
  43. 16 0
      public/app/plugins/datasource/opentsdb/directives.js
  44. 190 223
      public/app/plugins/datasource/opentsdb/partials/query.editor.html
  45. 1 2
      public/app/plugins/datasource/opentsdb/plugin.json
  46. 1 0
      public/app/plugins/datasource/opentsdb/queryCtrl.js
  47. 12 1
      public/app/services/datasourceSrv.js
  48. 0 8
      public/css/less/bootswatch.dark.less
  49. 9 4
      public/css/less/tightform.less
  50. 1 3
      public/test/specs/graphiteTargetCtrl-specs.js
  51. 58 0
      public/test/specs/panelSrv-specs.js
  52. 1 0
      public/test/test-main.js
  53. 1 0
      public/vendor/bootstrap/less/bootstrap.less

+ 11 - 1
CHANGELOG.md

@@ -1,12 +1,22 @@
 # 2.2 (unreleased)
 
-**New Features && Enhancements**
+** New Feature: Mix data sources **
+A built in data source is now available named `-- Mixed --`, When picked in the metrics tab,
+it allows you to add queries of differnet data source types & instances to the same graph/panel!
+[Issue #436](https://github.com/grafana/grafana/issues/436)
+
+** Other new Features && Enhancements**
 - [Issue #2457](https://github.com/grafana/grafana/issues/2457). Admin: admin page for all grafana organizations (list / edit view)
 - [Issue #1186](https://github.com/grafana/grafana/issues/1186). Time Picker: New option `today`, will set time range from midnight to now
 
 **Fixes**
 - [Issue #2490](https://github.com/grafana/grafana/issues/2490). Graphite: Dashboard import was broken in 2.1 and 2.1.1, working now
 
+**Breaking Changes**
+- Notice to makers/users of custom data sources, there is a minor breaking change in 2.2 that
+require and update to custom data sources for them to work in 2.2. [Read this doc](https://github.com/grafana/grafana/tree/master/docs/sources/datasources/plugin_api.md) for more on the
+data source api change.
+
 # 2.1.x (currently unreleased patch branch)
 
 **Fixes**

+ 40 - 0
docs/sources/datasources/plugin_api.md

@@ -0,0 +1,40 @@
+----
+page_title: Data source Plugin API
+page_description: Data Source Plugin Description
+page_keywords: grafana, data source, plugin, api, docs
+---
+
+# Data source plugin API
+
+All data sources in Grafana are implemented as plugins.
+
+## Breaking change in 2.2
+
+In Grafana 2.2 a breaking change was introduced for how data source query editors
+are structured, defined and loaded. This was in order to support mixing multiple data sources
+in the same panel.
+
+In Grafana 2.2, the query editor is no longer defined using the partials section in
+`plugin.json`, but defined via an angular directive named using convention naming
+scheme like `metricQueryEditor<data source type name>`. For example
+
+Graphite defines a directive like this:
+
+```javascript
+module.directive('metricQueryEditorGraphite', function() {
+  return {controller: 'GraphiteQueryCtrl', templateUrl: 'app/plugins/datasource/graphite/partials/query.editor.html'};
+});
+```
+
+Even though the data source type name is with lowercase `g`, the directive uses capital `G` in `Graphite` because
+that is how angular directives needs to be named in order to match an element with name `<metric-query-editor-graphite />`.
+You also specify the query controller here instead of in the query.editor.html partial like before.
+
+### query.editor.html
+
+This partial needs to be updated, remove the `np-repeat` this is done in the outer partial now,m the query.editor.html
+should only render a single query. Take a look at the Graphite or InfluxDB partials for `query.editor.html` for reference.
+You should also add a `tight-form-item` with `{{target.refId}}`, all queries needs to be assigned a letter (`refId`).
+These query reference letters are going to be utilized in a later feature.
+
+

+ 9 - 1
pkg/api/datasources.go

@@ -112,5 +112,13 @@ func UpdateDataSource(c *middleware.Context, cmd m.UpdateDataSourceCommand) {
 }
 
 func GetDataSourcePlugins(c *middleware.Context) {
-	c.JSON(200, plugins.DataSources)
+	dsList := make(map[string]interface{})
+
+	for key, value := range plugins.DataSources {
+		if value.(map[string]interface{})["builtIn"] == nil {
+			dsList[key] = value
+		}
+	}
+
+	c.JSON(200, dsList)
 }

+ 7 - 1
pkg/api/frontendsettings.go

@@ -86,11 +86,17 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
 
 	// add grafana backend data source
 	grafanaDatasourceMeta, _ := plugins.DataSources["grafana"]
-	datasources["grafana"] = map[string]interface{}{
+	datasources["-- Grafana --"] = map[string]interface{}{
 		"type": "grafana",
 		"meta": grafanaDatasourceMeta,
 	}
 
+	// add mixed backend data source
+	datasources["-- Mixed --"] = map[string]interface{}{
+		"type": "mixed",
+		"meta": plugins.DataSources["mixed"],
+	}
+
 	if defaultDatasource == "" {
 		defaultDatasource = "grafana"
 	}

+ 5 - 2
public/app/directives/giveFocus.js

@@ -16,8 +16,11 @@ function (angular) {
         }
         setTimeout(function() {
           element.focus();
-          var pos = element.val().length * 2;
-          element[0].setSelectionRange(pos, pos);
+          var domEl = element[0];
+          if (domEl.setSelectionRange) {
+            var pos = element.val().length * 2;
+            domEl.setSelectionRange(pos, pos);
+          }
         }, 200);
       },true);
     };

+ 1 - 1
public/app/directives/metric.segment.js

@@ -15,7 +15,7 @@ function (angular, app, _, $) {
                             ' spellcheck="false" style="display:none"></input>';
 
       var buttonTemplate = '<a class="tight-form-item" ng-class="segment.cssClass" ' +
-        'tabindex="1" focus-me="segment.focus" ng-bind-html="segment.html"></a>';
+        'tabindex="1" give-focus="segment.focus" ng-bind-html="segment.html"></a>';
 
       return {
         scope: {

+ 1 - 2
public/app/features/annotations/partials/editor.html

@@ -72,8 +72,7 @@
 				</div>
 			</div>
 
-			<div ng-include src="currentDatasource.meta.partials.annotations">
-			</div>
+			<datasource-editor-view datasource="currentAnnotation.datasource" name="annotations-query-editor"></datasource-editor-view>
 
 			<br>
 			<button ng-show="editor.index === 1" type="button" class="btn btn-success" ng-click="add()">Add</button>

+ 102 - 29
public/app/features/panel/panelDirective.js

@@ -6,35 +6,108 @@ define([
 function (angular, $, config) {
   'use strict';
 
-  angular
-    .module('grafana.directives')
-    .directive('panelLoader', function($compile, $parse) {
-      return {
-        restrict: 'E',
-        link: function(scope, elem, attr) {
-          var getter = $parse(attr.type), panelType = getter(scope);
-          var panelPath = config.panels[panelType].path;
-
-          scope.require([panelPath + "/module"], function () {
-            var panelEl = angular.element(document.createElement('grafana-panel-' + panelType));
-            elem.append(panelEl);
-            $compile(panelEl)(scope);
-          });
+  var module = angular.module('grafana.directives');
+
+  module.directive('panelLoader', function($compile, $parse) {
+    return {
+      restrict: 'E',
+      link: function(scope, elem, attr) {
+        var getter = $parse(attr.type), panelType = getter(scope);
+        var panelPath = config.panels[panelType].path;
+
+        scope.require([panelPath + "/module"], function () {
+          var panelEl = angular.element(document.createElement('grafana-panel-' + panelType));
+          elem.append(panelEl);
+          $compile(panelEl)(scope);
+        });
+      }
+    };
+  });
+
+  module.directive('grafanaPanel', function() {
+    return {
+      restrict: 'E',
+      templateUrl: 'app/features/panel/partials/panel.html',
+      transclude: true,
+      link: function(scope, elem) {
+        var panelContainer = elem.find('.panel-container');
+
+        scope.$watchGroup(['fullscreen', 'height', 'panel.height', 'row.height'], function() {
+          panelContainer.css({ minHeight: scope.height || scope.panel.height || scope.row.height, display: 'block' });
+          elem.toggleClass('panel-fullscreen', scope.fullscreen ? true : false);
+        });
+      }
+    };
+  });
+
+  module.service('dynamicDirectiveSrv', function($compile, $parse, datasourceSrv) {
+    var self = this;
+
+    this.addDirective = function(options, type, editorScope) {
+      var panelEl = angular.element(document.createElement(options.name + '-' + type));
+      options.parentElem.append(panelEl);
+      $compile(panelEl)(editorScope);
+    };
+
+    this.define = function(options) {
+      var editorScope;
+      options.scope.$watch(options.datasourceProperty, function(newVal) {
+        if (editorScope) {
+          editorScope.$destroy();
+          options.parentElem.empty();
         }
-      };
-    }).directive('grafanaPanel', function() {
-      return {
-        restrict: 'E',
-        templateUrl: 'app/features/panel/partials/panel.html',
-        transclude: true,
-        link: function(scope, elem) {
-          var panelContainer = elem.find('.panel-container');
-
-          scope.$watchGroup(['fullscreen', 'height', 'panel.height', 'row.height'], function() {
-            panelContainer.css({ minHeight: scope.height || scope.panel.height || scope.row.height, display: 'block' });
-            elem.toggleClass('panel-fullscreen', scope.fullscreen ? true : false);
+
+        editorScope = options.scope.$new();
+        datasourceSrv.get(newVal).then(function(ds) {
+          self.addDirective(options, ds.meta.type, editorScope);
+        });
+      });
+    };
+  });
+
+  module.directive('queryEditorLoader', function($compile, $parse, datasourceSrv) {
+    return {
+      restrict: 'E',
+      link: function(scope, elem) {
+        var editorScope;
+
+        scope.$watch("panel.datasource", function() {
+          var datasource = scope.target.datasource || scope.panel.datasource;
+
+          datasourceSrv.get(datasource).then(function(ds) {
+            if (editorScope) {
+              editorScope.$destroy();
+              elem.empty();
+            }
+
+            editorScope = scope.$new();
+            editorScope.datasource = ds;
+
+            if (!scope.target.refId) {
+              scope.target.refId = 'A';
+            }
+
+            var panelEl = angular.element(document.createElement('metric-query-editor-' + ds.meta.type));
+            elem.append(panelEl);
+            $compile(panelEl)(editorScope);
           });
-        }
-      };
-    });
+        });
+      }
+    };
+  });
+
+  module.directive('datasourceEditorView', function(dynamicDirectiveSrv) {
+    return {
+      restrict: 'E',
+      link: function(scope, elem, attrs) {
+        dynamicDirectiveSrv.define({
+          datasourceProperty: attrs.datasource,
+          name: attrs.name,
+          scope: scope,
+          parentElem: elem,
+        });
+      }
+    };
+  });
+
 });

+ 32 - 3
public/app/features/panel/panelSrv.js

@@ -43,8 +43,21 @@ function (angular, _, config) {
         });
       };
 
-      $scope.addDataQuery = function() {
-        $scope.panel.targets.push({target: ''});
+      $scope.addDataQuery = function(datasource) {
+        var letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+        var target = {};
+
+        if (datasource) {
+          target.datasource = datasource.name;
+        }
+
+        target.refId = _.find(letters, function(refId) {
+          return _.every($scope.panel.targets, function(other) {
+            return other.refId !== refId;
+          });
+        });
+
+        $scope.panel.targets.push(target);
       };
 
       $scope.removeDataQuery = function (query) {
@@ -53,7 +66,23 @@ function (angular, _, config) {
       };
 
       $scope.setDatasource = function(datasource) {
-        $scope.panel.datasource = datasource;
+        // switching to mixed
+        if (datasource.meta.mixed) {
+          _.each($scope.panel.targets, function(target) {
+            target.datasource = $scope.panel.datasource;
+            if (target.datasource === null) {
+              target.datasource = config.defaultDatasource;
+            }
+          });
+        }
+        // switching from mixed
+        else if ($scope.datasource && $scope.datasource.meta.mixed) {
+          _.each($scope.panel.targets, function(target) {
+            delete target.datasource;
+          });
+        }
+
+        $scope.panel.datasource = datasource.value;
         $scope.datasource = null;
         $scope.get_data();
       };

+ 4 - 1
public/app/features/templating/editorCtrl.js

@@ -23,7 +23,10 @@ function (angular, _) {
 
     $scope.init = function() {
       $scope.editor = { index: 0 };
-      $scope.datasources = datasourceSrv.getMetricSources();
+      $scope.datasources = _.filter(datasourceSrv.getMetricSources(), function(ds) {
+        return !ds.meta.builtIn;
+      });
+
       $scope.variables = templateSrv.variables;
       $scope.reset();
 

+ 37 - 13
public/app/partials/metrics.html

@@ -1,24 +1,48 @@
-<div ng-include src="datasource.meta.partials.query"></div>
+<div class="editor-row">
 
+	<div class="tight-form-container">
+		<query-editor-loader ng-repeat="target in panel.targets" ng-class="{'tight-form-disabled': target.hide}" >
+		</query-editor-loader>
+	</div>
+
+	<div style="margin: 20px 0 0 0">
+		<button class="btn btn-inverse" ng-click="addDataQuery()" ng-hide="datasource.meta.mixed">
+			<i class="fa fa-plus"></i>&nbsp;
+			Query
+		</button>
+
+		<div class="dropdown" ng-if="datasource.meta.mixed">
+			<button class="btn btn-inverse dropdown-toggle" data-toggle="dropdown">
+				<i class="fa fa-plus"></i>&nbsp;
+				Query &nbsp; <span class="caret"></span>
+			</button>
+
+			<ul class="dropdown-menu" role="menu">
+				<li ng-repeat="datasource in datasources" role="menuitem" ng-hide="datasource.meta.builtIn">
+					<a ng-click="addDataQuery(datasource);">{{datasource.name}}</a>
+				</li>
+			</ul>
+		</div>
+
+	</div>
+
+	<datasource-editor-view datasource="panel.datasource" name="metric-query-options"></datasource-editor-view>
+</div>
 
 <div class="editor-row" style="margin-top: 30px">
-	<button class="btn btn-inverse pull-right" ng-click="addDataQuery(panel.target)">
-		<i class="fa fa-plus"></i>&nbsp;
-		Add query
-	</button>
 
-  <div class="pull-right dropdown" style="margin-right: 10px;">
+	<div class="pull-right dropdown" style="margin-right: 10px;">
 		<button class="btn btn-inverse dropdown-toggle" data-toggle="dropdown" bs-tooltip="'Datasource'">
 			<i class="fa fa-database"></i>&nbsp;
-			{{datasource.name}} <span class="caret"></span>
+			{{datasource.name}} &nbsp; <span class="caret"></span>
 		</button>
 
-    <ul class="dropdown-menu" role="menu">
-      <li ng-repeat="datasource in datasources" role="menuitem">
-        <a ng-click="setDatasource(datasource.value);">{{datasource.name}}</a>
-      </li>
-    </ul>
-  </div>
+		<ul class="dropdown-menu" role="menu">
+			<li ng-repeat="datasource in datasources" role="menuitem">
+				<a ng-click="setDatasource(datasource);">{{datasource.name}}</a>
+			</li>
+		</ul>
+	</div>
 
 	<div class="clearfix"></div>
 </div>

+ 2 - 1
public/app/plugins/datasource/elasticsearch/datasource.js

@@ -3,7 +3,8 @@ define([
   'lodash',
   'config',
   'kbn',
-  'moment'
+  'moment',
+  './directives'
 ],
 function (angular, _, config, kbn, moment) {
   'use strict';

+ 13 - 0
public/app/plugins/datasource/elasticsearch/directives.js

@@ -0,0 +1,13 @@
+define([
+  'angular',
+],
+function (angular) {
+  'use strict';
+
+  var module = angular.module('grafana.directives');
+
+  module.directive('annotationsQueryEditorElasticsearch', function() {
+    return {templateUrl: 'app/plugins/datasource/elasticsearch/partials/annotations.editor.html'};
+  });
+
+});

+ 1 - 40
public/app/plugins/datasource/grafana/datasource.js

@@ -2,6 +2,7 @@ define([
   'angular',
   'lodash',
   'kbn',
+  './directives',
 ],
 function (angular, _, kbn) {
   'use strict';
@@ -13,16 +14,6 @@ function (angular, _, kbn) {
     function GrafanaDatasource() {
     }
 
-    GrafanaDatasource.prototype.getDashboard = function(slug, isTemp) {
-      var url = '/dashboards/' + slug;
-
-      if (isTemp) {
-        url = '/temp/' + slug;
-      }
-
-      return backendSrv.get('/api/dashboards/db/' + slug);
-    };
-
     GrafanaDatasource.prototype.query = function(options) {
       // get from & to in seconds
       var from = kbn.parseDate(options.range.from).getTime();
@@ -35,36 +26,6 @@ function (angular, _, kbn) {
       return $q.when([]);
     };
 
-    GrafanaDatasource.prototype.starDashboard = function(dashId) {
-      return backendSrv.post('/api/user/stars/dashboard/' + dashId);
-    };
-
-    GrafanaDatasource.prototype.unstarDashboard = function(dashId) {
-      return backendSrv.delete('/api/user/stars/dashboard/' + dashId);
-    };
-
-    GrafanaDatasource.prototype.saveDashboard = function(dashboard) {
-      return backendSrv.post('/api/dashboards/db/', { dashboard: dashboard })
-        .then(function(data) {
-          return { title: dashboard.title, url: '/dashboard/db/' + data.slug };
-        }, function(err) {
-          err.isHandled = true;
-          err.data = err.data || {};
-          throw err.data.message || "Unknown error";
-        });
-    };
-
-    GrafanaDatasource.prototype.deleteDashboard = function(id) {
-      return backendSrv.delete('/api/dashboards/db/' + id);
-    };
-
-    GrafanaDatasource.prototype.searchDashboards = function(query) {
-      return backendSrv.get('/api/search/', query)
-        .then(function(data) {
-          return data;
-        });
-    };
-
     return GrafanaDatasource;
 
   });

+ 13 - 0
public/app/plugins/datasource/grafana/directives.js

@@ -0,0 +1,13 @@
+define([
+  'angular',
+],
+function (angular) {
+  'use strict';
+
+  var module = angular.module('grafana.directives');
+
+  module.directive('metricQueryEditorGrafana', function() {
+    return {templateUrl: 'app/plugins/datasource/grafana/partials/query.editor.html'};
+  });
+
+});

+ 53 - 13
public/app/plugins/datasource/grafana/partials/query.editor.html

@@ -1,16 +1,56 @@
+<div class="tight-form">
+	<ul class="tight-form-list pull-right">
+		<li ng-show="parserError" class="tight-form-item">
+			<a bs-tooltip="parserError" style="color: rgb(229, 189, 28)" role="menuitem">
+				<i class="fa fa-warning"></i>
+			</a>
+		</li>
+		<li class="tight-form-item">
+			<div class="dropdown">
+				<a class="pointer dropdown-toggle" data-toggle="dropdown" tabindex="1">
+					<i class="fa fa-bars"></i>
+				</a>
+				<ul class="dropdown-menu pull-right" role="menu">
+					<li role="menuitem">
+						<a  tabindex="1"
+							ng-click="duplicate()">
+							Duplicate
+						</a>
+					</li>
+					<li role="menuitem">
+						<a  tabindex="1"
+							ng-click="moveMetricQuery($index, $index-1)">
+							Move up
+						</a>
+					</li>
+					<li role="menuitem">
+						<a  tabindex="1"
+							ng-click="moveMetricQuery($index, $index+1)">
+							Move down
+						</a>
+					</li>
+				</ul>
+			</div>
+		</li>
+		<li class="tight-form-item last">
+			<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
+				<i class="fa fa-remove"></i>
+			</a>
+		</li>
+	</ul>
 
-<div class="fluid-row" style="margin-top: 20px">
-	<div class="span2"></div>
-	<div class="grafana-info-box span8">
-		<h5>Test graph</h5>
-
-		<p>
-		This is just a test data source that generates random walk series. If this is your only data source
-		open the left side menu and navigate to the data sources admin screen and add your data sources (you need to be
-		logged in to do this). You can change data source using the button to the left of the <strong>Add query</strong> button.
-		</p>
-	</div>
-	<div class="span2"></div>
-
+	<ul class="tight-form-list">
+		<li class="tight-form-item" style="min-width: 15px; text-align: center">
+			{{target.refId}}
+		</li>
+		<li>
+			<a class="tight-form-item" ng-click="target.hide = !target.hide; get_data();" role="menuitem">
+				<i class="fa fa-eye"></i>
+			</a>
+		</li>
+		<li class="tight-form-item">
+			Test metric (fake data source)
+		</li>
+	</ul>
 	<div class="clearfix"></div>
 </div>

+ 2 - 6
public/app/plugins/datasource/grafana/plugin.json

@@ -1,15 +1,11 @@
 {
   "pluginType": "datasource",
-  "name": "Grafana (for testing)",
+  "name": "Grafana",
+  "builtIn": true,
 
   "type": "grafana",
   "serviceName": "GrafanaDatasource",
 
   "module": "plugins/datasource/grafana/datasource",
-
-  "partials": {
-    "query": "app/plugins/datasource/grafana/partials/query.editor.html"
-  },
-
   "metrics": true
 }

+ 12 - 15
public/app/plugins/datasource/graphite/datasource.js

@@ -5,6 +5,7 @@ define([
   'config',
   'kbn',
   'moment',
+  './directives',
   './queryCtrl',
   './funcEditor',
   './addGraphiteFunc',
@@ -228,21 +229,13 @@ function (angular, _, $, config, kbn, moment) {
       return backendSrv.datasourceRequest(options);
     };
 
-    GraphiteDatasource.prototype._seriesRefLetters = [
-      '#A', '#B', '#C', '#D',
-      '#E', '#F', '#G', '#H',
-      '#I', '#J', '#K', '#L',
-      '#M', '#N', '#O', '#P',
-      '#Q', '#R', '#S', '#T',
-      '#U', '#V', '#W', '#X',
-      '#Y', '#Z'
-    ];
+    GraphiteDatasource.prototype._seriesRefLetters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
 
     GraphiteDatasource.prototype.buildGraphiteParams = function(options, scopedVars) {
       var graphite_options = ['from', 'until', 'rawData', 'format', 'maxDataPoints', 'cacheTimeout'];
       var clean_options = [], targets = {};
       var target, targetValue, i;
-      var regex = /(\#[A-Z])/g;
+      var regex = /\#([A-Z])/g;
       var intervalFormatFixRegex = /'(\d+)m'/gi;
 
       if (options.format !== 'png') {
@@ -259,13 +252,17 @@ function (angular, _, $, config, kbn, moment) {
           continue;
         }
 
+        if (!target.refId) {
+          target.refId = this._seriesRefLetters[i];
+        }
+
         targetValue = templateSrv.replace(target.target, scopedVars);
         targetValue = targetValue.replace(intervalFormatFixRegex, fixIntervalFormat);
-        targets[this._seriesRefLetters[i]] = targetValue;
+        targets[target.refId] = targetValue;
       }
 
-      function nestedSeriesRegexReplacer(match) {
-        return targets[match];
+      function nestedSeriesRegexReplacer(match, g1) {
+        return targets[g1];
       }
 
       for (i = 0; i < options.targets.length; i++) {
@@ -274,9 +271,9 @@ function (angular, _, $, config, kbn, moment) {
           continue;
         }
 
-        targetValue = targets[this._seriesRefLetters[i]];
+        targetValue = targets[target.refId];
         targetValue = targetValue.replace(regex, nestedSeriesRegexReplacer);
-        targets[this._seriesRefLetters[i]] = targetValue;
+        targets[target.refId] = targetValue;
 
         if (!target.hide) {
           clean_options.push("target=" + encodeURIComponent(targetValue));

+ 21 - 0
public/app/plugins/datasource/graphite/directives.js

@@ -0,0 +1,21 @@
+define([
+  'angular',
+],
+function (angular) {
+  'use strict';
+
+  var module = angular.module('grafana.directives');
+
+  module.directive('metricQueryEditorGraphite', function() {
+    return {controller: 'GraphiteQueryCtrl', templateUrl: 'app/plugins/datasource/graphite/partials/query.editor.html'};
+  });
+
+  module.directive('metricQueryOptionsGraphite', function() {
+    return {templateUrl: 'app/plugins/datasource/graphite/partials/query.options.html'};
+  });
+
+  module.directive('annotationsQueryEditorGraphite', function() {
+    return {templateUrl: 'app/plugins/datasource/graphite/partials/annotations.editor.html'};
+  });
+
+});

+ 61 - 210
public/app/plugins/datasource/graphite/partials/query.editor.html

@@ -1,221 +1,72 @@
-<div class="editor-row">
-
-	<div  ng-repeat="target in panel.targets"
-        class="tight-form"
-        ng-class="{'tight-form-disabled': target.hide}"
-        ng-controller="GraphiteQueryCtrl"
-        ng-init="init()">
-      <ul class="tight-form-list pull-right">
-        <li ng-show="parserError" class="tight-form-item">
-          <a bs-tooltip="parserError" style="color: rgb(229, 189, 28)" role="menuitem">
-            <i class="fa fa-warning"></i>
-          </a>
-        </li>
-        <li class="tight-form-item">
-          <a class="pointer" tabindex="1" ng-click="toggleEditorMode()">
-            <i class="fa fa-pencil"></i>
-          </a>
-        </li>
-        <li class="tight-form-item">
-					<div class="dropdown">
-						<a  class="pointer dropdown-toggle"
-							data-toggle="dropdown"
-							tabindex="1">
-							<i class="fa fa-bars"></i>
+<div class="tight-form">
+	<ul class="tight-form-list pull-right">
+		<li ng-show="parserError" class="tight-form-item">
+			<a bs-tooltip="parserError" style="color: rgb(229, 189, 28)" role="menuitem">
+				<i class="fa fa-warning"></i>
+			</a>
+		</li>
+		<li class="tight-form-item small" ng-show="target.datasource">
+			<em>{{target.datasource}}</em>
+		</li>
+		<li class="tight-form-item">
+			<div class="dropdown">
+				<a class="pointer dropdown-toggle" data-toggle="dropdown" tabindex="1">
+					<i class="fa fa-bars"></i>
+				</a>
+				<ul class="dropdown-menu pull-right" role="menu">
+					<li role="menuitem">
+						<a tabindex="1" ng-click="toggleEditorMode()">
+							Switch editor mode
 						</a>
-						<ul class="dropdown-menu pull-right" role="menu">
-							<li role="menuitem">
-								<a  tabindex="1"
-									ng-click="duplicate()">
-									Duplicate
-								</a>
-							</li>
-							<li role="menuitem">
-								<a  tabindex="1"
-									ng-click="moveMetricQuery($index, $index-1)">
-									Move up
-								</a>
-							</li>
-							<li role="menuitem">
-								<a  tabindex="1"
-									ng-click="moveMetricQuery($index, $index+1)">
-									Move down
-								</a>
-							</li>
-						</ul>
-					</div>
-				</li>
-				<li class="tight-form-item last">
-					<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
-						<i class="fa fa-remove"></i>
-					</a>
-				</li>
-			</ul>
+					</li>
+					<li role="menuitem">
+						<a tabindex="1" ng-click="duplicate()">Duplicate</a>
+					</li>
+					<li role="menuitem">
+						<a tabindex="1" ng-click="moveMetricQuery($index, $index-1)">Move up</a>
+					</li>
+					<li role="menuitem">
+						<a tabindex="1" ng-click="moveMetricQuery($index, $index+1)">Move down</a>
+					</li>
+				</ul>
+			</div>
+		</li>
+		<li class="tight-form-item last">
+			<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
+				<i class="fa fa-remove"></i>
+			</a>
+		</li>
+	</ul>
 
-			<ul class="tight-form-list">
-				<li class="tight-form-item" style="min-width: 15px; text-align: center">
-					{{targetLetters[$index]}}
-				</li>
-				<li>
-					<a  class="tight-form-item"
-						ng-click="target.hide = !target.hide; get_data();"
-						role="menuitem">
-						<i class="fa fa-eye"></i>
-					</a>
-				</li>
-			</ul>
+	<ul class="tight-form-list">
+		<li class="tight-form-item" style="min-width: 15px; text-align: center">
+			{{target.refId}}
+		</li>
+		<li>
+			<a class="tight-form-item" ng-click="target.hide = !target.hide; get_data();" role="menuitem">
+				<i class="fa fa-eye"></i>
+			</a>
+		</li>
+	</ul>
 
-			<input  type="text" class="tight-form-clear-input span10"
-              ng-model="target.target"
-              focus-me="target.textEditor"
-              spellcheck='false'
-              ng-model-onblur ng-change="get_data()"
-              ng-show="target.textEditor" />
-
-      <ul class="tight-form-list" role="menu" ng-hide="target.textEditor">
-				<li ng-repeat="segment in segments" role="menuitem">
-					<metric-segment segment="segment" get-alt-segments="getAltSegments($index)" on-value-changed="segmentValueChanged(segment, $index)"></metric-segment>
-				</li>
-				<li ng-repeat="func in functions">
-          <span graphite-func-editor class="tight-form-item tight-form-func">
-          </span>
-        </li>
-        <li class="dropdown" graphite-add-func>
-        </li>
-      </ul>
-      <div class="clearfix"></div>
-    </div>
-</div>
+	<input  type="text" class="tight-form-clear-input span10"
+		ng-model="target.target"
+		give-focus="target.textEditor"
+		spellcheck='false'
+		ng-model-onblur ng-change="get_data()"
+		ng-show="target.textEditor" />
 
-<section class="grafana-metric-options">
-	<div class="tight-form">
-		<ul class="tight-form-list">
-			<li class="tight-form-item tight-form-item-icon">
-				<i class="fa fa-wrench"></i>
-			</li>
-			<li class="tight-form-item">
-				Cache timeout
-			</li>
-			<li>
-				<input type="text"
-				class="input-mini tight-form-input"
-				ng-model="panel.cacheTimeout"
-				bs-tooltip="'Graphite parameter to override memcache default timeout (unit is seconds)'"
-				data-placement="right"
-				spellcheck='false'
-				placeholder="60">
-			</li>
-			<li class="tight-form-item">
-				Max data points
-			</li>
-			<li>
-				<input type="text"
-				class="input-mini tight-form-input"
-				ng-model="panel.maxDataPoints"
-				bs-tooltip="'Override max data points, automatically set to graph width in pixels.'"
-				data-placement="right"
-				ng-model-onblur ng-change="get_data()"
-				spellcheck='false'
-				placeholder="auto">
-			</li>
-		</ul>
-		<div class="clearfix"></div>
-	</div>
-	<div class="tight-form">
-		<ul class="tight-form-list">
-			<li class="tight-form-item tight-form-item-icon">
-				<i class="fa fa-info-circle"></i>
-			</li>
-			<li class="tight-form-item">
-				<a ng-click="toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
-					shorter legend names
-				</a>
+		<ul class="tight-form-list" role="menu" ng-hide="target.textEditor">
+			<li ng-repeat="segment in segments" role="menuitem">
+				<metric-segment segment="segment" get-alt-segments="getAltSegments($index)" on-value-changed="segmentValueChanged(segment, $index)"></metric-segment>
 			</li>
-			<li class="tight-form-item">
-				<a ng-click="toggleEditorHelp(2);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
-					series as parameters
-				</a>
-			</li>
-			<li class="tight-form-item">
-				<a ng-click="toggleEditorHelp(3)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
-					stacking
-				</a>
+			<li ng-repeat="func in functions">
+				<span graphite-func-editor class="tight-form-item tight-form-func">
+				</span>
 			</li>
-			<li class="tight-form-item">
-				<a ng-click="toggleEditorHelp(4)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
-					templating
-				</a>
-			</li>
-			<li class="tight-form-item">
-				<a ng-click="toggleEditorHelp(5)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
-					max data points
-				</a>
+			<li class="dropdown" graphite-add-func>
 			</li>
 		</ul>
 		<div class="clearfix"></div>
 	</div>
-</section>
-
-<div class="editor-row">
-	<div class="pull-left" style="margin-top: 30px;">
-
-		<div class="grafana-info-box span8" ng-if="editorHelpIndex === 1">
-			<h5>Shorter legend names</h5>
-			<ul>
-				<li>alias() function to specify a custom series name</li>
-				<li>aliasByNode(2) to alias by a specific part of your metric path</li>
-				<li>aliasByNode(2, -1) you can add multiple segment paths, and use negative index</li>
-				<li>groupByNode(2, 'sum') is useful if you have 2 wildcards in your metric path and want to sumSeries and group by</li>
-			</ul>
-		</div>
-
-		<div class="grafana-info-box span8" ng-if="editorHelpIndex === 2">
-			<h5>Series as parameter</h5>
-			<ul>
-				<li>Some graphite functions allow you to have many series arguments</li>
-				<li>Use #[A-Z] to use a graphite query as parameter to a function</li>
-				<li>
-					Examples:
-					<ul>
-						<li>asPercent(#A, #B)</li>
-						<li>prod.srv-01.counters.count - asPercent(#A) : percentage of count in comparison with A query</li>
-						<li>prod.srv-01.counters.count - sumSeries(#A) : sum count and series A </li>
-						<li>divideSeries(#A, #B)</li>
-					</ul>
-				</li>
-				<li>If a query is added only to be used as a parameter, hide it from the graph with the eye icon</li>
-			</ul>
-		</div>
-
-		<div class="grafana-info-box span6" ng-if="editorHelpIndex === 3">
-			<h5>Stacking</h5>
-			<ul>
-				<li>You find the stacking option under Display Styles tab</li>
-				<li>When stacking is enabled make sure null point mode is set to 'null as zero'</li>
-			</ul>
-		</div>
-
-		<div class="grafana-info-box span6" ng-if="editorHelpIndex === 4">
-			<h5>Templating</h5>
-			<ul>
-				<li>You can use a template variable in place of metric names</li>
-				<li>You can use a template variable in place of function parameters</li>
-				<li>You enable the templating feature in Dashboard settings / Feature toggles </li>
-			</ul>
-		</div>
-
-		<div class="grafana-info-box span6" ng-if="editorHelpIndex === 5">
-			<h5>Max data points</h5>
-			<ul>
-				<li>Every graphite request is issued with a maxDataPoints parameter</li>
-				<li>Graphite uses this parameter to consolidate the real number of values down to this number</li>
-				<li>If there are more real values, then by default they will be consolidated using averages</li>
-				<li>This could hide real peaks and max values in your series</li>
-				<li>You can change how point consolidation is made using the consolidateBy graphite function</li>
-				<li>Point consolidation will effect series legend values (min,max,total,current)</li>
-				<li>If you override maxDataPoint and set a high value performance can be severely effected</li>
-			</ul>
-		</div>
-
-	</div>
 </div>

+ 132 - 0
public/app/plugins/datasource/graphite/partials/query.options.html

@@ -0,0 +1,132 @@
+<section class="grafana-metric-options">
+
+	<div class="tight-form">
+		<ul class="tight-form-list">
+			<li class="tight-form-item tight-form-item-icon">
+				<i class="fa fa-wrench"></i>
+			</li>
+			<li class="tight-form-item">
+				Cache timeout
+			</li>
+			<li>
+				<input type="text"
+					class="input-mini tight-form-input"
+					ng-model="panel.cacheTimeout"
+					bs-tooltip="'Graphite parameter to override memcache default timeout (unit is seconds)'"
+					data-placement="right"
+					spellcheck='false'
+					placeholder="60"></input>
+			</li>
+			<li class="tight-form-item">
+				Max data points
+			</li>
+			<li>
+				<input type="text"
+					class="input-mini tight-form-input"
+					ng-model="panel.maxDataPoints"
+					bs-tooltip="'Override max data points, automatically set to graph width in pixels.'"
+					data-placement="right"
+					ng-model-onblur ng-change="get_data()"
+					spellcheck='false'
+					placeholder="auto"></input>
+			</li>
+		</ul>
+		<div class="clearfix"></div>
+	</div>
+	<div class="tight-form">
+		<ul class="tight-form-list">
+			<li class="tight-form-item tight-form-item-icon">
+				<i class="fa fa-info-circle"></i>
+			</li>
+			<li class="tight-form-item">
+				<a ng-click="toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
+					shorter legend names
+				</a>
+			</li>
+			<li class="tight-form-item">
+				<a ng-click="toggleEditorHelp(2);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
+					series as parameters
+				</a>
+			</li>
+			<li class="tight-form-item">
+				<a ng-click="toggleEditorHelp(3)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
+					stacking
+				</a>
+			</li>
+			<li class="tight-form-item">
+				<a ng-click="toggleEditorHelp(4)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
+					templating
+				</a>
+			</li>
+			<li class="tight-form-item">
+				<a ng-click="toggleEditorHelp(5)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
+					max data points
+				</a>
+			</li>
+		</ul>
+		<div class="clearfix"></div>
+	</div>
+</section>
+
+<div class="editor-row">
+	<div class="pull-left" style="margin-top: 30px;">
+
+		<div class="grafana-info-box span8" ng-if="editorHelpIndex === 1">
+			<h5>Shorter legend names</h5>
+			<ul>
+				<li>alias() function to specify a custom series name</li>
+				<li>aliasByNode(2) to alias by a specific part of your metric path</li>
+				<li>aliasByNode(2, -1) you can add multiple segment paths, and use negative index</li>
+				<li>groupByNode(2, 'sum') is useful if you have 2 wildcards in your metric path and want to sumSeries and group by</li>
+			</ul>
+		</div>
+
+		<div class="grafana-info-box span8" ng-if="editorHelpIndex === 2">
+			<h5>Series as parameter</h5>
+			<ul>
+				<li>Some graphite functions allow you to have many series arguments</li>
+				<li>Use #[A-Z] to use a graphite query as parameter to a function</li>
+				<li>
+					Examples:
+					<ul>
+						<li>asPercent(#A, #B)</li>
+						<li>prod.srv-01.counters.count - asPercent(#A) : percentage of count in comparison with A query</li>
+						<li>prod.srv-01.counters.count - sumSeries(#A) : sum count and series A </li>
+						<li>divideSeries(#A, #B)</li>
+					</ul>
+				</li>
+				<li>If a query is added only to be used as a parameter, hide it from the graph with the eye icon</li>
+			</ul>
+		</div>
+
+		<div class="grafana-info-box span6" ng-if="editorHelpIndex === 3">
+			<h5>Stacking</h5>
+			<ul>
+				<li>You find the stacking option under Display Styles tab</li>
+				<li>When stacking is enabled make sure null point mode is set to 'null as zero'</li>
+			</ul>
+		</div>
+
+		<div class="grafana-info-box span6" ng-if="editorHelpIndex === 4">
+			<h5>Templating</h5>
+			<ul>
+				<li>You can use a template variable in place of metric names</li>
+				<li>You can use a template variable in place of function parameters</li>
+				<li>You enable the templating feature in Dashboard settings / Feature toggles </li>
+			</ul>
+		</div>
+
+		<div class="grafana-info-box span6" ng-if="editorHelpIndex === 5">
+			<h5>Max data points</h5>
+			<ul>
+				<li>Every graphite request is issued with a maxDataPoints parameter</li>
+				<li>Graphite uses this parameter to consolidate the real number of values down to this number</li>
+				<li>If there are more real values, then by default they will be consolidated using averages</li>
+				<li>This could hide real peaks and max values in your series</li>
+				<li>You can change how point consolidation is made using the consolidateBy graphite function</li>
+				<li>Point consolidation will effect series legend values (min,max,total,current)</li>
+				<li>If you override maxDataPoint and set a high value performance can be severely effected</li>
+			</ul>
+		</div>
+	</div>
+</div>

+ 1 - 3
public/app/plugins/datasource/graphite/plugin.json

@@ -8,9 +8,7 @@
   "module": "plugins/datasource/graphite/datasource",
 
   "partials": {
-    "config": "app/plugins/datasource/graphite/partials/config.html",
-    "query": "app/plugins/datasource/graphite/partials/query.editor.html",
-    "annotations": "app/plugins/datasource/graphite/partials/annotations.editor.html"
+    "config": "app/plugins/datasource/graphite/partials/config.html"
   },
 
   "metrics": true,

+ 5 - 20
public/app/plugins/datasource/graphite/queryCtrl.js

@@ -9,15 +9,14 @@ function (angular, _, config, gfunc, Parser) {
   'use strict';
 
   var module = angular.module('grafana.controllers');
-  var targetLetters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
 
   module.controller('GraphiteQueryCtrl', function($scope, $sce, templateSrv) {
 
     $scope.init = function() {
-      $scope.target.target = $scope.target.target || '';
-      $scope.targetLetters = targetLetters;
-
-      parseTarget();
+      if ($scope.target) {
+        $scope.target.target = $scope.target.target || '';
+        parseTarget();
+      }
     };
 
     $scope.toggleEditorMode = function() {
@@ -313,22 +312,8 @@ function (angular, _, config, gfunc, Parser) {
       return new MetricSegment({value: 'select metric', fake: true});
     };
 
-  });
+    $scope.init();
 
-  module.directive('focusMe', function($timeout, $parse) {
-    return {
-      //scope: true,   // optionally create a child scope
-      link: function(scope, element, attrs) {
-        var model = $parse(attrs.focusMe);
-        scope.$watch(model, function(value) {
-          if(value === true) {
-            $timeout(function() {
-              element[0].focus();
-            });
-          }
-        });
-      }
-    };
   });
 
 });

+ 1 - 0
public/app/plugins/datasource/influxdb/datasource.js

@@ -4,6 +4,7 @@ define([
   'kbn',
   './influxSeries',
   './queryBuilder',
+  './directives',
   './queryCtrl',
   './funcEditor',
 ],

+ 21 - 0
public/app/plugins/datasource/influxdb/directives.js

@@ -0,0 +1,21 @@
+define([
+  'angular',
+],
+function (angular) {
+  'use strict';
+
+  var module = angular.module('grafana.directives');
+
+  module.directive('metricQueryEditorInfluxdb', function() {
+    return {controller: 'InfluxQueryCtrl', templateUrl: 'app/plugins/datasource/influxdb/partials/query.editor.html'};
+  });
+
+  module.directive('metricQueryOptionsInfluxdb', function() {
+    return {templateUrl: 'app/plugins/datasource/influxdb/partials/query.options.html'};
+  });
+
+  module.directive('annotationsQueryEditorInfluxdb', function() {
+    return {templateUrl: 'app/plugins/datasource/influxdb/partials/annotations.editor.html'};
+  });
+
+});

+ 55 - 160
public/app/plugins/datasource/influxdb/partials/query.editor.html

@@ -1,65 +1,56 @@
-<div class="editor-row">
-
-	<div ng-repeat="target in panel.targets" ng-controller="InfluxQueryCtrl" ng-init="init()" ng-class="{'tight-form-disabled': target.hide}" class="tight-form-container-no-item-borders" style="margin-bottom: 10px">
-		<div  class="tight-form">
-			<ul class="tight-form-list pull-right">
-				<li ng-show="parserError" class="tight-form-item">
-					<a bs-tooltip="parserError" style="color: rgb(229, 189, 28)" role="menuitem">
-						<i class="fa fa-warning"></i>
-					</a>
-				</li>
-				<li class="tight-form-item">
-					<a class="pointer" tabindex="1" ng-click="toggleQueryMode()">
-						<i class="fa fa-pencil"></i>
+<div class="tight-form-container-no-item-borders">
+	<div  class="tight-form">
+		<ul class="tight-form-list pull-right">
+			<li ng-show="parserError" class="tight-form-item">
+				<a bs-tooltip="parserError" style="color: rgb(229, 189, 28)" role="menuitem">
+					<i class="fa fa-warning"></i>
+				</a>
+			</li>
+			<li class="tight-form-item small" ng-show="target.datasource">
+				<em>{{target.datasource}}</em>
+			</li>
+			<li class="tight-form-item">
+				<div class="dropdown">
+					<a class="pointer dropdown-toggle" data-toggle="dropdown" tabindex="1">
+						<i class="fa fa-bars"></i>
 					</a>
-				</li>
-				<li class="tight-form-item">
-					<div class="dropdown">
-						<a  class="pointer dropdown-toggle"
-							data-toggle="dropdown"
-							tabindex="1">
-							<i class="fa fa-bars"></i>
+					<ul class="dropdown-menu pull-right" role="menu">
+						<a tabindex="1" ng-click="toggleEditorMode()">
+							Switch editor mode
 						</a>
-						<ul class="dropdown-menu pull-right" role="menu">
-							<li role="menuitem">
-								<a  tabindex="1"
-									ng-click="duplicate()">
-									Duplicate
-								</a>
-							</li>
-							<li role="menuitem">
-								<a  tabindex="1"
-									ng-click="moveMetricQuery($index, $index-1)">
-									Move up
-								</a>
-							</li>
-							<li role="menuitem">
-								<a  tabindex="1"
-									ng-click="moveMetricQuery($index, $index+1)">
-									Move down
-								</a>
-							</li>
-						</ul>
-					</div>
-				</li>
-				<li class="tight-form-item last">
-					<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
-						<i class="fa fa-remove"></i>
-					</a>
-				</li>
-			</ul>
+						<li role="menuitem">
+							<a tabindex="1" ng-click="duplicate()">Duplicate</a>
+						</li>
+						<li role="menuitem">
+							<a tabindex="1" ng-click="moveMetricQuery($index, $index-1)">Move up</a>
+						</li>
+						<li role="menuitem">
+							<a tabindex="1" ng-click="moveMetricQuery($index, $index+1)">Move down</a>
+						</li>
+					</ul>
+				</div>
+			</li>
+			<li class="tight-form-item last">
+				<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
+					<i class="fa fa-remove"></i>
+				</a>
+			</li>
+		</ul>
 
-			<ul class="tight-form-list">
-				<li>
-					<a  class="tight-form-item"
-						ng-click="target.hide = !target.hide; get_data();"
-						role="menuitem">
-						<i class="fa fa-eye"></i>
-					</a>
-				</li>
-			</ul>
+		<ul class="tight-form-list">
+			<li class="tight-form-item" style="min-width: 15px; text-align: center">
+				{{target.refId}}
+			</li>
+			<li>
+				<a  class="tight-form-item"
+					ng-click="target.hide = !target.hide; get_data();"
+					role="menuitem">
+					<i class="fa fa-eye"></i>
+				</a>
+			</li>
+		</ul>
 
-			<input type="text" class="tight-form-clear-input" style="width: 80%" ng-model="target.query" focus-me="target.rawQuery" spellcheck='false' ng-model-onblur ng-change="get_data()" ng-show="target.rawQuery"/>
+		<input type="text" class="tight-form-clear-input" style="width: 80%" ng-model="target.query" give-focus="target.rawQuery" spellcheck='false' ng-model-onblur ng-change="get_data()" ng-show="target.rawQuery"/>
 
 			<ul class="tight-form-list" role="menu" ng-hide="target.rawQuery">
 				<li class="tight-form-item query-keyword" style="width: 75px;">
@@ -79,10 +70,7 @@
 
 		<div class="tight-form" ng-hide="target.rawQuery">
 			<ul class="tight-form-list">
-				<li class="tight-form-item">
-					<i class="fa fa-eye invisible"></i>
-				</li>
-				<li class="tight-form-item query-keyword" style="width: 75px;">
+				<li class="tight-form-item query-keyword tight-form-align" style="width: 75px;">
 					FROM
 				</li>
 				<li>
@@ -95,11 +83,7 @@
 
 		<div class="tight-form" ng-hide="target.rawQuery">
 			<ul class="tight-form-list">
-				<li class="tight-form-item">
-					<i class="fa fa-eye invisible"></i>
-				</li>
-
-				<li class="tight-form-item query-keyword" style="width: 75px;">
+				<li class="tight-form-item query-keyword tight-form-align" style="width: 75px;">
 					WHERE
 				</li>
 
@@ -112,11 +96,7 @@
 
 		<div class="tight-form">
 			<ul class="tight-form-list" ng-hide="target.rawQuery">
-				<li class="tight-form-item">
-					<i class="fa fa-eye invisible"></i>
-				</li>
-
-				<li class="tight-form-item query-keyword">
+				<li class="tight-form-item query-keyword tight-form-align">
 					GROUP BY
 				</li>
 
@@ -150,7 +130,9 @@
 					Alias pattern
 				</li>
 				<li>
-					<input type="text" class="input-medium tight-form-input" ng-model="target.alias" spellcheck='false' placeholder="alias" ng-model-onblur ng-change="get_data()">
+					<input type="text" class="input-medium tight-form-input"
+						ng-model="target.alias" spellcheck='false' placeholder="alias"
+						ng-model-onblur ng-change="get_data()"></input>
 				</li>
 			</ul>
 			<div class="clearfix"></div>
@@ -159,90 +141,3 @@
 	</div>
 </div>
 
-<section class="grafana-metric-options">
-	<div class="tight-form">
-		<ul class="tight-form-list">
-			<li class="tight-form-item tight-form-item-icon">
-				<i class="fa fa-wrench"></i>
-			</li>
-			<li class="tight-form-item">
-				 Group by time interval
-			</li>
-			<li>
-				<input type="text" class="input-medium tight-form-input" ng-model="panel.interval" ng-blur="get_data();"
-				spellcheck='false' placeholder="example: >10s">
-			</li>
-			<li class="tight-form-item">
-				<i class="fa fa-question-circle" bs-tooltip="'Set a low limit by having a greater sign: example: >60s'" data-placement="right"></i>
-			</li>
-		</ul>
-		<div class="clearfix"></div>
-	</div>
-
-	<div class="tight-form">
-		<ul class="tight-form-list">
-			<li class="tight-form-item tight-form-item-icon">
-				<i class="fa fa-info-circle"></i>
-			</li>
-			<li class="tight-form-item">
-				<a ng-click="toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
-					alias patterns
-				</a>
-			</li>
-			<li class="tight-form-item">
-				<a ng-click="toggleEditorHelp(2)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
-					stacking &amp; and fill
-				</a>
-			</li>
-			<li class="tight-form-item">
-				<a ng-click="toggleEditorHelp(3)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
-					group by time
-				</a>
-			</li>
-		</ul>
-		<div class="clearfix"></div>
-	</div>
-</section>
-
-<div class="editor-row">
-	<div class="pull-left" style="margin-top: 30px;">
-
-		<div class="grafana-info-box span6" ng-if="editorHelpIndex === 1">
-			<h5>Alias patterns</h5>
-			<ul>
-				<li>$m = replaced with measurement name</li>
-				<li>$measurement = replaced with measurement name</li>
-				<li>$col = replaced with column name</li>
-				<li>$tag_hostname = replaced with the value of the hostname tag</li>
-				<li>You can also use [[tag_hostname]] pattern replacement syntax</li>
-			</ul>
-		</div>
-
-		<div class="grafana-info-box span6" ng-if="editorHelpIndex === 2">
-			<h5>Stacking and fill</h5>
-			<ul>
-				<li>When stacking is enabled it important that points align</li>
-				<li>If there are missing points for one series it can cause gaps or missing bars</li>
-				<li>You must use fill(0), and select a group by time low limit</li>
-				<li>Use the group by time option below your queries and specify for example &gt;10s if your metrics are written every 10 seconds</li>
-				<li>This will insert zeros for series that are missing measurements and will make stacking work properly</li>
-			</ul>
-		</div>
-
-		<div class="grafana-info-box span6" ng-if="editorHelpIndex === 3">
-			<h5>Group by time</h5>
-			<ul>
-				<li>Group by time is important, otherwise the query could return many thousands of datapoints that will slow down Grafana</li>
-				<li>Leave the group by time field empty for each query and it will be calculated based on time range and pixel width of the graph</li>
-				<li>If you use fill(0) or fill(null) set a low limit for the auto group by time interval</li>
-				<li>The low limit can only be set in the group by time option below your queries</li>
-				<li>You set a low limit by adding a greater sign before the interval</li>
-				<li>Example: &gt;60s if you write metrics to InfluxDB every 60 seconds</li>
-			</ul>
-		</div>
-
-
-	</div>
-</div>
-
-

+ 87 - 0
public/app/plugins/datasource/influxdb/partials/query.options.html

@@ -0,0 +1,87 @@
+<section class="grafana-metric-options">
+	<div class="tight-form">
+		<ul class="tight-form-list">
+			<li class="tight-form-item tight-form-item-icon">
+				<i class="fa fa-wrench"></i>
+			</li>
+			<li class="tight-form-item">
+				 Group by time interval
+			</li>
+			<li>
+				<input type="text" class="input-medium tight-form-input" ng-model="panel.interval" ng-blur="get_data();"
+				spellcheck='false' placeholder="example: >10s">
+			</li>
+			<li class="tight-form-item">
+				<i class="fa fa-question-circle" bs-tooltip="'Set a low limit by having a greater sign: example: >60s'" data-placement="right"></i>
+			</li>
+		</ul>
+		<div class="clearfix"></div>
+	</div>
+
+	<div class="tight-form">
+		<ul class="tight-form-list">
+			<li class="tight-form-item tight-form-item-icon">
+				<i class="fa fa-info-circle"></i>
+			</li>
+			<li class="tight-form-item">
+				<a ng-click="toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
+					alias patterns
+				</a>
+			</li>
+			<li class="tight-form-item">
+				<a ng-click="toggleEditorHelp(2)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
+					stacking &amp; and fill
+				</a>
+			</li>
+			<li class="tight-form-item">
+				<a ng-click="toggleEditorHelp(3)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
+					group by time
+				</a>
+			</li>
+		</ul>
+		<div class="clearfix"></div>
+	</div>
+</section>
+
+<div class="editor-row">
+	<div class="pull-left" style="margin-top: 30px;">
+
+		<div class="grafana-info-box span6" ng-if="editorHelpIndex === 1">
+			<h5>Alias patterns</h5>
+			<ul>
+				<li>$m = replaced with measurement name</li>
+				<li>$measurement = replaced with measurement name</li>
+				<li>$col = replaced with column name</li>
+				<li>$tag_hostname = replaced with the value of the hostname tag</li>
+				<li>You can also use [[tag_hostname]] pattern replacement syntax</li>
+			</ul>
+		</div>
+
+		<div class="grafana-info-box span6" ng-if="editorHelpIndex === 2">
+			<h5>Stacking and fill</h5>
+			<ul>
+				<li>When stacking is enabled it important that points align</li>
+				<li>If there are missing points for one series it can cause gaps or missing bars</li>
+				<li>You must use fill(0), and select a group by time low limit</li>
+				<li>Use the group by time option below your queries and specify for example &gt;10s if your metrics are written every 10 seconds</li>
+				<li>This will insert zeros for series that are missing measurements and will make stacking work properly</li>
+			</ul>
+		</div>
+
+		<div class="grafana-info-box span6" ng-if="editorHelpIndex === 3">
+			<h5>Group by time</h5>
+			<ul>
+				<li>Group by time is important, otherwise the query could return many thousands of datapoints that will slow down Grafana</li>
+				<li>Leave the group by time field empty for each query and it will be calculated based on time range and pixel width of the graph</li>
+				<li>If you use fill(0) or fill(null) set a low limit for the auto group by time interval</li>
+				<li>The low limit can only be set in the group by time option below your queries</li>
+				<li>You set a low limit by adding a greater sign before the interval</li>
+				<li>Example: &gt;60s if you write metrics to InfluxDB every 60 seconds</li>
+			</ul>
+		</div>
+
+
+	</div>
+</div>
+
+

+ 1 - 3
public/app/plugins/datasource/influxdb/plugin.json

@@ -8,9 +8,7 @@
   "module": "plugins/datasource/influxdb/datasource",
 
   "partials": {
-    "config": "app/plugins/datasource/influxdb/partials/config.html",
-    "query": "app/plugins/datasource/influxdb/partials/query.editor.html",
-    "annotations": "app/plugins/datasource/influxdb/partials/annotations.editor.html"
+    "config": "app/plugins/datasource/influxdb/partials/config.html"
   },
 
   "metrics": true,

+ 4 - 0
public/app/plugins/datasource/influxdb/queryCtrl.js

@@ -11,6 +11,8 @@ function (angular, _, InfluxQueryBuilder) {
   module.controller('InfluxQueryCtrl', function($scope, $timeout, $sce, templateSrv, $q) {
 
     $scope.init = function() {
+      if (!$scope.target) { return; }
+
       var target = $scope.target;
       target.tags = target.tags || [];
       target.groupByTags = target.groupByTags || [];
@@ -337,6 +339,8 @@ function (angular, _, InfluxQueryBuilder) {
       return new MetricSegment({value: 'select tag value', fake: true});
     };
 
+    $scope.init();
+
   });
 
 });

+ 1 - 0
public/app/plugins/datasource/influxdb_08/datasource.js

@@ -4,6 +4,7 @@ define([
   'kbn',
   './influxSeries',
   './queryBuilder',
+  './directives',
   './queryCtrl',
   './funcEditor',
 ],

+ 21 - 0
public/app/plugins/datasource/influxdb_08/directives.js

@@ -0,0 +1,21 @@
+define([
+  'angular',
+],
+function (angular) {
+  'use strict';
+
+  var module = angular.module('grafana.directives');
+
+  module.directive('metricQueryEditorInfluxdb08', function() {
+    return {controller: 'InfluxQueryCtrl_08', templateUrl: 'app/plugins/datasource/influxdb_08/partials/query.editor.html'};
+  });
+
+  module.directive('metricQueryOptionsInfluxdb08', function() {
+    return {templateUrl: 'app/plugins/datasource/influxdb_08/partials/query.options.html'};
+  });
+
+  module.directive('annotationsQueryEditorInfluxdb08', function() {
+    return {templateUrl: 'app/plugins/datasource/influxdb_08/partials/annotations.editor.html'};
+  });
+
+});

+ 159 - 250
public/app/plugins/datasource/influxdb_08/partials/query.editor.html

@@ -1,256 +1,165 @@
-<div class="editor-row">
-	<div ng-repeat="target in panel.targets" ng-controller="InfluxQueryCtrl_08" ng-init="init()" ng-class="{'tight-form-disabled': target.hide}" class="tight-form-container">
-		<div class="tight-form">
-			<ul class="tight-form-list pull-right">
-				<li class="tight-form-item">
-					<div class="dropdown">
-						<a class="pointer dropdown-toggle"
-							data-toggle="dropdown"
-							tabindex="1">
-							<i class="fa fa-bars"></i>
-						</a>
-						<ul class="dropdown-menu pull-right" role="menu">
-							<li role="menuitem"><a tabindex="1" ng-click="duplicate()">Duplicate</a></li>
-							<li role="menuitem"><a tabindex="1" ng-click="showQuery()" ng-hide="target.rawQuery">Raw query mode</a></li>
-							<li role="menuitem"><a tabindex="1" ng-click="hideQuery()" ng-show="target.rawQuery">Query editor mode</a></li>
-							<li role="menuitem"><a tabindex="1" ng-click="moveMetricQuery($index, $index-1)">Move up </a></li>
-							<li role="menuitem"><a tabindex="1" ng-click="moveMetricQuery($index, $index+1)">Move down</a></li>
-						</ul>
-					</div>
-				</li>
-				<li class="tight-form-item last">
-					<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
-						<i class="fa fa-remove"></i>
-					</a>
-				</li>
-			</ul>
-
-			<ul class="tight-form-list">
-				<li>
-					<a class="tight-form-item" ng-click="target.hide = !target.hide; get_data();" role="menuitem">
-						<i class="fa fa-eye"></i>
-					</a>
-				</li>
-			</ul>
-
-			<!-- Raw Query mode  -->
-			<ul class="tight-form-list" ng-show="target.rawQuery">
-				<li>
-					<input type="text"
-					class="tight-form-input span10"
-					ng-model="target.query"
-					placeholder="select ..."
-					focus-me="target.rawQuery"
-					spellcheck='false'
-					data-min-length=0 data-items=100
-					ng-model-onblur
-					ng-blur="get_data()">
-				</li>
-			</ul>
-
-			<!-- Query editor mode -->
-			<ul class="tight-form-list" role="menu" ng-hide="target.rawQuery">
-				<li class="tight-form-item">
-					series
-				</li>
-				<li>
-					<input type="text"
-					class="tight-form-input span8"
-					ng-model="target.series"
-					spellcheck='false'
-					bs-typeahead="listSeries"
-					match-all="true"
-					min-length="3"
-					placeholder="series name"
-					data-min-length=0 data-items=100
-					ng-blur="seriesBlur()">
-				</li>
-
-				<li class="tight-form-item">
-					alias
-				</li>
-
-				<li>
-					<input type="text" class="input-medium tight-form-input" ng-model="target.alias"
-					spellcheck='false' placeholder="alias" ng-blur="get_data()">
-				</li>
-
-			</ul>
-
-			<div class="clearfix"></div>
-		</div>
-
-		<div class="tight-form">
-			<!-- Raw Query mode  -->
-			<ul class="tight-form-list" ng-show="target.rawQuery">
-				<li class="tight-form-item">
-					<i class="fa fa-eye invisible"></i>
-				</li>
-				<li class="tight-form-item">
-					alias
-				</li>
-				<li>
-					<input type="text"
-					class="input-medium tight-form-input"
-					ng-model="target.alias"
-					spellcheck='false'
-					placeholder="alias"
-					ng-blur="get_data()">
-				</li>
-				<li class="tight-form-item">
-					group by time
-				</li>
-				<li>
-					<input type="text" class="input-mini tight-form-input" ng-model="target.interval"
-					spellcheck='false' placeholder="{{interval}}" data-placement="right"
-					bs-tooltip="'Leave blank for auto handling based on time range and panel width'"
-					ng-model-onblur ng-change="get_data()" >
-				</li>
-			</ul>
-
-			<!-- Query editor mode -->
-			<ul class="tight-form-list" role="menu" ng-hide="target.rawQuery">
-				<li class="tight-form-item">
-					<i class="fa fa-eye invisible"></i>
-				</li>
-				<li class="tight-form-item">
-					select
-				</li>
-				<li class="dropdown">
-					<span influxdb-func-editor08 class="tight-form-item tight-form-func">
-					</span>
-				</li>
-
-				<li class="tight-form-item">
-					where
-				</li>
-				<li>
-					<input type="text" class="input-medium tight-form-input" ng-model="target.condition"
-					bs-tooltip="'Add a where clause'" data-placement="right" spellcheck='false' placeholder="column ~= value" ng-blur="get_data()">
-				</li>
-
-				<li class="tight-form-item">
-					group by time
-				</li>
-				<li>
-					<input type="text" class="input-mini tight-form-input" ng-model="target.interval"
-					spellcheck='false' placeholder="{{interval}}" data-placement="right"
-					bs-tooltip="'Leave blank for auto handling based on time range and panel width'"
-					ng-model-onblur ng-change="get_data()" >
-				</li>
-
-				<li class="tight-form-item">
-					and
-				</li>
-
-				<li>
-					<input type="text" class="input-small tight-form-input" ng-model="target.groupby_field" bs-tooltip="'Add a group by column or leave blank'"
-					placeholder="column" spellcheck="false" bs-typeahead="listColumns" data-min-length=0 ng-blur="get_data()">
-				</li>
-
-				<li class="dropdown">
-					<a class="tight-form-item pointer" data-toggle="dropdown" bs-tooltip="'Insert missing values, important when stacking'" data-placement="right">
-						<span ng-show="target.fill">
-							fill ({{target.fill}})
-						</span>
-						<span ng-show="!target.fill">
-							no fill
-						</span>
-					</a>
-					<ul class="dropdown-menu">
-						<li><a ng-click="target.fill = ''">no fill</a></li>
-						<li><a ng-click="target.fill = 'null'">fill (null)</a></li>
-						<li><a ng-click="target.fill = '0'">fill (0)</a></li>
-					</ul>
-				</li>
-			</ul>
-			<div class="clearfix"></div>
-		</div>
-	</div>
-</div>
-
-<section class="grafana-metric-options">
-	<div class="tight-form">
-		<ul class="tight-form-list">
-			<li class="tight-form-item tight-form-item-icon">
-				<i class="fa fa-wrench"></i>
-			</li>
-			<li class="tight-form-item">
-				group by time
-			</li>
-			<li>
-				<input type="text" class="input-medium tight-form-input" ng-model="panel.interval" ng-blur="get_data();"
-				spellcheck='false' placeholder="example: >10s">
-			</li>
-			<li class="tight-form-item">
-				<i class="fa fa-question-circle" bs-tooltip="'Set a low limit by having a greater sign: example: >60s'" data-placement="right"></i>
-			</li>
-		</ul>
-		<div class="clearfix"></div>
-	</div>
-
-	<div class="tight-form">
-		<ul class="tight-form-list">
-			<li class="tight-form-item tight-form-item-icon">
-				<i class="fa fa-info-circle"></i>
-			</li>
-			<li class="tight-form-item">
-				<a ng-click="toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
-					alias patterns
-				</a>
-			</li>
-			<li class="tight-form-item">
-				<a ng-click="toggleEditorHelp(2)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
-					stacking &amp; and fill
-				</a>
-			</li>
-			<li class="tight-form-item">
-				<a ng-click="toggleEditorHelp(3)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
-					group by time
+<div class="tight-form">
+	<ul class="tight-form-list pull-right">
+		<li class="tight-form-item">
+			<div class="dropdown">
+				<a class="pointer dropdown-toggle"
+					data-toggle="dropdown"
+					tabindex="1">
+					<i class="fa fa-bars"></i>
 				</a>
-			</li>
-		</ul>
-		<div class="clearfix"></div>
-	</div>
-</section>
-
-<div class="editor-row">
-	<div class="pull-left" style="margin-top: 30px;">
-
-		<div class="grafana-info-box span6" ng-if="editorHelpIndex === 1">
-			<h5>Alias patterns</h5>
-			<ul>
-				<li>$s = series name</li>
-				<li>$g = group by</li>
-				<li>$[0-9] part of series name for series names seperated by dots.</li>
-			</ul>
-		</div>
-
-		<div class="grafana-info-box span6" ng-if="editorHelpIndex === 2">
-			<h5>Stacking and fill</h5>
-			<ul>
-				<li>When stacking is enabled it important that points align</li>
-				<li>If there are missing points for one series it can cause gaps or missing bars</li>
-				<li>You must use fill(0), and select a group by time low limit</li>
-				<li>Use the group by time option below your queries and specify for example &gt;10s if your metrics are written every 10 seconds</li>
-				<li>This will insert zeros for series that are missing measurements and will make stacking work properly</li>
-			</ul>
-		</div>
+				<ul class="dropdown-menu pull-right" role="menu">
+					<li role="menuitem"><a tabindex="1" ng-click="duplicate()">Duplicate</a></li>
+					<li role="menuitem"><a tabindex="1" ng-click="showQuery()" ng-hide="target.rawQuery">Raw query mode</a></li>
+					<li role="menuitem"><a tabindex="1" ng-click="hideQuery()" ng-show="target.rawQuery">Query editor mode</a></li>
+					<li role="menuitem"><a tabindex="1" ng-click="moveMetricQuery($index, $index-1)">Move up </a></li>
+					<li role="menuitem"><a tabindex="1" ng-click="moveMetricQuery($index, $index+1)">Move down</a></li>
+				</ul>
+			</div>
+		</li>
+		<li class="tight-form-item last">
+			<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
+				<i class="fa fa-remove"></i>
+			</a>
+		</li>
+	</ul>
+
+	<ul class="tight-form-list">
+		<li class="tight-form-item" style="min-width: 15px; text-align: center">
+			{{target.refId}}
+		</li>
+		<li>
+			<a class="tight-form-item" ng-click="target.hide = !target.hide; get_data();" role="menuitem">
+				<i class="fa fa-eye"></i>
+			</a>
+		</li>
+	</ul>
+
+	<!-- Raw Query mode  -->
+	<ul class="tight-form-list" ng-show="target.rawQuery">
+		<li>
+			<input type="text"
+			class="tight-form-input span10"
+			ng-model="target.query"
+			placeholder="select ..."
+			give-focus="target.rawQuery"
+			spellcheck='false'
+			data-min-length=0 data-items=100
+			ng-model-onblur
+			ng-blur="get_data()">
+		</li>
+	</ul>
+
+	<!-- Query editor mode -->
+	<ul class="tight-form-list" role="menu" ng-hide="target.rawQuery">
+		<li class="tight-form-item">
+			series
+		</li>
+		<li>
+			<input type="text"
+			class="tight-form-input span8"
+			ng-model="target.series"
+			spellcheck='false'
+			bs-typeahead="listSeries"
+			match-all="true"
+			min-length="3"
+			placeholder="series name"
+			data-min-length=0 data-items=100
+			ng-blur="seriesBlur()">
+		</li>
+
+		<li class="tight-form-item">
+			alias
+		</li>
+
+		<li>
+			<input type="text" class="input-medium tight-form-input" ng-model="target.alias"
+			spellcheck='false' placeholder="alias" ng-blur="get_data()">
+		</li>
+
+	</ul>
+
+	<div class="clearfix"></div>
+</div>
 
-		<div class="grafana-info-box span6" ng-if="editorHelpIndex === 3">
-			<h5>Group by time</h5>
-			<ul>
-				<li>Group by time is important, otherwise the query could return many thousands of datapoints that will slow down Grafana</li>
-				<li>Leave the group by time field empty for each query and it will be calculated based on time range and pixel width of the graph</li>
-				<li>If you use fill(0) or fill(null) set a low limit for the auto group by time interval</li>
-				<li>The low limit can only be set in the group by time option below your queries</li>
-				<li>You set a low limit by adding a greater sign before the interval</li>
-				<li>Example: &gt;60s if you write metrics to InfluxDB every 60 seconds</li>
+<div class="tight-form">
+	<!-- Raw Query mode  -->
+	<ul class="tight-form-list" ng-show="target.rawQuery">
+		<li class="tight-form-item tight-form-align">
+			alias
+		</li>
+		<li>
+			<input type="text"
+			class="input-medium tight-form-input"
+			ng-model="target.alias"
+			spellcheck='false'
+			placeholder="alias"
+			ng-blur="get_data()">
+		</li>
+		<li class="tight-form-item">
+			group by time
+		</li>
+		<li>
+			<input type="text" class="input-mini tight-form-input" ng-model="target.interval"
+			spellcheck='false' placeholder="{{interval}}" data-placement="right"
+			bs-tooltip="'Leave blank for auto handling based on time range and panel width'"
+			ng-model-onblur ng-change="get_data()" >
+		</li>
+	</ul>
+
+	<!-- Query editor mode -->
+	<ul class="tight-form-list" role="menu" ng-hide="target.rawQuery">
+		<li class="tight-form-item tight-form-align">
+			select
+		</li>
+		<li class="dropdown">
+			<span influxdb-func-editor08 class="tight-form-item tight-form-func">
+			</span>
+		</li>
+
+		<li class="tight-form-item">
+			where
+		</li>
+		<li>
+			<input type="text" class="input-medium tight-form-input" ng-model="target.condition"
+			bs-tooltip="'Add a where clause'" data-placement="right" spellcheck='false' placeholder="column ~= value" ng-blur="get_data()">
+		</li>
+
+		<li class="tight-form-item">
+			group by time
+		</li>
+		<li>
+			<input type="text" class="input-mini tight-form-input" ng-model="target.interval"
+			spellcheck='false' placeholder="{{interval}}" data-placement="right"
+			bs-tooltip="'Leave blank for auto handling based on time range and panel width'"
+			ng-model-onblur ng-change="get_data()" >
+		</li>
+
+		<li class="tight-form-item">
+			and
+		</li>
+
+		<li>
+			<input type="text" class="input-small tight-form-input" ng-model="target.groupby_field" bs-tooltip="'Add a group by column or leave blank'"
+			placeholder="column" spellcheck="false" bs-typeahead="listColumns" data-min-length=0 ng-blur="get_data()">
+		</li>
+
+		<li class="dropdown">
+			<a class="tight-form-item pointer" data-toggle="dropdown" bs-tooltip="'Insert missing values, important when stacking'" data-placement="right">
+				<span ng-show="target.fill">
+					fill ({{target.fill}})
+				</span>
+				<span ng-show="!target.fill">
+					no fill
+				</span>
+			</a>
+			<ul class="dropdown-menu">
+				<li><a ng-click="target.fill = ''">no fill</a></li>
+				<li><a ng-click="target.fill = 'null'">fill (null)</a></li>
+				<li><a ng-click="target.fill = '0'">fill (0)</a></li>
 			</ul>
-		</div>
-
-
-	</div>
+		</li>
+	</ul>
+	<div class="clearfix"></div>
 </div>
 
 

+ 85 - 0
public/app/plugins/datasource/influxdb_08/partials/query.options.html

@@ -0,0 +1,85 @@
+<section class="grafana-metric-options">
+	<div class="tight-form">
+		<ul class="tight-form-list">
+			<li class="tight-form-item tight-form-item-icon">
+				<i class="fa fa-wrench"></i>
+			</li>
+			<li class="tight-form-item">
+				group by time
+			</li>
+			<li>
+				<input type="text" class="input-medium tight-form-input" ng-model="panel.interval" ng-blur="get_data();"
+				spellcheck='false' placeholder="example: >10s">
+			</li>
+			<li class="tight-form-item">
+				<i class="fa fa-question-circle" bs-tooltip="'Set a low limit by having a greater sign: example: >60s'" data-placement="right"></i>
+			</li>
+		</ul>
+		<div class="clearfix"></div>
+	</div>
+
+	<div class="tight-form">
+		<ul class="tight-form-list">
+			<li class="tight-form-item tight-form-item-icon">
+				<i class="fa fa-info-circle"></i>
+			</li>
+			<li class="tight-form-item">
+				<a ng-click="toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
+					alias patterns
+				</a>
+			</li>
+			<li class="tight-form-item">
+				<a ng-click="toggleEditorHelp(2)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
+					stacking &amp; and fill
+				</a>
+			</li>
+			<li class="tight-form-item">
+				<a ng-click="toggleEditorHelp(3)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
+					group by time
+				</a>
+			</li>
+		</ul>
+		<div class="clearfix"></div>
+	</div>
+</section>
+
+<div class="editor-row">
+	<div class="pull-left" style="margin-top: 30px;">
+
+		<div class="grafana-info-box span6" ng-if="editorHelpIndex === 1">
+			<h5>Alias patterns</h5>
+			<ul>
+				<li>$s = series name</li>
+				<li>$g = group by</li>
+				<li>$[0-9] part of series name for series names seperated by dots.</li>
+			</ul>
+		</div>
+
+		<div class="grafana-info-box span6" ng-if="editorHelpIndex === 2">
+			<h5>Stacking and fill</h5>
+			<ul>
+				<li>When stacking is enabled it important that points align</li>
+				<li>If there are missing points for one series it can cause gaps or missing bars</li>
+				<li>You must use fill(0), and select a group by time low limit</li>
+				<li>Use the group by time option below your queries and specify for example &gt;10s if your metrics are written every 10 seconds</li>
+				<li>This will insert zeros for series that are missing measurements and will make stacking work properly</li>
+			</ul>
+		</div>
+
+		<div class="grafana-info-box span6" ng-if="editorHelpIndex === 3">
+			<h5>Group by time</h5>
+			<ul>
+				<li>Group by time is important, otherwise the query could return many thousands of datapoints that will slow down Grafana</li>
+				<li>Leave the group by time field empty for each query and it will be calculated based on time range and pixel width of the graph</li>
+				<li>If you use fill(0) or fill(null) set a low limit for the auto group by time interval</li>
+				<li>The low limit can only be set in the group by time option below your queries</li>
+				<li>You set a low limit by adding a greater sign before the interval</li>
+				<li>Example: &gt;60s if you write metrics to InfluxDB every 60 seconds</li>
+			</ul>
+		</div>
+
+
+	</div>
+</div>
+
+

+ 1 - 3
public/app/plugins/datasource/influxdb_08/plugin.json

@@ -8,9 +8,7 @@
   "module": "plugins/datasource/influxdb_08/datasource",
 
   "partials": {
-    "config": "app/plugins/datasource/influxdb_08/partials/config.html",
-    "query": "app/plugins/datasource/influxdb_08/partials/query.editor.html",
-    "annotations": "app/plugins/datasource/influxdb_08/partials/annotations.editor.html"
+    "config": "app/plugins/datasource/influxdb_08/partials/config.html"
   },
 
   "metrics": true,

+ 1 - 0
public/app/plugins/datasource/kairosdb/datasource.js

@@ -3,6 +3,7 @@ define([
   'lodash',
   'kbn',
   './queryCtrl',
+  './directives',
 ],
 function (angular, _, kbn) {
   'use strict';

+ 17 - 0
public/app/plugins/datasource/kairosdb/directives.js

@@ -0,0 +1,17 @@
+define([
+  'angular',
+],
+function (angular) {
+  'use strict';
+
+  var module = angular.module('grafana.directives');
+
+  module.directive('metricQueryEditorKairosdb', function() {
+    return {controller: 'KairosDBQueryCtrl', templateUrl: 'app/plugins/datasource/kairosdb/partials/query.editor.html'};
+  });
+
+  module.directive('metricQueryOptionsKairosdb', function() {
+    return {templateUrl: 'app/plugins/datasource/kairosdb/partials/query.options.html'};
+  });
+
+});

+ 306 - 362
public/app/plugins/datasource/kairosdb/partials/query.editor.html

@@ -1,384 +1,328 @@
-<div class="editor-row">
-	<div ng-repeat="target in panel.targets"
-		class="tight-form-container"
-		ng-class="{'tight-form-disabled': target.hide}"
-		ng-controller="KairosDBQueryCtrl"
-		ng-init="init()">
-
-		<div class="tight-form">
-			<ul class="tight-form-list pull-right">
-				<li class="tight-form-item">
-					<div class="dropdown">
-						<a  class="pointer dropdown-toggle"
-							data-toggle="dropdown"
-							tabindex="1">
-							<i class="fa fa-bars"></i>
-						</a>
-						<ul class="dropdown-menu pull-right" role="menu">
-							<li role="menuitem"><a tabindex="1" ng-click="duplicate()">Duplicate</a></li>
-							<li role="menuitem"><a tabindex="1" ng-click="moveMetricQuery($index, $index-1)">Move up</a></li>
-							<li role="menuitem"><a tabindex="1" ng-click="moveMetricQuery($index, $index+1)">Move down</a></li>
-						</ul>
-					</div>
-				</li>
-				<li class="tight-form-item last">
-					<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
-						<i class="fa fa-remove"></i>
-					</a>
-				</li>
-			</ul>
-
-			<ul class="tight-form-list">
-				<li>
-					<a  class="tight-form-item" ng-click="target.hide = !target.hide; targetBlur();" role="menuitem">
-						<i class="fa fa-eye"></i>
-					</a>
-				</li>
-				<li class="tight-form-item">
-					Metric
-				</li>
-				<li>
-					<input type="text"
-						   class="input-large tight-form-input"
-						   ng-model="target.metric"
-						   spellcheck="false"
-						   bs-typeahead="suggestMetrics"
-						   placeholder="metric name"
-						   data-min-length=0 data-items=100
-						   ng-blur="targetBlur()"
-						   >
-					<a bs-tooltip="target.errors.metric"
-						style="color: rgb(229, 189, 28)"
-						ng-show="target.errors.metric">
-						<i class="fa fa-warning"></i>
-					</a>
-				</li>
-				<li class="tight-form-item">
-					Alias
-				</li>
-				<li>
-					<input type="text" class="input-medium tight-form-input" ng-model="target.alias"
-						   spellcheck='false' placeholder="alias" ng-blur="targetBlur()">
-				</li>
-				<li  class="tight-form-item">
-					&nbsp;Peak filter
-					<input class="input-medium" type="checkbox" ng-model="target.exOuter" ng-change="targetBlur()">
-				</li>
-			</ul>
-
-			<div class="clearfix"></div>
-		</div>
+<div class="tight-form">
+	<ul class="tight-form-list pull-right">
+		<li class="tight-form-item">
+			<div class="dropdown">
+				<a  class="pointer dropdown-toggle" data-toggle="dropdown" tabindex="1">
+					<i class="fa fa-bars"></i>
+				</a>
+				<ul class="dropdown-menu pull-right" role="menu">
+					<li role="menuitem"><a tabindex="1" ng-click="duplicate()">Duplicate</a></li>
+					<li role="menuitem"><a tabindex="1" ng-click="moveMetricQuery($index, $index-1)">Move up</a></li>
+					<li role="menuitem"><a tabindex="1" ng-click="moveMetricQuery($index, $index+1)">Move down</a></li>
+				</ul>
+			</div>
+		</li>
+		<li class="tight-form-item last">
+			<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
+				<i class="fa fa-remove"></i>
+			</a>
+		</li>
+	</ul>
 
-		<!-- TAGS -->
-		<div class="tight-form">
-			<ul class="tight-form-list" role="menu">
-				<li class="tight-form-item">
-					<i class="fa fa-eye invisible"></i>
-				</li>
-				<li class="tight-form-item">
-					Tags
-				</li>
-				<li ng-repeat="(key, value) in target.tags track by $index" class="tight-form-item">
-					{{key}}&nbsp;=&nbsp;{{value}}
-					<a ng-click="removeFilterTag(key)">
-						<i class="fa fa-remove"></i>
-					</a>
-				</li>
+	<ul class="tight-form-list">
+		<li class="tight-form-item" style="min-width: 15px; text-align: center">
+			{{target.refId}}
+		</li>
+		<li>
+			<a class="tight-form-item" ng-click="target.hide = !target.hide; targetBlur();" role="menuitem">
+				<i class="fa fa-eye"></i>
+			</a>
+		</li>
+		<li class="tight-form-item">
+			Metric
+		</li>
+		<li>
+			<input type="text" class="input-large tight-form-input"
+			ng-model="target.metric"
+			spellcheck="false"
+			bs-typeahead="suggestMetrics"
+			placeholder="metric name"
+			data-min-length=0 data-items=100
+			ng-blur="targetBlur()">
+			<a bs-tooltip="target.errors.metric"
+				style="color: rgb(229, 189, 28)"
+				ng-show="target.errors.metric">
+				<i class="fa fa-warning"></i>
+			</a>
+		</li>
+		<li class="tight-form-item">
+			Alias
+		</li>
+		<li>
+			<input type="text" class="input-medium tight-form-input" ng-model="target.alias"
+			spellcheck='false' placeholder="alias" ng-blur="targetBlur()">
+		</li>
+		<li  class="tight-form-item">
+			&nbsp;Peak filter
+			<input class="input-medium" type="checkbox" ng-model="target.exOuter" ng-change="targetBlur()">
+		</li>
+	</ul>
 
-				<li class="tight-form-item" ng-hide="addFilterTagMode">
-					<a ng-click="addFilterTag()">
-						<i class="fa fa-plus"></i>
-					</a>
-				</li>
+	<div class="clearfix"></div>
+</div>
 
-				<li ng-show="addFilterTagMode">
-					<input type="text"
-					class="input-small tight-form-input"
-					spellcheck='false'
-					bs-typeahead="suggestTagKeys"
-					ng-change="validateFilterTag()"
-					data-min-length=0 data-items=100
-					ng-model="target.currentTagKey"
-					placeholder="key">
-				</li>
-				<li ng-show="addFilterTagMode">
-					<input type="text"
-					class="input-small tight-form-input"
-					spellcheck='false'
-					bs-typeahead="suggestTagValues"
-					ng-change="validateFilterTag()"
-					data-min-length=0 data-items=100
-					ng-model="target.currentTagValue"
-					placeholder="value">
-					<a bs-tooltip="target.errors.tags"
-						style="color: rgb(229, 189, 28)"
-						ng-show="target.errors.tags">
-						<i class="fa fa-warning"></i>
-					</a>
-					<li class="tight-form-item" ng-show="addFilterTagMode">
-						<a ng-click="addFilterTag()">
-							<i ng-show="target.errors.tags" class="fa fa-remove"></i>
-							<i ng-hide="target.errors.tags" class="fa fa-plus-circle"></i>
-						</a>
-					</li>
-				</li>
-			</ul>
-			<div class="clearfix"></div>
-		</div>
+<!-- TAGS -->
+<div class="tight-form">
+	<ul class="tight-form-list" role="menu">
+		<li class="tight-form-item tight-form-align">
+			Tags
+		</li>
+		<li ng-repeat="(key, value) in target.tags track by $index" class="tight-form-item">
+			{{key}}&nbsp;=&nbsp;{{value}}
+			<a ng-click="removeFilterTag(key)">
+				<i class="fa fa-remove"></i>
+			</a>
+		</li>
 
-		<!-- GROUP BY -->
-		<div class="tight-form">
-			<ul class="tight-form-list" role="menu">
-				<li class="tight-form-item">
-					<i class="fa fa-eye invisible"></i>
-				</li>
+		<li class="tight-form-item" ng-hide="addFilterTagMode">
+			<a ng-click="addFilterTag()">
+				<i class="fa fa-plus"></i>
+			</a>
+		</li>
 
-				<li class="tight-form-item">
-					Group By
-				</li>
+		<li ng-show="addFilterTagMode">
+			<input type="text"
+			class="input-small tight-form-input"
+			spellcheck='false'
+			bs-typeahead="suggestTagKeys"
+			ng-change="validateFilterTag()"
+			data-min-length=0 data-items=100
+			ng-model="target.currentTagKey"
+			placeholder="key">
+		</li>
+		<li ng-show="addFilterTagMode">
+			<input type="text"
+			class="input-small tight-form-input"
+			spellcheck='false'
+			bs-typeahead="suggestTagValues"
+			ng-change="validateFilterTag()"
+			data-min-length=0 data-items=100
+			ng-model="target.currentTagValue"
+			placeholder="value">
+			<a bs-tooltip="target.errors.tags"
+				style="color: rgb(229, 189, 28)"
+				ng-show="target.errors.tags">
+				<i class="fa fa-warning"></i>
+			</a>
+			<li class="tight-form-item" ng-show="addFilterTagMode">
+				<a ng-click="addFilterTag()">
+					<i ng-show="target.errors.tags" class="fa fa-remove"></i>
+					<i ng-hide="target.errors.tags" class="fa fa-plus-circle"></i>
+				</a>
+			</li>
+		</li>
+	</ul>
+	<div class="clearfix"></div>
+</div>
 
-				<li class="tight-form-item" ng-show="target.groupByTags">
-					tags:
-				</li>
+<!-- GROUP BY -->
+<div class="tight-form">
+	<ul class="tight-form-list" role="menu">
+		<li class="tight-form-item tight-form-align">
+			Group By
+		</li>
 
-				<li ng-repeat="key in target.groupByTags track by $index" class="tight-form-item">
-					{{key}}
-					<a ng-click="removeGroupByTag($index)">
-						<i class="fa fa-remove"></i>
-					</a>
-				</li>
+		<li class="tight-form-item" ng-show="target.groupByTags">
+			tags:
+		</li>
 
-				<li class="tight-form-item" ng-show="target.groupByTags && target.nonTagGroupBys">
-					and by:
-				</li>
+		<li ng-repeat="key in target.groupByTags track by $index" class="tight-form-item">
+			{{key}}
+			<a ng-click="removeGroupByTag($index)">
+				<i class="fa fa-remove"></i>
+			</a>
+		</li>
 
-				<li ng-repeat="groupByObject in target.nonTagGroupBys track by $index" class="tight-form-item">
-					{{_.values(groupByObject)}}
-					<a ng-click="removeNonTagGroupBy($index)">
-						<i class="fa fa-remove"></i>
-					</a>
-				</li>
+		<li class="tight-form-item" ng-show="target.groupByTags && target.nonTagGroupBys">
+			and by:
+		</li>
 
-				<li class="tight-form-item" ng-hide="addGroupByMode">
-					<a ng-click="addGroupBy()">
-						<i class="fa fa-plus"></i>
-					</a>
-				</li>
-				<li ng-show="addGroupByMode">
-					<select class="input-small tight-form-input"
-						ng-change="changeGroupByInput()"
-						ng-model="target.currentGroupByType"
-						ng-options="f for f in ['tag','value','time']"></select>
-				</li>
-				<li ng-show="isTagGroupBy">
-					<input type="text"
-					class="input-small tight-form-input"
-					spellcheck='false'
-					bs-typeahead="suggestTagKeys"
-					ng-change = "validateGroupBy()"
-					data-min-length=0 data-items=100
-					ng-model="target.groupBy.tagKey"
-					placeholder="key">
-					<a bs-tooltip="target.errors.groupBy.tagKey"
-						style="color: rgb(229, 189, 28)"
-						ng-show="target.errors.groupBy.tagKey">
-						<i class="fa fa-warning"></i>
-					</a>
-				</li>
-				<li ng-show="isValueGroupBy">
-					<input type="text"
-					class="input-mini tight-form-input"
-					spellcheck='false'
-					ng-model="target.groupBy.valueRange"
-					placeholder="range"
-					bs-tooltip="'Range on which values are considered in the same group'"
-					ng-change = "validateGroupBy()" >
-					<a bs-tooltip="target.errors.groupBy.valueRange"
-						style="color: rgb(229, 189, 28)"
-						ng-show="target.errors.groupBy.valueRange">
-						<i class="fa fa-warning"></i>
-					</a>
-				</li>
-				<li ng-show="isTimeGroupBy">
-					<input type="text"
-					class="input-mini tight-form-input"
-					ng-model="target.groupBy.timeInterval"
-					ng-init="target.groupBy.timeInterval='1s'"
-					placeholder="interval"
-					bs-tooltip="'Duration of time groups'"
-					spellcheck='false'
-					ng-change="validateGroupBy()">
-					<a bs-tooltip="target.errors.groupBy.timeInterval"
-						style="color: rgb(229, 189, 28)"
-						ng-show="target.errors.groupBy.timeInterval">
-						<i class="fa fa-warning"></i>
-					</a>
-				</li>
-				<li ng-show="isTimeGroupBy">
-					<input type="text"
-					class="input-mini tight-form-input"
-					ng-model="target.groupBy.groupCount"
-					placeholder="Count"
-					bs-tooltip="'Number of time groups to be formed'"
-					spellcheck='false'
-					ng-change="validateGroupBy()">
-					<a bs-tooltip="target.errors.groupBy.groupCount"
-						style="color: rgb(229, 189, 28)"
-						ng-show="target.errors.groupBy.groupCount">
-						<i class="fa fa-warning"></i>
-					</a>
-				</li>
-				<li class="tight-form-item" ng-show="addGroupByMode">
-					<a ng-click="addGroupBy()">
-						<i ng-hide="isGroupByValid" class="fa fa-remove"></i>
-						<i ng-show="isGroupByValid" class="fa fa-plus-circle"></i>
-					</a>
-				</li>
-			<div class="clearfix"></div>
-		</div>
+		<li ng-repeat="groupByObject in target.nonTagGroupBys track by $index" class="tight-form-item">
+			{{_.values(groupByObject)}}
+			<a ng-click="removeNonTagGroupBy($index)">
+				<i class="fa fa-remove"></i>
+			</a>
+		</li>
 
-		<!-- HORIZONTAL AGGREGATION -->
-		<div class="tight-form">
-			<ul class="tight-form-list" role="menu">
-				<li class="tight-form-item">
-					<i class="fa fa-eye invisible"></i>
-				</li>
+		<li class="tight-form-item" ng-hide="addGroupByMode">
+			<a ng-click="addGroupBy()">
+				<i class="fa fa-plus"></i>
+			</a>
+		</li>
+		<li ng-show="addGroupByMode">
+			<select class="input-small tight-form-input"
+				ng-change="changeGroupByInput()"
+				ng-model="target.currentGroupByType"
+				ng-options="f for f in ['tag','value','time']"></select>
+		</li>
+		<li ng-show="isTagGroupBy">
+			<input type="text"
+			class="input-small tight-form-input"
+			spellcheck='false'
+			bs-typeahead="suggestTagKeys"
+			ng-change = "validateGroupBy()"
+			data-min-length=0 data-items=100
+			ng-model="target.groupBy.tagKey"
+			placeholder="key">
+			<a bs-tooltip="target.errors.groupBy.tagKey"
+				style="color: rgb(229, 189, 28)"
+				ng-show="target.errors.groupBy.tagKey">
+				<i class="fa fa-warning"></i>
+			</a>
+		</li>
+		<li ng-show="isValueGroupBy">
+			<input type="text"
+			class="input-mini tight-form-input"
+			spellcheck='false'
+			ng-model="target.groupBy.valueRange"
+			placeholder="range"
+			bs-tooltip="'Range on which values are considered in the same group'"
+			ng-change = "validateGroupBy()" >
+			<a bs-tooltip="target.errors.groupBy.valueRange"
+				style="color: rgb(229, 189, 28)"
+				ng-show="target.errors.groupBy.valueRange">
+				<i class="fa fa-warning"></i>
+			</a>
+		</li>
+		<li ng-show="isTimeGroupBy">
+			<input type="text"
+			class="input-mini tight-form-input"
+			ng-model="target.groupBy.timeInterval"
+			ng-init="target.groupBy.timeInterval='1s'"
+			placeholder="interval"
+			bs-tooltip="'Duration of time groups'"
+			spellcheck='false'
+			ng-change="validateGroupBy()">
+			<a bs-tooltip="target.errors.groupBy.timeInterval"
+				style="color: rgb(229, 189, 28)"
+				ng-show="target.errors.groupBy.timeInterval">
+				<i class="fa fa-warning"></i>
+			</a>
+		</li>
+		<li ng-show="isTimeGroupBy">
+			<input type="text"
+			class="input-mini tight-form-input"
+			ng-model="target.groupBy.groupCount"
+			placeholder="Count"
+			bs-tooltip="'Number of time groups to be formed'"
+			spellcheck='false'
+			ng-change="validateGroupBy()">
+			<a bs-tooltip="target.errors.groupBy.groupCount"
+				style="color: rgb(229, 189, 28)"
+				ng-show="target.errors.groupBy.groupCount">
+				<i class="fa fa-warning"></i>
+			</a>
+		</li>
+		<li class="tight-form-item" ng-show="addGroupByMode">
+			<a ng-click="addGroupBy()">
+				<i ng-hide="isGroupByValid" class="fa fa-remove"></i>
+				<i ng-show="isGroupByValid" class="fa fa-plus-circle"></i>
+			</a>
+		</li>
+	</ul>
+	<div class="clearfix"></div>
+</div>
 
-				<li class="tight-form-item">
-					Aggregators
-				</li>
-				<li ng-repeat="aggregatorObject in target.horizontalAggregators track by $index" class="tight-form-item">
-					{{aggregatorObject.name}}&#40;
-					<span ng-repeat="aggKey in _.keys(_.omit(aggregatorObject,'name'))" bs-tooltip="aggKey">
-						{{$last?aggregatorObject[aggKey]:aggregatorObject[aggKey]+","}}
-					</span>
-					&#41;
-					<a ng-click="removeHorizontalAggregator($index)">
-						<i class="fa fa-remove"></i>
-					</a>
-				</li>
+<!-- HORIZONTAL AGGREGATION -->
+<div class="tight-form">
+	<ul class="tight-form-list" role="menu">
+		<li class="tight-form-item tight-form-align">
+			Aggregators
+		</li>
+		<li ng-repeat="aggregatorObject in target.horizontalAggregators track by $index" class="tight-form-item">
+			{{aggregatorObject.name}}&#40;
+			<span ng-repeat="aggKey in _.keys(_.omit(aggregatorObject,'name'))" bs-tooltip="aggKey">
+				{{$last?aggregatorObject[aggKey]:aggregatorObject[aggKey]+","}}
+			</span>
+			&#41;
+			<a ng-click="removeHorizontalAggregator($index)">
+				<i class="fa fa-remove"></i>
+			</a>
+		</li>
 
-				<li class="tight-form-item" ng-hide="addHorizontalAggregatorMode">
-					<a ng-click="addHorizontalAggregator()">
-						<i class="fa fa-plus"></i>
-					</a>
-				</li>
+		<li class="tight-form-item" ng-hide="addHorizontalAggregatorMode">
+			<a ng-click="addHorizontalAggregator()">
+				<i class="fa fa-plus"></i>
+			</a>
+		</li>
 
-				<li ng-show="addHorizontalAggregatorMode">
-					<select class="input-medium tight-form-input"
-						ng-change="changeHorAggregationInput()"
-						ng-model="target.currentHorizontalAggregatorName"
-						ng-options="f for f in ['avg','dev','max','min','rate','sampler','count','sum','least_squares','percentile','scale','div']"></select>
-				</li>
+		<li ng-show="addHorizontalAggregatorMode">
+			<select class="input-medium tight-form-input"
+				ng-change="changeHorAggregationInput()"
+				ng-model="target.currentHorizontalAggregatorName"
+				ng-options="f for f in ['avg','dev','max','min','rate','sampler','count','sum','least_squares','percentile','scale','div']"></select>
+		</li>
 
-				<!-- Different parameters -->
-				<li ng-show="hasSamplingRate" class="tight-form-item">
-					every
-				</li>
-				<li ng-show="hasSamplingRate">
-					<input type="text"
-					class="input-mini tight-form-input"
-					ng-model="target.horAggregator.samplingRate"
-					ng-init="target.horAggregator.samplingRate='1s'"
-					spellcheck='false'
-					ng-change="validateHorizontalAggregator()" >
-					<a bs-tooltip="target.errors.horAggregator.samplingRate"
-						style="color: rgb(229, 189, 28)"
-						ng-show="target.errors.horAggregator.samplingRate">
-						<i class="fa fa-warning"></i>
-					</a>
-				</li>
+		<!-- Different parameters -->
+		<li ng-show="hasSamplingRate" class="tight-form-item">
+			every
+		</li>
+		<li ng-show="hasSamplingRate">
+			<input type="text"
+			class="input-mini tight-form-input"
+			ng-model="target.horAggregator.samplingRate"
+			ng-init="target.horAggregator.samplingRate='1s'"
+			spellcheck='false'
+			ng-change="validateHorizontalAggregator()" >
+			<a bs-tooltip="target.errors.horAggregator.samplingRate"
+				style="color: rgb(229, 189, 28)"
+				ng-show="target.errors.horAggregator.samplingRate">
+				<i class="fa fa-warning"></i>
+			</a>
+		</li>
 
-				<li ng-show="hasUnit" class="tight-form-item">
-					every
-				</li>
-				<li ng-show="hasUnit">
-					<select class="input-medium tight-form-input"
-						ng-model="target.horAggregator.unit"
-						ng-init="target.horAggregator.unit='millisecond'"
-						ng-options="f for f in ['millisecond','second','minute','hour','day','week','month','year']"></select>
-				</li>
+		<li ng-show="hasUnit" class="tight-form-item">
+			every
+		</li>
+		<li ng-show="hasUnit">
+			<select class="input-medium tight-form-input"
+				ng-model="target.horAggregator.unit"
+				ng-init="target.horAggregator.unit='millisecond'"
+				ng-options="f for f in ['millisecond','second','minute','hour','day','week','month','year']"></select>
+		</li>
 
-				<li ng-show="hasFactor" class="tight-form-item">
-					by
-				</li>
-				<li ng-show="hasFactor">
-					<input type="text"
-					class="input-mini tight-form-input"
-					ng-model="target.horAggregator.factor"
-					ng-init="target.horAggregator.factor='1'"
-					spellcheck='false'
-					ng-change="validateHorizontalAggregator()" >
-					<a bs-tooltip="target.errors.horAggregator.factor"
-						style="color: rgb(229, 189, 28)"
-						ng-show="target.errors.horAggregator.factor">
-						<i class="fa fa-warning"></i>
-					</a>
-				</li>
+		<li ng-show="hasFactor" class="tight-form-item">
+			by
+		</li>
+		<li ng-show="hasFactor">
+			<input type="text"
+			class="input-mini tight-form-input"
+			ng-model="target.horAggregator.factor"
+			ng-init="target.horAggregator.factor='1'"
+			spellcheck='false'
+			ng-change="validateHorizontalAggregator()" >
+			<a bs-tooltip="target.errors.horAggregator.factor"
+				style="color: rgb(229, 189, 28)"
+				ng-show="target.errors.horAggregator.factor">
+				<i class="fa fa-warning"></i>
+			</a>
+		</li>
 
-				<li ng-show="hasPercentile" class="tight-form-item">
-					percentile
-				</li>
-				<li ng-show="hasPercentile">
-					<input type="text"
-					class="input-mini tight-form-input"
-					ng-model="target.horAggregator.percentile"
-					ng-init="target.horAggregator.percentile='0.75'"
-					spellcheck='false'
-					ng-change="validateHorizontalAggregator()" >
-					<a bs-tooltip="target.errors.horAggregator.percentile"
-						style="color: rgb(229, 189, 28)"
-						ng-show="target.errors.horAggregator.percentile">
-						<i class="fa fa-warning"></i>
-					</a>
-				</li>
+		<li ng-show="hasPercentile" class="tight-form-item">
+			percentile
+		</li>
+		<li ng-show="hasPercentile">
+			<input type="text"
+			class="input-mini tight-form-input"
+			ng-model="target.horAggregator.percentile"
+			ng-init="target.horAggregator.percentile='0.75'"
+			spellcheck='false'
+			ng-change="validateHorizontalAggregator()" >
+			<a bs-tooltip="target.errors.horAggregator.percentile"
+				style="color: rgb(229, 189, 28)"
+				ng-show="target.errors.horAggregator.percentile">
+				<i class="fa fa-warning"></i>
+			</a>
+		</li>
 
-				<li class="tight-form-item" ng-show="addHorizontalAggregatorMode">
-					<a ng-click="addHorizontalAggregator()">
-						<i ng-hide="isAggregatorValid" class="fa fa-remove"></i>
-						<i ng-show="isAggregatorValid" class="fa fa-plus-circle"></i>
-					</a>
-				</li>
-			</ul>
-			<div class="clearfix"></div>
-		</div>
-	</div>
+		<li class="tight-form-item" ng-show="addHorizontalAggregatorMode">
+			<a ng-click="addHorizontalAggregator()">
+				<i ng-hide="isAggregatorValid" class="fa fa-remove"></i>
+				<i ng-show="isAggregatorValid" class="fa fa-plus-circle"></i>
+			</a>
+		</li>
+	</ul>
+	<div class="clearfix"></div>
 </div>
 
-<section class="grafana-metric-options" ng-controller="KairosDBQueryCtrl">
-	<div class="tight-form">
-		<ul class="tight-form-list">
-			<li class="tight-form-item tight-form-item-icon">
-				<i class="fa fa-wrench"></i>
-			</li>
-
-			<li class="tight-form-item">
-				Downsampling with
-			</li>
-			<li>
-				<select class="input-medium tight-form-input" ng-change="panelBlur()" ng-model="panel.downsampling" ng-options="f for f in ['(NONE)','avg', 'sum', 'min', 'max', 'dev']" ></select>
-			</li>
 
-			<!-- SAMPLING RATE -->
-			<li ng-hide="panel.downsampling=='(NONE)'" class="tight-form-item">
-				every
-			</li>
-			<li>
-				<input type="text"
-				ng-hide="panel.downsampling=='(NONE)'"
-				class="input-mini tight-form-input"
-				ng-model="panel.sampling"
-				placeholder="{{interval}}"
-				bs-tooltip="'Leave blank for auto handling based on time range and panel width'"
-				spellcheck='false'
-				ng-blur="panelBlur()" >
-				<a bs-tooltip="target.errors.sampling"
-					style="color: rgb(229, 189, 28)"
-					ng-show="target.errors.sampling">
-					<i class="fa fa-warning"></i>
-				</a>
-			</li>
-		</ul>
-		<div class="clearfix"></div>
-	</div>
-</section>

+ 37 - 0
public/app/plugins/datasource/kairosdb/partials/query.options.html

@@ -0,0 +1,37 @@
+<section class="grafana-metric-options" ng-controller="KairosDBQueryCtrl">
+	<div class="tight-form">
+		<ul class="tight-form-list">
+			<li class="tight-form-item tight-form-item-icon">
+				<i class="fa fa-wrench"></i>
+			</li>
+
+			<li class="tight-form-item">
+				Downsampling with
+			</li>
+			<li>
+				<select class="input-medium tight-form-input" ng-change="panelBlur()" ng-model="panel.downsampling" ng-options="f for f in ['(NONE)','avg', 'sum', 'min', 'max', 'dev']" ></select>
+			</li>
+
+			<!-- SAMPLING RATE -->
+			<li ng-hide="panel.downsampling=='(NONE)'" class="tight-form-item">
+				every
+			</li>
+			<li>
+				<input type="text"
+				ng-hide="panel.downsampling=='(NONE)'"
+				class="input-mini tight-form-input"
+				ng-model="panel.sampling"
+				placeholder="{{interval}}"
+				bs-tooltip="'Leave blank for auto handling based on time range and panel width'"
+				spellcheck='false'
+				ng-blur="panelBlur()" >
+				<a bs-tooltip="target.errors.sampling"
+					style="color: rgb(229, 189, 28)"
+					ng-show="target.errors.sampling">
+					<i class="fa fa-warning"></i>
+				</a>
+			</li>
+		</ul>
+		<div class="clearfix"></div>
+	</div>
+</section>

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

@@ -8,8 +8,7 @@
   "module": "plugins/datasource/kairosdb/datasource",
 
   "partials": {
-    "config": "app/plugins/datasource/kairosdb/partials/config.html",
-    "query": "app/plugins/datasource/kairosdb/partials/query.editor.html"
+    "config": "app/plugins/datasource/kairosdb/partials/config.html"
   },
 
   "metrics": true,

+ 35 - 0
public/app/plugins/datasource/mixed/datasource.js

@@ -0,0 +1,35 @@
+define([
+  'angular',
+  'lodash',
+],
+function (angular, _) {
+  'use strict';
+
+  var module = angular.module('grafana.services');
+
+  module.factory('MixedDatasource', function($q, backendSrv, datasourceSrv) {
+
+    function MixedDatasource() {
+    }
+
+    MixedDatasource.prototype.query = function(options) {
+      var sets = _.groupBy(options.targets, 'datasource');
+      var promises = _.map(sets, function(targets) {
+        return datasourceSrv.get(targets[0].datasource).then(function(ds) {
+          var opt = angular.copy(options);
+          opt.targets = targets;
+          return ds.query(opt);
+        });
+      });
+
+      return $q.all(promises).then(function(results) {
+        return { data: _.flatten(_.pluck(results, 'data')) };
+      });
+
+    };
+
+    return MixedDatasource;
+
+  });
+
+});

+ 12 - 0
public/app/plugins/datasource/mixed/plugin.json

@@ -0,0 +1,12 @@
+{
+  "pluginType": "datasource",
+  "name": "Mixed datasource",
+  "builtIn": true,
+  "mixed": true,
+
+  "type": "mixed",
+  "serviceName": "MixedDatasource",
+
+  "module": "plugins/datasource/mixed/datasource",
+  "metrics": true
+}

+ 1 - 0
public/app/plugins/datasource/opentsdb/datasource.js

@@ -3,6 +3,7 @@ define([
   'lodash',
   'kbn',
   'moment',
+  './directives',
   './queryCtrl',
 ],
 function (angular, _, kbn) {

+ 16 - 0
public/app/plugins/datasource/opentsdb/directives.js

@@ -0,0 +1,16 @@
+define([
+  'angular',
+],
+function (angular) {
+  'use strict';
+
+  var module = angular.module('grafana.directives');
+
+  module.directive('metricQueryEditorOpentsdb', function() {
+    return {
+      controller: 'OpenTSDBQueryCtrl',
+      templateUrl: 'app/plugins/datasource/opentsdb/partials/query.editor.html',
+    };
+  });
+
+});

+ 190 - 223
public/app/plugins/datasource/opentsdb/partials/query.editor.html

@@ -1,241 +1,208 @@
-<div class="editor-row" style="margin-top: 10px;">
-
-  <div  ng-repeat="target in panel.targets"
-        style="margin-bottom: 10px;"
-        ng-class="{'tight-form-disabled': target.hide}"
-        ng-controller="OpenTSDBQueryCtrl"
-        ng-init="init()">
-
-    <div class="tight-form">
-			<ul class="tight-form-list pull-right">
-				<li class="tight-form-item">
-					<div class="dropdown">
-						<a  class="pointer dropdown-toggle"
-							data-toggle="dropdown"
-							tabindex="1">
-							<i class="fa fa-bars"></i>
+<div class="tight-form">
+	<ul class="tight-form-list pull-right">
+		<li class="tight-form-item">
+			<div class="dropdown">
+				<a  class="pointer dropdown-toggle"
+					data-toggle="dropdown"
+					tabindex="1">
+					<i class="fa fa-bars"></i>
+				</a>
+				<ul class="dropdown-menu pull-right" role="menu">
+					<li role="menuitem">
+						<a  tabindex="1"
+							ng-click="duplicate()">
+							Duplicate
 						</a>
-						<ul class="dropdown-menu pull-right" role="menu">
-							<li role="menuitem">
-								<a  tabindex="1"
-									ng-click="duplicate()">
-									Duplicate
-								</a>
-							</li>
-						</ul>
-					</div>
-				</li>
-				<li class="tight-form-item last">
-					<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
-            <i class="fa fa-remove"></i>
-          </a>
-        </li>
-      </ul>
-
-      <ul class="tight-form-list">
-        <li>
-          <a  class="tight-form-item"
-              ng-click="target.hide = !target.hide; get_data();"
-              role="menuitem">
-            <i class="fa fa-eye"></i>
-          </a>
-        </li>
-      </ul>
-
-      <ul class="tight-form-list" role="menu">
-				<li class="tight-form-item" style="width: 86px">
-					Metric
-				</li>
-        <li>
-          <input type="text"
-                 class="input-large tight-form-input"
-                 ng-model="target.metric"
-                 spellcheck='false'
-                 bs-typeahead="suggestMetrics"
-                 placeholder="metric name"
-                 data-min-length=0 data-items=100
-								 ng-model-onblur
-                 ng-change="targetBlur()"
-                 >
-          <a bs-tooltip="target.errors.metric"
-             style="color: rgb(229, 189, 28)"
-             ng-show="target.errors.metric">
-            <i class="fa fa-warning"></i>
-          </a>
-        </li>
-				<li class="tight-form-item">
-          Aggregator
-        </li>
-        <li>
-          <select ng-model="target.aggregator"
-                  class="tight-form-input input-small"
-                  ng-options="agg for agg in aggregators"
-                  ng-change="targetBlur()">
-          </select>
-          <a bs-tooltip="target.errors.aggregator"
-             style="color: rgb(229, 189, 28)"
-             ng-show="target.errors.aggregator">
-            <i class="fa fa-warning"></i>
-          </a>
-        </li>
-
-				<li class="tight-form-item">
-          Alias:
-					<tip>Use patterns like $tag_tagname to replace part of the alias for a tag value</tip>
-        </li>
-				<li>
-					<input type="text"
-                 class="tight-form-input input-large"
-                 ng-model="target.alias"
-                 spellcheck='false'
-                 placeholder="series alias"
-                 data-min-length=0 data-items=100
-                 ng-blur="targetBlur()"
-                 />
-        </li>
-
-      </ul>
-
-      <div class="clearfix"></div>
-    </div>
-
-    <div class="tight-form">
-			<ul class="tight-form-list" role="menu">
-				<li class="tight-form-item">
-					<i class="fa fa-eye invisible"></i>
-				</li>
-
-				<li class="tight-form-item" style="width: 86px">
-					Down sample
-				</li>
+					</li>
+				</ul>
+			</div>
+		</li>
+		<li class="tight-form-item last">
+			<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
+				<i class="fa fa-remove"></i>
+			</a>
+		</li>
+	</ul>
+
+	<ul class="tight-form-list">
+		<li class="tight-form-item" style="min-width: 15px; text-align: center">
+			{{target.refId}}
+		</li>
+		<li>
+			<a  class="tight-form-item"
+				ng-click="target.hide = !target.hide; get_data();"
+				role="menuitem">
+				<i class="fa fa-eye"></i>
+			</a>
+		</li>
+	</ul>
+
+	<ul class="tight-form-list" role="menu">
+		<li class="tight-form-item" style="width: 86px">
+			Metric
+		</li>
+		<li>
+			<input type="text"
+				class="input-large tight-form-input"
+				ng-model="target.metric"
+				spellcheck='false'
+				bs-typeahead="suggestMetrics"
+				placeholder="metric name"
+				data-min-length=0 data-items=100
+				ng-model-onblur
+				ng-change="targetBlur()">
+			</input>
+			<a bs-tooltip="target.errors.metric" style="color: rgb(229, 189, 28)" ng-show="target.errors.metric">
+				<i class="fa fa-warning"></i>
+			</a>
+		</li>
+		<li class="tight-form-item">
+			Aggregator
+		</li>
+		<li>
+			<select ng-model="target.aggregator" class="tight-form-input input-small"
+				ng-options="agg for agg in aggregators"
+				ng-change="targetBlur()">
+			</select>
+			<a bs-tooltip="target.errors.aggregator" style="color: rgb(229, 189, 28)" ng-show="target.errors.aggregator">
+				<i class="fa fa-warning"></i>
+			</a>
+		</li>
+
+		<li class="tight-form-item">
+			Alias:
+			<tip>Use patterns like $tag_tagname to replace part of the alias for a tag value</tip>
+		</li>
+		<li>
+			<input type="text" class="tight-form-input input-large"
+				ng-model="target.alias"
+				spellcheck='false'
+				placeholder="series alias"
+				data-min-length=0 data-items=100
+				ng-blur="targetBlur()"></input>
+		</li>
+	</ul>
+
+	<div class="clearfix"></div>
+</div>
 
-				<li>
-          <input type="text"
-                 class="input-large tight-form-input"
-                 ng-model="target.downsampleInterval"
-								 ng-model-onblur
-                 ng-change="targetBlur()"
-                 placeholder="interval (empty = auto)"
-                 >
-        </li>
-
-        <li class="tight-form-item">
-          Aggregator
-        </li>
-
-        <li>
-          <select ng-model="target.downsampleAggregator"
-                  class="tight-form-input input-small"
-                  ng-options="agg for agg in aggregators"
-                  ng-change="targetBlur()">
-          </select>
-        </li>
-
-				<li class="tight-form-item">
-					<editor-checkbox text="Disable downsampling" model="target.disableDownsampling" change="targetBlur()"></editor-checkbox>
+<div class="tight-form">
+	<ul class="tight-form-list" role="menu">
+		<li class="tight-form-item tight-form-align" style="width: 86px">
+			Down sample
+		</li>
+
+		<li>
+			<input type="text" class="input-large tight-form-input"
+				ng-model="target.downsampleInterval"
+				ng-model-onblur
+				ng-change="targetBlur()"
+				placeholder="interval (empty = auto)">
+			</li>
+
+			<li class="tight-form-item">
+				Aggregator
+			</li>
+
+			<li>
+				<select ng-model="target.downsampleAggregator" class="tight-form-input input-small"
+					ng-options="agg for agg in aggregators"
+					ng-change="targetBlur()">
+				</select>
+			</li>
+
+			<li class="tight-form-item">
+				<editor-checkbox text="Disable downsampling" model="target.disableDownsampling" change="targetBlur()"></editor-checkbox>
+			</li>
+
+		</ul>
+		<div class="clearfix"></div>
+	</div>
+
+	<div class="tight-form">
+		<ul class="tight-form-list" role="menu">
+			<li class="tight-form-item tight-form-align" style="width: 86px">
+				Tags
+			</li>
+			<li ng-repeat="(key, value) in target.tags track by $index" class="tight-form-item">
+				{{key}}&nbsp;=&nbsp;{{value}}
+				<a ng-click="removeTag(key)">
+					<i class="fa fa-remove"></i>
+				</a>
+			</li>
+
+			<li class="tight-form-item" ng-hide="addTagMode">
+				<a ng-click="addTag()">
+					<i class="fa fa-plus"></i>
+				</a>
+			</li>
+
+			<li ng-show="addTagMode">
+				<input type="text" class="input-small tight-form-input"
+					spellcheck='false'
+					bs-typeahead="suggestTagKeys"
+					data-min-length=0 data-items=100
+					ng-model="target.currentTagKey"
+					placeholder="key">
+					<input type="text"
+						class="input-small tight-form-input"
+						spellcheck='false'
+						bs-typeahead="suggestTagValues"
+						data-min-length=0 data-items=100
+						ng-model="target.currentTagValue"
+						placeholder="value">
+					</input>
+					<a ng-click="addTag()">
+						add tag
+					</a>
+					<a bs-tooltip="target.errors.tags"
+						style="color: rgb(229, 189, 28)"
+						ng-show="target.errors.tags">
+						<i class="fa fa-warning"></i>
+					</a>
 				</li>
-
 			</ul>
-      <div class="clearfix"></div>
-    </div>
+			<div class="clearfix"></div>
+		</div>
 
 		<div class="tight-form">
 			<ul class="tight-form-list" role="menu">
-				<li class="tight-form-item">
-					<i class="fa fa-eye invisible"></i>
+				<li class="tight-form-item tight-form-align" style="width: 86px">
+					<editor-checkbox text="Rate" model="target.shouldComputeRate" change="targetBlur()"></editor-checkbox>
 				</li>
 
-				<li class="tight-form-item" style="width: 86px">
-           Tags
-        </li>
-        <li ng-repeat="(key, value) in target.tags track by $index" class="tight-form-item">
-          {{key}}&nbsp;=&nbsp;{{value}}
-          <a ng-click="removeTag(key)">
-            <i class="fa fa-remove"></i>
-          </a>
-        </li>
-
-        <li class="tight-form-item" ng-hide="addTagMode">
-          <a ng-click="addTag()">
-            <i class="fa fa-plus"></i>
-          </a>
-        </li>
-
-        <li ng-show="addTagMode">
-            <input type="text"
-                   class="input-small tight-form-input"
-                   spellcheck='false'
-                   bs-typeahead="suggestTagKeys"
-                   data-min-length=0 data-items=100
-                   ng-model="target.currentTagKey"
-                   placeholder="key">
-            <input type="text"
-                   class="input-small tight-form-input"
-                   spellcheck='false'
-                   bs-typeahead="suggestTagValues"
-                   data-min-length=0 data-items=100
-                   ng-model="target.currentTagValue"
-                   placeholder="value">
-            <a ng-click="addTag()">
-							add tag
-            </a>
-            <a bs-tooltip="target.errors.tags"
-               style="color: rgb(229, 189, 28)"
-               ng-show="target.errors.tags">
-              <i class="fa fa-warning"></i>
-            </a>
-        </li>
-      </ul>
-      <div class="clearfix"></div>
-    </div>
-
-		<div class="tight-form">
-			<ul class="tight-form-list" role="menu">
-				<li class="tight-form-item">
-					<i class="fa fa-eye invisible"></i>
+				<li class="tight-form-item" ng-hide="!target.shouldComputeRate">
+					<editor-checkbox text="Counter" model="target.isCounter" change="targetBlur()"></editor-checkbox>
 				</li>
 
-				<li class="tight-form-item" style="width: 86px">
-					<editor-checkbox text="Rate" model="target.shouldComputeRate" change="targetBlur()"></editor-checkbox>
+				<li class="tight-form-item" ng-hide="!target.isCounter">
+					Counter Max:
 				</li>
 
-        <li class="tight-form-item" ng-hide="!target.shouldComputeRate">
-					<editor-checkbox text="Counter" model="target.isCounter" change="targetBlur()"></editor-checkbox>
+				<li ng-hide="!target.isCounter">
+					<input type="text" class="tight-form-input input-small"
+						ng-disabled="!target.shouldComputeRate"
+						ng-model="target.counterMax"
+						spellcheck='false'
+						placeholder="max value"
+						ng-model-onblur
+						ng-blur="targetBlur()"></input>
 				</li>
-
 				<li class="tight-form-item" ng-hide="!target.isCounter">
-          Counter Max:
-        </li>
-
-        <li ng-hide="!target.isCounter">
-          <input type="text"
-                 class="tight-form-input input-small"
-                 ng-disabled="!target.shouldComputeRate"
-                 ng-model="target.counterMax"
-                 spellcheck='false'
-                 placeholder="max value"
-								 ng-model-onblur
-                 ng-blur="targetBlur()"
-                 />
-        </li>
-        <li class="tight-form-item" ng-hide="!target.isCounter">
-          Reset Value:
-        </li>
-        <li ng-hide="!target.isCounter">
-          <input type="text"
-                 class="tight-form-input input-small"
-                 ng-disabled="!target.shouldComputeRate"
-                 ng-model="target.counterResetValue"
-                 spellcheck='false'
-                 placeholder="reset value"
-								 ng-model-onblur
-                 ng-blur="targetBlur()"
-                 />
-        </li>
+					Reset Value:
+				</li>
+				<li ng-hide="!target.isCounter">
+					<input type="text" class="tight-form-input input-small"
+						ng-disabled="!target.shouldComputeRate"
+						ng-model="target.counterResetValue"
+						spellcheck='false'
+						placeholder="reset value"
+						ng-model-onblur
+						ng-blur="targetBlur()"></input>
+				</li>
 			</ul>
 
-      <div class="clearfix"></div>
-    </div>
-
-  </div>
+			<div class="clearfix"></div>
+		</div>
+	</div>
 </div>

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

@@ -8,8 +8,7 @@
   "module": "plugins/datasource/opentsdb/datasource",
 
   "partials": {
-    "config": "app/plugins/datasource/opentsdb/partials/config.html",
-    "query": "app/plugins/datasource/opentsdb/partials/query.editor.html"
+    "config": "app/plugins/datasource/opentsdb/partials/config.html"
   },
 
   "metrics": true

+ 1 - 0
public/app/plugins/datasource/opentsdb/queryCtrl.js

@@ -113,6 +113,7 @@ function (angular, _, kbn) {
       return errs;
     }
 
+    $scope.init();
   });
 
 });

+ 12 - 1
public/app/services/datasourceSrv.js

@@ -20,13 +20,24 @@ function (angular, _, config) {
         if (value.meta && value.meta.metrics) {
           self.metricSources.push({
             value: key === config.defaultDatasource ? null : key,
-            name: key
+            name: key,
+            meta: value.meta,
           });
         }
         if (value.meta && value.meta.annotations) {
           self.annotationSources.push(value);
         }
       });
+
+      this.metricSources.sort(function(a, b) {
+        if (a.meta.builtIn || a.name > b.name) {
+          return 1;
+        }
+        if (a.name < b.name) {
+          return -1;
+        }
+        return 0;
+      });
     };
 
     this.get = function(name) {

+ 0 - 8
public/css/less/bootswatch.dark.less

@@ -358,7 +358,6 @@ div.subnav {
 
 // BUTTONS
 // -----------------------------------------------------
-
 .btn {
 	padding: 5px 12px;
 	background-image: none;
@@ -389,13 +388,6 @@ div.subnav {
 }
 
 .btn-group {
-
-	& > .btn:first-child,
-	& > .btn:last-child,
-	& > .dropdown-toggle {
-		.border-radius(0);
-	}
-
 	& > .btn + .dropdown-toggle {
 		.box-shadow(none);
 	}

+ 9 - 4
public/css/less/tightform.less

@@ -22,12 +22,14 @@
 
 .tight-form-container-no-item-borders {
   border: 1px solid @grafanaTargetBorder;
+  border-bottom: none;
 
   .tight-form, .tight-form-item, [type=text].tight-form-input, [type=text].tight-form-clear-input  {
     border: none;
   }
 }
 
+
 .spaced-form {
   .tight-form {
     margin: 7px 0;
@@ -42,12 +44,11 @@
 }
 
 .tight-form-container {
+  border-bottom: 1px solid @grafanaTargetBorder;
+
   .tight-form:last-child {
     border-bottom: none;
   }
-  &:last-child {
-    border-bottom: 1px solid @grafanaTargetBorder;
-  }
 }
 
 .tight-form-btn {
@@ -63,7 +64,7 @@
 }
 
 .grafana-metric-options {
-  margin-top: 35px;
+  margin-top: 25px;
 }
 
 .tight-form-item {
@@ -209,3 +210,7 @@ select.tight-form-input {
   }
 }
 
+.tight-form-align {
+  padding-left: 66px;
+}
+

+ 1 - 3
public/test/specs/graphiteTargetCtrl-specs.js

@@ -13,9 +13,7 @@ define([
     beforeEach(ctx.createControllerPhase('GraphiteQueryCtrl'));
 
     beforeEach(function() {
-      ctx.scope.target = {
-        target: 'aliasByNode(scaleToSeconds(test.prod.*,1),2)'
-      };
+      ctx.scope.target = {target: 'aliasByNode(scaleToSeconds(test.prod.*,1),2)'};
 
       ctx.scope.datasource = ctx.datasource;
       ctx.scope.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([]));

+ 58 - 0
public/test/specs/panelSrv-specs.js

@@ -0,0 +1,58 @@
+define([
+  'helpers',
+  'features/panel/panelSrv',
+], function() {
+  'use strict';
+
+  describe('PanelSrv', function() {
+    var _panelSrv;
+    var _panelScope;
+    var _datasourceSrvStub;
+
+    beforeEach(module('grafana.services'));
+    beforeEach(module(function($provide) {
+      _datasourceSrvStub = {
+        getMetricSources: sinon.spy(),
+      };
+      $provide.value('datasourceSrv', _datasourceSrvStub);
+    }));
+
+    beforeEach(inject(function(panelSrv, $rootScope) {
+      _panelSrv = panelSrv;
+      _panelScope = $rootScope.$new();
+      _panelScope.panel = {
+        targets: [],
+      };
+      _panelScope.dashboardViewState = {
+        registerPanel: sinon.spy(),
+      };
+    }));
+
+    describe('init', function() {
+      beforeEach(function() {
+        _panelSrv.init(_panelScope);
+      });
+
+      describe('addDataQuery', function() {
+        it('should add target', function() {
+          _panelScope.addDataQuery();
+          expect(_panelScope.panel.targets.length).to.be(1);
+        });
+
+        it('should set refId', function() {
+          _panelScope.addDataQuery();
+          expect(_panelScope.panel.targets[0].refId).to.be('A');
+        });
+
+        it('should set refId to first available letter', function() {
+          _panelScope.panel.targets = [{refId: 'A'}];
+          _panelScope.addDataQuery();
+          expect(_panelScope.panel.targets[1].refId).to.be('B');
+        });
+      });
+
+    });
+  });
+
+});
+

+ 1 - 0
public/test/test-main.js

@@ -139,6 +139,7 @@ require([
     'specs/seriesOverridesCtrl-specs',
     'specs/shareModalCtrl-specs',
     'specs/timeSrv-specs',
+    'specs/panelSrv-specs',
     'specs/templateSrv-specs',
     'specs/templateValuesSrv-specs',
     'specs/kbn-format-specs',

+ 1 - 0
public/vendor/bootstrap/less/bootstrap.less

@@ -32,6 +32,7 @@
 
 // Components: Buttons & Alerts
 @import "buttons.less";
+@import "button-groups.less";
 @import "alerts.less"; // Note: alerts share common CSS with buttons and thus have styles in buttons.less
 
 // Components: Nav