Explorar o código

Merge branch 'master' into dashboard_tag_colors

Torkel Ödegaard %!s(int64=11) %!d(string=hai) anos
pai
achega
3fec2cdfd6

+ 1 - 0
CHANGELOG.md

@@ -5,6 +5,7 @@
 - [Issue #578](https://github.com/grafana/grafana/issues/578). Dashboard: Row option to display row title even when the row is visible
 - [Issue #672](https://github.com/grafana/grafana/issues/672). Dashboard: panel fullscreen & edit state is present in url, can now link to graph in edit & fullscreen mode.
 - [Issue #709](https://github.com/grafana/grafana/issues/709). Dashboard: Small UI look polish to search results, made dashboard title link are larger
+- [Issue #425](https://github.com/grafana/grafana/issues/425). Graph: New section in 'Display Styles' tab to override any display setting on per series bases (mix and match lines, bars, points, fill, stack, line width etc)
 
 **Fixes**
 - [Issue #696](https://github.com/grafana/grafana/issues/696). Graph: Fix for y-axis format 'none' when values are in scientific notation (ex 2.3e-13)

+ 1 - 1
package.json

@@ -34,7 +34,7 @@
     "grunt-string-replace": "~0.2.4",
     "grunt-usemin": "^2.1.1",
     "jshint-stylish": "~0.1.5",
-    "karma": "~0.12.16",
+    "karma": "~0.12.21",
     "karma-chrome-launcher": "~0.1.4",
     "karma-coffee-preprocessor": "~0.1.2",
     "karma-coverage": "^0.2.5",

+ 14 - 14
src/app/components/kbn.js

@@ -231,36 +231,36 @@ function($, _, moment) {
         if (type === 0) {
           roundUp ? dateTime.endOf('year') : dateTime.startOf('year');
         } else if (type === 1) {
-          dateTime.add('years',num);
+          dateTime.add(num, 'years');
         } else if (type === 2) {
-          dateTime.subtract('years',num);
+          dateTime.subtract(num, 'years');
         }
         break;
       case 'M':
         if (type === 0) {
           roundUp ? dateTime.endOf('month') : dateTime.startOf('month');
         } else if (type === 1) {
-          dateTime.add('months',num);
+          dateTime.add(num, 'months');
         } else if (type === 2) {
-          dateTime.subtract('months',num);
+          dateTime.subtract(num, 'months');
         }
         break;
       case 'w':
         if (type === 0) {
           roundUp ? dateTime.endOf('week') : dateTime.startOf('week');
         } else if (type === 1) {
-          dateTime.add('weeks',num);
+          dateTime.add(num, 'weeks');
         } else if (type === 2) {
-          dateTime.subtract('weeks',num);
+          dateTime.subtract(num, 'weeks');
         }
         break;
       case 'd':
         if (type === 0) {
           roundUp ? dateTime.endOf('day') : dateTime.startOf('day');
         } else if (type === 1) {
-          dateTime.add('days',num);
+          dateTime.add(num, 'days');
         } else if (type === 2) {
-          dateTime.subtract('days',num);
+          dateTime.subtract(num, 'days');
         }
         break;
       case 'h':
@@ -268,27 +268,27 @@ function($, _, moment) {
         if (type === 0) {
           roundUp ? dateTime.endOf('hour') : dateTime.startOf('hour');
         } else if (type === 1) {
-          dateTime.add('hours',num);
+          dateTime.add(num, 'hours');
         } else if (type === 2) {
-          dateTime.subtract('hours',num);
+          dateTime.subtract(num,'hours');
         }
         break;
       case 'm':
         if (type === 0) {
           roundUp ? dateTime.endOf('minute') : dateTime.startOf('minute');
         } else if (type === 1) {
-          dateTime.add('minutes',num);
+          dateTime.add(num, 'minutes');
         } else if (type === 2) {
-          dateTime.subtract('minutes',num);
+          dateTime.subtract(num, 'minutes');
         }
         break;
       case 's':
         if (type === 0) {
           roundUp ? dateTime.endOf('second') : dateTime.startOf('second');
         } else if (type === 1) {
-          dateTime.add('seconds',num);
+          dateTime.add(num, 'seconds');
         } else if (type === 2) {
-          dateTime.subtract('seconds',num);
+          dateTime.subtract(num, 'seconds');
         }
         break;
       default:

+ 1 - 1
src/app/components/settings.js

@@ -19,7 +19,7 @@ function (_, crypto) {
       default_route                 : '/dashboard/file/default.json',
       playlist_timespan             : "1m",
       unsaved_changes_warning       : true,
-      search                        : { max_results: 20 },
+      search                        : { max_results: 16 },
       admin                         : {}
     };
 

+ 49 - 6
src/app/panels/graph/timeSeries.js → src/app/components/timeSeries.js

@@ -5,15 +5,57 @@ define([
 function (_, kbn) {
   'use strict';
 
-  var ts = {};
-
-  ts.ZeroFilled = function (opts) {
+  function TimeSeries(opts) {
     this.datapoints = opts.datapoints;
     this.info = opts.info;
     this.label = opts.info.alias;
+  }
+
+  function matchSeriesOverride(aliasOrRegex, seriesAlias) {
+    if (!aliasOrRegex) { return false; }
+
+    if (aliasOrRegex[0] === '/') {
+      var match = aliasOrRegex.match(new RegExp('^/(.*?)/(g?i?m?y?)$'));
+      var regex = new RegExp(match[1], match[2]);
+      return seriesAlias.match(regex) != null;
+    }
+
+    return aliasOrRegex === seriesAlias;
+  }
+
+  function translateFillOption(fill) {
+    return fill === 0 ? 0.001 : fill/10;
+  }
+
+  TimeSeries.prototype.applySeriesOverrides = function(overrides) {
+    this.lines = {};
+    this.points = {};
+    this.bars = {};
+    this.info.yaxis = 1;
+    this.zindex = 0;
+    delete this.stack;
+
+    for (var i = 0; i < overrides.length; i++) {
+      var override = overrides[i];
+      if (!matchSeriesOverride(override.alias, this.info.alias)) {
+        continue;
+      }
+      if (override.lines !== void 0) { this.lines.show = override.lines; }
+      if (override.points !== void 0) { this.points.show = override.points; }
+      if (override.bars !== void 0) { this.bars.show = override.bars; }
+      if (override.fill !== void 0) { this.lines.fill = translateFillOption(override.fill); }
+      if (override.stack !== void 0) { this.stack = override.stack; }
+      if (override.linewidth !== void 0) { this.lines.lineWidth = override.linewidth; }
+      if (override.pointradius !== void 0) { this.points.radius = override.pointradius; }
+      if (override.steppedLine !== void 0) { this.lines.steps = override.steppedLine; }
+      if (override.zindex !== void 0) { this.zindex = override.zindex; }
+      if (override.yaxis !== void 0) {
+        this.info.yaxis = override.yaxis;
+      }
+    }
   };
 
-  ts.ZeroFilled.prototype.getFlotPairs = function (fillStyle, yFormats) {
+  TimeSeries.prototype.getFlotPairs = function (fillStyle, yFormats) {
     var result = [];
 
     this.color = this.info.color;
@@ -74,5 +116,6 @@ function (_, kbn) {
     return result;
   };
 
-  return ts;
-});
+  return TimeSeries;
+
+});

+ 8 - 1
src/app/controllers/dashboardCtrl.js

@@ -13,7 +13,7 @@ function (angular, $, config, _) {
   module.controller('DashboardCtrl', function(
       $scope, $rootScope, dashboardKeybindings,
       filterSrv, dashboardSrv, dashboardViewStateSrv,
-      panelMoveSrv, timer) {
+      panelMoveSrv, timer, $timeout) {
 
     $scope.editor = { index: 0 };
     $scope.panelNames = config.panels;
@@ -21,6 +21,13 @@ function (angular, $, config, _) {
     $scope.init = function() {
       $scope.availablePanels = config.panels;
       $scope.onAppEvent('setup-dashboard', $scope.setupDashboard);
+
+      angular.element(window).bind('resize', function() {
+        $timeout(function() {
+          $scope.$broadcast('render');
+        });
+      });
+
     };
 
     $scope.setupDashboard = function(event, dashboardData) {

+ 1 - 1
src/app/directives/addGraphiteFunc.js

@@ -97,4 +97,4 @@ function (angular, app, _, $, gfunc) {
       };
     });
   }
-});
+});

+ 13 - 11
src/app/directives/grafanaGraph.js

@@ -46,11 +46,6 @@ function (angular, $, kbn, moment, _) {
           render_panel();
         });
 
-        // Re-render if the window is resized
-        angular.element(window).bind('resize', function() {
-          render_panel();
-        });
-
         function setElementHeight() {
           try {
             var height = scope.height || scope.panel.height || scope.row.height;
@@ -118,7 +113,7 @@ function (angular, $, kbn, moment, _) {
               lines:  {
                 show: panel.lines,
                 zero: false,
-                fill: panel.fill === 0 ? 0.001 : panel.fill/10,
+                fill: translateFillOption(panel.fill),
                 lineWidth: panel.linewidth,
                 steps: panel.steppedLine
               },
@@ -154,11 +149,12 @@ function (angular, $, kbn, moment, _) {
           };
 
           for (var i = 0; i < data.length; i++) {
-            var _d = data[i].getFlotPairs(panel.nullPointMode, panel.y_formats);
-            data[i].data = _d;
+            var series = data[i];
+            series.applySeriesOverrides(panel.seriesOverrides);
+            series.data = series.getFlotPairs(panel.nullPointMode, panel.y_formats);
           }
 
-          if (panel.bars && data.length && data[0].info.timeStep) {
+          if (data.length && data[0].info.timeStep) {
             options.series.bars.barWidth = data[0].info.timeStep / 1.5;
           }
 
@@ -167,21 +163,27 @@ function (angular, $, kbn, moment, _) {
           addAnnotations(options);
           configureAxisOptions(data, options);
 
+          var sortedSeries = _.sortBy(data, function(series) { return series.zindex; });
+
           // if legend is to the right delay plot draw a few milliseconds
           // so the legend width calculation can be done
           if (shouldDelayDraw(panel)) {
             legendSideLastValue = panel.legend.rightSide;
             setTimeout(function() {
-              plot = $.plot(elem, data, options);
+              plot = $.plot(elem, sortedSeries, options);
               addAxisLabels();
             }, 50);
           }
           else {
-            plot = $.plot(elem, data, options);
+            plot = $.plot(elem, sortedSeries, options);
             addAxisLabels();
           }
         }
 
+        function translateFillOption(fill) {
+          return fill === 0 ? 0.001 : fill/10;
+        }
+
         function shouldDelayDraw(panel) {
           if (panel.legend.rightSide) {
             return true;

+ 2 - 2
src/app/panels/graph/legend.html

@@ -34,12 +34,12 @@
 
     <div class="editor-row small" style="padding-bottom: 0;">
       <label>Axis:</label>
-      <button ng-click="toggleYAxis(series)"
+      <button ng-click="toggleYAxis(series);dismiss();"
               class="btn btn-mini"
               ng-class="{'btn-success': series.yaxis === 1 }">
         Left
       </button>
-      <button ng-click="toggleYAxis(series)"
+      <button ng-click="toggleYAxis(series);dismiss();"
               class="btn btn-mini"
               ng-class="{'btn-success': series.yaxis === 2 }">
         Right

+ 1 - 1
src/app/panels/graph/module.html

@@ -27,7 +27,7 @@
       </div>
     </div>
 
-    <div class="tab-content" ng-repeat="tab in panelMeta.fullEditorTabs" ng-show="editorTabs[editor.index] == tab.title">
+    <div class="tab-content" ng-repeat="tab in panelMeta.fullEditorTabs" ng-if="editorTabs[editor.index] == tab.title">
       <div ng-include src="tab.src"></div>
     </div>
   </div>

+ 22 - 24
src/app/panels/graph/module.js

@@ -1,16 +1,3 @@
-/** @scratch /panels/5
- * include::panels/histogram.asciidoc[]
- */
-
-/** @scratch /panels/histogram/0
- * == Histogram
- * Status: *Stable*
- *
- * The histogram panel allow for the display of time charts. It includes several modes and tranformations
- * to display event counts, mean, min, max and total of numeric fields, and derivatives of counter
- * fields.
- *
- */
 define([
   'angular',
   'app',
@@ -18,7 +5,8 @@ define([
   'lodash',
   'kbn',
   'moment',
-  './timeSeries',
+  'components/timeSeries',
+  './seriesOverridesCtrl',
   'services/panelSrv',
   'services/annotationsSrv',
   'services/datasourceSrv',
@@ -29,11 +17,10 @@ define([
   'jquery.flot.stack',
   'jquery.flot.stackpercent'
 ],
-function (angular, app, $, _, kbn, moment, timeSeries) {
-
+function (angular, app, $, _, kbn, moment, TimeSeries) {
   'use strict';
 
-  var module = angular.module('grafana.panels.graph', []);
+  var module = angular.module('grafana.panels.graph');
   app.useModule(module);
 
   module.controller('GraphCtrl', function($scope, $rootScope, $timeout, panelSrv, annotationsSrv) {
@@ -179,7 +166,8 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
       targets: [{}],
 
       aliasColors: {},
-      aliasYAxis: {},
+
+      seriesOverrides: [],
     };
 
     _.defaults($scope.panel,_d);
@@ -258,18 +246,15 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
       var datapoints = seriesData.datapoints;
       var alias = seriesData.target;
       var color = $scope.panel.aliasColors[alias] || $rootScope.colors[index];
-      var yaxis = $scope.panel.aliasYAxis[alias] || 1;
 
       var seriesInfo = {
         alias: alias,
         color:  color,
-        enable: true,
-        yaxis: yaxis
       };
 
       $scope.legend.push(seriesInfo);
 
-      var series = new timeSeries.ZeroFilled({
+      var series = new TimeSeries({
         datapoints: datapoints,
         info: seriesInfo,
       });
@@ -347,8 +332,12 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
     };
 
     $scope.toggleYAxis = function(info) {
-      info.yaxis = info.yaxis === 2 ? 1 : 2;
-      $scope.panel.aliasYAxis[info.alias] = info.yaxis;
+      var override = _.findWhere($scope.panel.seriesOverrides, { alias: info.alias });
+      if (!override) {
+        override = { alias: info.alias };
+        $scope.panel.seriesOverrides.push(override);
+      }
+      override.yaxis = info.yaxis === 2 ? 1 : 2;
       $scope.render();
     };
 
@@ -357,6 +346,15 @@ function (angular, app, $, _, kbn, moment, timeSeries) {
       $scope.render();
     };
 
+    $scope.addSeriesOverride = function() {
+      $scope.panel.seriesOverrides.push({});
+    };
+
+    $scope.removeSeriesOverride = function(override) {
+      $scope.panel.seriesOverrides = _.without($scope.panel.seriesOverrides, override);
+      $scope.render();
+    };
+
     panelSrv.init($scope);
   });
 

+ 80 - 0
src/app/panels/graph/seriesOverridesCtrl.js

@@ -0,0 +1,80 @@
+define([
+  'angular',
+  'app',
+  'lodash',
+], function(angular, app, _) {
+  'use strict';
+
+  var module = angular.module('grafana.panels.graph', []);
+  app.useModule(module);
+
+  module.controller('SeriesOverridesCtrl', function($scope) {
+    $scope.overrideMenu = [];
+    $scope.currentOverrides = [];
+    $scope.override = $scope.override || {};
+
+    $scope.addOverrideOption = function(name, propertyName, values) {
+      var option = {};
+      option.text = name;
+      option.propertyName = propertyName;
+      option.index = $scope.overrideMenu.length;
+      option.values = values;
+
+      option.submenu = _.map(values, function(value, index) {
+        return {
+          text: String(value),
+          click: 'setOverride(' + option.index + ',' + index + ')'
+        };
+      });
+
+      $scope.overrideMenu.push(option);
+    };
+
+    $scope.setOverride = function(optionIndex, valueIndex) {
+      var option = $scope.overrideMenu[optionIndex];
+      var value = option.values[valueIndex];
+      $scope.override[option.propertyName] = value;
+      $scope.updateCurrentOverrides();
+      $scope.render();
+    };
+
+    $scope.removeOverride = function(option) {
+      delete $scope.override[option.propertyName];
+      $scope.updateCurrentOverrides();
+      $scope.render();
+    };
+
+    $scope.getSeriesNames = function() {
+      return _.map($scope.legend, function(info) {
+        return info.alias;
+      });
+    };
+
+    $scope.updateCurrentOverrides = function() {
+      $scope.currentOverrides = [];
+      _.each($scope.overrideMenu, function(option) {
+        var value = $scope.override[option.propertyName];
+        if (_.isUndefined(value)) { return; }
+        $scope.currentOverrides.push({
+          name: option.text,
+          propertyName: option.propertyName,
+          value: String(value)
+        });
+      });
+    };
+
+    $scope.addOverrideOption('Bars', 'bars', [true, false]);
+    $scope.addOverrideOption('Lines', 'lines', [true, false]);
+    $scope.addOverrideOption('Line fill', 'fill', [0,1,2,3,4,5,6,7,8,9,10]);
+    $scope.addOverrideOption('Line width', 'linewidth', [0,1,2,3,4,5,6,7,8,9,10]);
+    $scope.addOverrideOption('Staircase line', 'steppedLine', [true, false]);
+    $scope.addOverrideOption('Points', 'points', [true, false]);
+    $scope.addOverrideOption('Points Radius', 'pointradius', [1,2,3,4,5]);
+    $scope.addOverrideOption('Stack', 'stack', [true, false]);
+    $scope.addOverrideOption('Y-axis', 'yaxis', [1, 2]);
+    $scope.addOverrideOption('Z-index', 'zindex', [-1,-2,-3,0,1,2,3]);
+    $scope.updateCurrentOverrides();
+
+  });
+
+});

+ 46 - 2
src/app/panels/graph/styleEditor.html

@@ -1,5 +1,3 @@
-
-
 <div class="editor-row">
   <div class="section">
     <h5>Chart Options</h5>
@@ -64,3 +62,49 @@
     </div>
   </div>
 </div>
+
+<div class="editor-row">
+  <div class="section">
+		<h5>Series specific overrides <tip>Regex match example: /server[0-3]/i </tip></h5>
+		<div>
+		<div class="grafana-target" ng-repeat="override in panel.seriesOverrides" ng-controller="SeriesOverridesCtrl">
+			<div class="grafana-target-inner-wrapper">
+				<div class="grafana-target-inner">
+
+					<ul class="grafana-target-controls-left">
+						<li class="grafana-target-segment">
+							<i class="icon-remove pointer" ng-click="removeSeriesOverride(override)"></i>
+						</li>
+					</ul>
+
+					<ul class="grafana-segment-list">
+						<li class="grafana-target-segment">
+							alias or regex
+						</li>
+						<li>
+							<input type="text"
+										ng-model="override.alias"
+                    bs-typeahead="getSeriesNames"
+										ng-blur="render()"
+										data-min-length=0 data-items=100
+										class="input-medium grafana-target-segment-input" >
+						</li>
+						<li class="grafana-target-segment" ng-repeat="option in currentOverrides">
+							<i class="pointer icon-remove" ng-click="removeOverride(option)"></i>
+							{{option.name}}: {{option.value}}
+						</li>
+						<li class="dropdown">
+							<a class="dropdown-toggle grafana-target-segment" data-toggle="dropdown" gf-dropdown="overrideMenu" bs-tooltip="'set option to override'">
+								<i class="icon-plus"></i>
+							</a>
+						</li>
+					</ul>
+					<div class="clearfix"></div>
+				</div>
+			</div>
+		</div>
+		</div>
+
+		<button class="btn btn-success" style="margin-top: 20px" ng-click="addSeriesOverride()">Add series override rule</button>
+	</div>
+</div>

+ 2 - 2
src/app/panels/timepicker/module.js

@@ -193,8 +193,8 @@ function (angular, app, _, moment, kbn) {
             moment(model.to.date).fromNow();
         }
         else {
-          model.rangeString = $scope.dashboard.formatDate(model.from.date, 'MMM D, YYYY hh:mm:ss') + ' to ' +
-            $scope.dashboard.formatDate(model.to.date, 'MMM D, YYYY hh:mm:ss');
+          model.rangeString = $scope.dashboard.formatDate(model.from.date, 'MMM D, YYYY HH:mm:ss') + ' to ' +
+            $scope.dashboard.formatDate(model.to.date, 'MMM D, YYYY HH:mm:ss');
         }
       }
 

+ 1 - 1
src/app/partials/dashboard_topnav.html

@@ -22,7 +22,7 @@
 				</li>
 
 				<li class="dropdown grafana-menu-save">
-					<a href="#"  bs-tooltip="'Save'" data-placement="bottom" class="dropdown-toggle" data-toggle="dropdown" ng-click="openSaveDropdown()">
+					<a bs-tooltip="'Save'" data-placement="bottom" class="dropdown-toggle" data-toggle="dropdown" ng-click="openSaveDropdown()">
 						<i class='icon-save'></i>
 					</a>
 

+ 1 - 1
src/app/partials/dasheditor.html

@@ -68,7 +68,7 @@
     </div>
   </div>
 
-  <div ng-if="editor.index == 2" ng-controller="dashLoader">
+  <div ng-if="editor.index == 2">
     <div class="editor-row">
 			<div class="section">
 				<h5>Feature toggles</h5>

+ 6 - 6
src/app/partials/search.html

@@ -45,7 +45,7 @@
           </span>
         </div>
 
-        <h6 ng-hide="results.dashboards.length || results.metrics.length">No dashboards or metrics matching your query found</h6>
+        <h6 ng-hide="results.dashboards.length">No dashboards matching your query were found.</h6>
 
         <div class="search-results-container" ng-if="tagsOnly">
 					<div ng-repeat="tag in results.tags"
@@ -64,11 +64,6 @@
 							bindonce ng-repeat="row in results.dashboards"
 							ng-class="{'selected': $index === selectedIndex }" ng-click="goToDashboard(row.id)">
 
-							<a class="search-result-link" href="#/dashboard/db/{{row.id}}">
-								<i class="icon icon-th-large"></i>
-								<span bo-text="row.id"></span>
-							</a>
-
 							<div class="search-result-actions">
 								<a ng-click="shareDashboard(row.id, row.id, $event)" config-modal="app/partials/dashLoaderShare.html">
 									<i class="icon-share"></i> share &nbsp;&nbsp;&nbsp;
@@ -84,6 +79,11 @@
 								</a>
 							</div>
 
+							<a class="search-result-link">
+								<i class="icon icon-th-large"></i>
+								<span bo-text="row.title"></span>
+							</a>
+
 					</div>
 				</div>
 

+ 2 - 0
src/app/routes/dashboard-from-db.js

@@ -16,10 +16,12 @@ function (angular) {
       .when('/dashboard/elasticsearch/:id', {
         templateUrl: 'app/partials/dashboard.html',
         controller : 'DashFromDBProvider',
+        reloadOnSearch: false,
       })
       .when('/dashboard/temp/:id', {
         templateUrl: 'app/partials/dashboard.html',
         controller : 'DashFromDBProvider',
+        reloadOnSearch: false,
       });
   });
 

+ 1 - 0
src/app/routes/dashboard-from-file.js

@@ -14,6 +14,7 @@ function (angular, $, config, _) {
       .when('/dashboard/file/:jsonFile', {
         templateUrl: 'app/partials/dashboard.html',
         controller : 'DashFromFileProvider',
+        reloadOnSearch: false,
       });
   });
 

+ 72 - 54
src/app/services/dashboard/dashboardSrv.js

@@ -19,7 +19,9 @@ function (angular, $, kbn, _, moment) {
         data = {};
       }
 
+      this.id = data.id || null;
       this.title = data.title || 'No Title';
+      this.originalTitle = this.title;
       this.tags = data.tags || [];
       this.style = data.style || "dark";
       this.timezone = data.timezone || 'browser';
@@ -31,7 +33,6 @@ function (angular, $, kbn, _, moment) {
       this.templating = data.templating || { list: [] };
       this.refresh = data.refresh;
       this.version = data.version || 0;
-      this.$state = data.$state;
 
       if (this.nav.length === 0) {
         this.nav.push({ type: 'timepicker' });
@@ -144,80 +145,97 @@ function (angular, $, kbn, _, moment) {
     };
 
     p.updateSchema = function(old) {
-      var i, j, row, panel;
       var oldVersion = this.version;
-      this.version = 3;
+      var panelUpgrades = [];
+      this.version = 4;
 
-      if (oldVersion === 3) {
+      if (oldVersion === 4) {
         return;
       }
 
-      // Version 3 schema changes
-      // ensure panel ids
-      var maxId = this.getNextPanelId();
-      for (i = 0; i < this.rows.length; i++) {
-        row = this.rows[i];
-        for (j = 0; j < row.panels.length; j++) {
-          panel = row.panels[j];
-          if (!panel.id) {
-            panel.id = maxId;
-            maxId += 1;
-          }
-        }
-      }
+      // version 2 schema changes
+      if (oldVersion < 2) {
 
-      if (oldVersion === 2) {
-        return;
-      }
-
-      // Version 2 schema changes
-      if (old.services) {
-        if (old.services.filter) {
-          this.time = old.services.filter.time;
-          this.templating.list = old.services.filter.list;
+        if (old.services) {
+          if (old.services.filter) {
+            this.time = old.services.filter.time;
+            this.templating.list = old.services.filter.list;
+          }
+          delete this.services;
         }
-        delete this.services;
-      }
 
-      for (i = 0; i < this.rows.length; i++) {
-        row = this.rows[i];
-        for (j = 0; j < row.panels.length; j++) {
-          panel = row.panels[j];
+        panelUpgrades.push(function(panel) {
+          // rename panel type
           if (panel.type === 'graphite') {
             panel.type = 'graph';
           }
 
-          if (panel.type === 'graph') {
-            if (_.isBoolean(panel.legend)) {
-              panel.legend = { show: panel.legend };
-            }
+          if (panel.type !== 'graph') {
+            return;
+          }
 
-            if (panel.grid) {
-              if (panel.grid.min) {
-                panel.grid.leftMin = panel.grid.min;
-                delete panel.grid.min;
-              }
+          if (_.isBoolean(panel.legend)) { panel.legend = { show: panel.legend }; }
 
-              if (panel.grid.max) {
-                panel.grid.leftMax = panel.grid.max;
-                delete panel.grid.max;
-              }
+          if (panel.grid) {
+            if (panel.grid.min) {
+              panel.grid.leftMin = panel.grid.min;
+              delete panel.grid.min;
             }
 
-            if (panel.y_format) {
-              panel.y_formats[0] = panel.y_format;
-              delete panel.y_format;
+            if (panel.grid.max) {
+              panel.grid.leftMax = panel.grid.max;
+              delete panel.grid.max;
             }
+          }
 
-            if (panel.y2_format) {
-              panel.y_formats[1] = panel.y2_format;
-              delete panel.y2_format;
-            }
+          if (panel.y_format) {
+            panel.y_formats[0] = panel.y_format;
+            delete panel.y_format;
           }
-        }
+
+          if (panel.y2_format) {
+            panel.y_formats[1] = panel.y2_format;
+            delete panel.y2_format;
+          }
+        });
+      }
+
+      // schema version 3 changes
+      if (oldVersion < 3) {
+        // ensure panel ids
+        var maxId = this.getNextPanelId();
+        panelUpgrades.push(function(panel) {
+          if (!panel.id) {
+            panel.id = maxId;
+            maxId += 1;
+          }
+        });
+      }
+
+      // schema version 4 changes
+      if (oldVersion < 4) {
+        // move aliasYAxis changes
+        panelUpgrades.push(function(panel) {
+          if (panel.type !== 'graph') { return; }
+          _.each(panel.aliasYAxis, function(value, key) {
+            panel.seriesOverrides = [{ alias: key, yaxis: value }];
+          });
+          delete panel.aliasYAxis;
+        });
+      }
+
+      if (panelUpgrades.length === 0) {
+        return;
       }
 
-      this.version = 3;
+      for (var i = 0; i < this.rows.length; i++) {
+        var row = this.rows[i];
+        for (var j = 0; j < row.panels.length; j++) {
+          for (var k = 0; k < panelUpgrades.length; k++) {
+            panelUpgrades[k](row.panels[j]);
+          }
+        }
+      }
     };
 
     return {

+ 1 - 0
src/app/services/elasticsearch/es-datasource.js

@@ -216,6 +216,7 @@ function (angular, _, $, config, kbn, moment) {
           for (var i = 0; i < results.hits.hits.length; i++) {
             hits.dashboards.push({
               id: results.hits.hits[i]._id,
+              title: results.hits.hits[i]._id,
               tags: results.hits.hits[i]._source.tags
             });
           }

+ 2 - 2
src/app/services/graphite/graphiteDatasource.js

@@ -150,7 +150,7 @@ function (angular, _, $, config, kbn, moment) {
 
       if (rounding === 'round-up') {
         if (date.get('s')) {
-          date.add('m', 1);
+          date.add(1, 'm');
         }
       }
       else if (rounding === 'round-down') {
@@ -159,7 +159,7 @@ function (angular, _, $, config, kbn, moment) {
         // to guarantee that we get all the data that
         // exists for the specified range
         if (date.get('s')) {
-          date.subtract('m', 1);
+          date.subtract(1, 'm');
         }
       }
 

+ 1 - 0
src/app/services/influxdb/influxdbDatasource.js

@@ -358,6 +358,7 @@ function (angular, _, kbn, InfluxSeries) {
         for (var i = 0; i < results.length; i++) {
           var hit =  {
             id: results[i].points[0][dashCol],
+            title: results[i].points[0][dashCol],
             tags: results[i].points[0][tagsCol].split(",")
           };
           hit.tags = hit.tags[0] ? hit.tags : [];

+ 3 - 0
src/css/less/grafana.less

@@ -52,6 +52,9 @@
 }
 
 .search-results-container {
+  max-height: 600px;
+  overflow: auto;
+  display: block;
   .search-result-item a {
   }
 

+ 14 - 0
src/css/less/graph.less

@@ -154,3 +154,17 @@
 .annotation-tags {
   color: @purple;
 }
+
+.graph-series-override {
+  input {
+    float: left;
+    margin-right: 10px;
+  }
+  .graph-series-override-option {
+    float: left;
+    padding: 2px 6px;
+  }
+  .graph-series-override-selector {
+    float: left;
+  }
+}

+ 7 - 1
src/test/specs/dashboardSrv-specs.js

@@ -97,6 +97,7 @@ define([
               {
                 type: 'graphite',
                 legend: true,
+                aliasYAxis: { test: 2 },
                 grid: { min: 1, max: 10 }
               }
             ]
@@ -134,8 +135,13 @@ define([
       expect(graph.grid.leftMax).to.be(10);
     });
 
+    it('move aliasYAxis to series override', function() {
+      expect(graph.seriesOverrides[0].alias).to.be("test");
+      expect(graph.seriesOverrides[0].yaxis).to.be(2);
+    });
+
     it('dashboard schema version should be set to latest', function() {
-      expect(model.version).to.be(3);
+      expect(model.version).to.be(4);
     });
 
   });

+ 147 - 0
src/test/specs/grafanaGraph-specs.js

@@ -0,0 +1,147 @@
+define([
+  './helpers',
+  'angular',
+  'jquery',
+  'components/timeSeries',
+  'directives/grafanaGraph'
+], function(helpers, angular, $, TimeSeries) {
+  'use strict';
+
+  describe('grafanaGraph', function() {
+
+    beforeEach(module('grafana.directives'));
+
+    function graphScenario(desc, func)  {
+      describe(desc, function() {
+        var ctx = {};
+        ctx.setup = function (setupFunc) {
+          beforeEach(inject(function($rootScope, $compile) {
+            var scope = $rootScope.$new();
+            var element = angular.element("<div style='width:500px' grafana-graph><div>");
+
+            scope.height = '200px';
+            scope.panel = {
+              legend: {},
+              grid: {},
+              y_formats: [],
+              seriesOverrides: []
+            };
+            scope.dashboard = { timezone: 'browser' };
+            scope.range = {
+              from: new Date('2014-08-09 10:00:00'),
+              to: new Date('2014-09-09 13:00:00')
+            };
+            ctx.data = [];
+            ctx.data.push(new TimeSeries({
+              datapoints: [[1,1],[2,2]],
+              info: { alias: 'series1', enable: true }
+            }));
+            ctx.data.push(new TimeSeries({
+              datapoints: [[1,1],[2,2]],
+              info: { alias: 'series2', enable: true }
+            }));
+
+            setupFunc(scope, ctx.data);
+
+            $compile(element)(scope);
+            scope.$digest();
+            $.plot = ctx.plotSpy = sinon.spy();
+
+            scope.$emit('render', ctx.data);
+            ctx.plotData = ctx.plotSpy.getCall(0).args[1];
+            ctx.plotOptions = ctx.plotSpy.getCall(0).args[2];
+          }));
+        };
+
+        func(ctx);
+      });
+    }
+
+    graphScenario('simple lines options', function(ctx) {
+      ctx.setup(function(scope) {
+        scope.panel.lines = true;
+        scope.panel.fill = 5;
+        scope.panel.linewidth = 3;
+        scope.panel.steppedLine = true;
+      });
+
+      it('should configure plot with correct options', function() {
+        expect(ctx.plotOptions.series.lines.show).to.be(true);
+        expect(ctx.plotOptions.series.lines.fill).to.be(0.5);
+        expect(ctx.plotOptions.series.lines.lineWidth).to.be(3);
+        expect(ctx.plotOptions.series.lines.steps).to.be(true);
+      });
+    });
+
+    graphScenario('grid thresholds 100, 200', function(ctx) {
+      ctx.setup(function(scope) {
+        scope.panel.grid = {
+          threshold1: 100,
+          threshold1Color: "#111",
+          threshold2: 200,
+          threshold2Color: "#222",
+        };
+      });
+
+      it('should add grid markings', function() {
+        var markings = ctx.plotOptions.grid.markings;
+        expect(markings[0].yaxis.from).to.be(100);
+        expect(markings[0].yaxis.to).to.be(200);
+        expect(markings[0].color).to.be('#111');
+        expect(markings[1].yaxis.from).to.be(200);
+        expect(markings[1].yaxis.to).to.be(Infinity);
+      });
+    });
+
+    graphScenario('inverted grid thresholds 200, 100', function(ctx) {
+      ctx.setup(function(scope) {
+        scope.panel.grid = {
+          threshold1: 200,
+          threshold1Color: "#111",
+          threshold2: 100,
+          threshold2Color: "#222",
+        };
+      });
+
+      it('should add grid markings', function() {
+        var markings = ctx.plotOptions.grid.markings;
+        expect(markings[0].yaxis.from).to.be(200);
+        expect(markings[0].yaxis.to).to.be(100);
+        expect(markings[0].color).to.be('#111');
+        expect(markings[1].yaxis.from).to.be(100);
+        expect(markings[1].yaxis.to).to.be(-Infinity);
+      });
+    });
+
+    graphScenario('series option overrides, fill & points', function(ctx) {
+      ctx.setup(function(scope, data) {
+        scope.panel.lines = true;
+        scope.panel.fill = 5;
+        scope.panel.seriesOverrides = [
+          { alias: 'test', fill: 0, points: true }
+        ];
+
+        data[1].info.alias = 'test';
+      });
+
+      it('should match second series and fill zero, and enable points', function() {
+        expect(ctx.plotOptions.series.lines.fill).to.be(0.5);
+        expect(ctx.plotData[1].lines.fill).to.be(0.001);
+        expect(ctx.plotData[1].points.show).to.be(true);
+      });
+    });
+
+    graphScenario('should order series order according to zindex', function(ctx) {
+      ctx.setup(function(scope) {
+        scope.panel.seriesOverrides = [{ alias: 'series1', zindex: 2 }];
+      });
+
+      it('should move zindex 2 last', function() {
+        expect(ctx.plotData[0].info.alias).to.be('series2');
+        expect(ctx.plotData[1].info.alias).to.be('series1');
+      });
+    });
+
+  });
+});
+

+ 2 - 2
src/test/specs/helpers.js

@@ -64,12 +64,12 @@ define([
   function FilterSrvStub() {
     this.time = { from:'now-1h', to: 'now'};
     this.timeRange = function(parse) {
-      if (!parse) {
+      if (parse === false) {
         return this.time;
       }
       return {
         from : kbn.parseDate(this.time.from),
-             to : kbn.parseDate(this.time.to)
+        to : kbn.parseDate(this.time.to)
       };
     };
 

+ 51 - 0
src/test/specs/seriesOverridesCtrl-specs.js

@@ -0,0 +1,51 @@
+define([
+  './helpers',
+  'panels/graph/seriesOverridesCtrl'
+], function(helpers) {
+  'use strict';
+
+  describe('SeriesOverridesCtrl', function() {
+    var ctx = new helpers.ControllerTestContext();
+
+    beforeEach(module('grafana.services'));
+    beforeEach(module('grafana.panels.graph'));
+
+    beforeEach(ctx.providePhase());
+    beforeEach(ctx.createControllerPhase('SeriesOverridesCtrl'));
+    beforeEach(function() {
+      ctx.scope.render = function() {};
+    });
+
+    describe('Controller should init overrideMenu', function() {
+      it('click should include option and value index', function() {
+        expect(ctx.scope.overrideMenu[1].submenu[1].click).to.be('setOverride(1,1)');
+      });
+    });
+
+    describe('When setting an override', function() {
+      beforeEach(function() {
+        ctx.scope.setOverride(1, 0);
+      });
+
+      it('should set override property', function() {
+        expect(ctx.scope.override.lines).to.be(true);
+      });
+
+      it('should update view model', function() {
+        expect(ctx.scope.currentOverrides[0].name).to.be('Lines');
+        expect(ctx.scope.currentOverrides[0].value).to.be('true');
+      });
+    });
+
+    describe('When removing overide', function() {
+      it('click should include option and value index', function() {
+        ctx.scope.setOverride(1,0);
+        ctx.scope.removeOverride({ propertyName: 'lines' });
+        expect(ctx.scope.currentOverrides.length).to.be(0);
+      });
+    });
+
+  });
+
+});
+

+ 115 - 0
src/test/specs/timeSeries-specs.js

@@ -0,0 +1,115 @@
+define([
+  'components/timeSeries'
+], function(TimeSeries) {
+  'use strict';
+
+  describe("TimeSeries", function() {
+    var points, series;
+    var yAxisFormats = ['short', 'ms'];
+    var testData = {
+      info: { alias: 'test' },
+      datapoints: [
+        [1,2],[null,3],[10,4],[8,5]
+      ]
+    };
+
+    describe('when getting flot pairs', function() {
+      it('with connected style, should ignore nulls', function() {
+        series = new TimeSeries(testData);
+        points = series.getFlotPairs('connected', yAxisFormats);
+        expect(points.length).to.be(3);
+      });
+
+      it('with null as zero style, should replace nulls with zero', function() {
+        series = new TimeSeries(testData);
+        points = series.getFlotPairs('null as zero', yAxisFormats);
+        expect(points.length).to.be(4);
+        expect(points[1][1]).to.be(0);
+      });
+    });
+
+    describe('series overrides', function() {
+      var series;
+      beforeEach(function() {
+        series = new TimeSeries(testData);
+      });
+
+      describe('fill & points', function() {
+        beforeEach(function() {
+          series.info.alias = 'test';
+          series.applySeriesOverrides([{ alias: 'test', fill: 0, points: true }]);
+        });
+
+        it('should set fill zero, and enable points', function() {
+          expect(series.lines.fill).to.be(0.001);
+          expect(series.points.show).to.be(true);
+        });
+      });
+
+      describe('series option overrides, bars, true & lines false', function() {
+        beforeEach(function() {
+          series.info.alias = 'test';
+          series.applySeriesOverrides([{ alias: 'test', bars: true, lines: false }]);
+        });
+
+        it('should disable lines, and enable bars', function() {
+          expect(series.lines.show).to.be(false);
+          expect(series.bars.show).to.be(true);
+        });
+      });
+
+      describe('series option overrides, linewidth, stack', function() {
+        beforeEach(function() {
+          series.info.alias = 'test';
+          series.applySeriesOverrides([{ alias: 'test', linewidth: 5, stack: false }]);
+        });
+
+        it('should disable stack, and set lineWidth', function() {
+          expect(series.stack).to.be(false);
+          expect(series.lines.lineWidth).to.be(5);
+        });
+      });
+
+      describe('series option overrides, pointradius, steppedLine', function() {
+        beforeEach(function() {
+          series.info.alias = 'test';
+          series.applySeriesOverrides([{ alias: 'test', pointradius: 5, steppedLine: true }]);
+        });
+
+        it('should set pointradius, and set steppedLine', function() {
+          expect(series.points.radius).to.be(5);
+          expect(series.lines.steps).to.be(true);
+        });
+      });
+
+      describe('override match on regex', function() {
+        beforeEach(function() {
+          series.info.alias = 'test_01';
+          series.applySeriesOverrides([{ alias: '/.*01/', lines: false }]);
+        });
+
+        it('should match second series', function() {
+          expect(series.lines.show).to.be(false);
+        });
+      });
+
+      describe('override series y-axis, and z-index', function() {
+        beforeEach(function() {
+          series.info.alias = 'test';
+          series.applySeriesOverrides([{ alias: 'test', yaxis: 2, zindex: 2 }]);
+        });
+
+        it('should set yaxis', function() {
+          expect(series.info.yaxis).to.be(2);
+        });
+
+        it('should set zindex', function() {
+          expect(series.zindex).to.be(2);
+        });
+      });
+
+    });
+
+  });
+
+});

+ 3 - 0
src/test/test-main.js

@@ -118,10 +118,13 @@ require([
     'specs/lexer-specs',
     'specs/parser-specs',
     'specs/gfunc-specs',
+    'specs/timeSeries-specs',
     'specs/row-ctrl-specs',
     'specs/graphiteTargetCtrl-specs',
     'specs/influxdb-datasource-specs',
     'specs/graph-ctrl-specs',
+    'specs/grafanaGraph-specs',
+    'specs/seriesOverridesCtrl-specs',
     'specs/filterSrv-specs',
     'specs/kbn-format-specs',
     'specs/dashboardSrv-specs',

+ 2 - 1
src/vendor/bootstrap/bootstrap.js

@@ -753,7 +753,8 @@
 
     if (!selector) {
       selector = $this.attr('href')
-      selector = selector && /#/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+      // grafana backport fix from bootstrap 3
+      selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
     }
 
     $parent = selector && $(selector)

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 359 - 224
src/vendor/moment.js


Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio