diff --git a/CHANGELOG.md b/CHANGELOG.md index 55cde63197f..8215e325a1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,12 +3,23 @@ **New features and improvements** - [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. + +**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) **Tech** - Upgraded from angularjs 1.1.5 to 1.3 beta 17; - Switch from underscore to lodash - helpers to easily unit test angularjs controllers and services - Test coverage through coveralls +- Upgrade from jquery 1.8.0 to 2.1.1 (**Removes support for IE7 & IE8**) + +# 1.7.1 (unreleased) + +**Fixes** +- [Issue #691](https://github.com/grafana/grafana/issues/691). Dashboard: tooltip fixes, sometimes they would not show, and sometimes they would get stuck. +- [Issue #695](https://github.com/grafana/grafana/issues/695). Dashboard: Tooltip on goto home menu icon would get stuck after clicking on it # 1.7.0 (2014-08-11) diff --git a/src/app/app.js b/src/app/app.js index df42f7b14f9..91094ec38db 100644 --- a/src/app/app.js +++ b/src/app/app.js @@ -14,7 +14,7 @@ define([ 'extend-jquery', 'bindonce', ], -function (angular, $, _, appLevelRequire) { +function (angular, $, _, appLevelRequire, config) { "use strict"; @@ -56,6 +56,7 @@ function (angular, $, _, appLevelRequire) { register_fns.factory = $provide.factory; register_fns.service = $provide.service; register_fns.filter = $filterProvider.register; + }); var apps_deps = [ @@ -76,14 +77,23 @@ function (angular, $, _, appLevelRequire) { apps_deps.push(module_name); }); + var preBootRequires = [ + 'controllers/all', + 'directives/all', + 'filters/all', + 'components/partials', + 'routes/all', + ]; + + _.each(config.plugins.dependencies, function(dep) { + preBootRequires.push('../plugins/' + dep); + }); + app.boot = function() { - require([ - 'controllers/all', - 'directives/all', - 'filters/all', - 'components/partials', - 'routes/p_all', - ], function () { + require(preBootRequires, function () { + + // disable tool tip animation + $.fn.tooltip.defaults.animation = false; // bootstrap the app angular diff --git a/src/app/components/kbn.js b/src/app/components/kbn.js index 07d0f21c090..566a2edcde7 100644 --- a/src/app/components/kbn.js +++ b/src/app/components/kbn.js @@ -525,12 +525,35 @@ function($, _, moment) { return kbn.nanosFormat(val, decimals); }; default: - return function(val) { - return val % 1 === 0 ? val : val.toFixed(decimals); + return function(val, axis) { + return kbn.noneFormat(val, axis ? axis.tickDecimals : decimals); }; } }; + kbn.noneFormat = function(value, decimals) { + var factor = decimals ? Math.pow(10, decimals) : 1; + var formatted = String(Math.round(value * factor) / factor); + + // if exponent return directly + if (formatted.indexOf('e') !== -1) { + return formatted; + } + + // If tickDecimals was specified, ensure that we have exactly that + // much precision; otherwise default to the value's own precision. + + if (decimals != null) { + var decimalPos = formatted.indexOf("."); + var precision = decimalPos === -1 ? 0 : formatted.length - decimalPos - 1; + if (precision < decimals) { + return (precision ? formatted : formatted + ".") + (String(factor)).substr(1, decimals - precision); + } + } + + return formatted; + }; + kbn.msFormat = function(size, decimals) { // Less than 1 milli, downscale to micro if (Math.abs(size) < 1) { diff --git a/src/app/components/require.config.js b/src/app/components/require.config.js index 32e8025f201..e90c37958f0 100644 --- a/src/app/components/require.config.js +++ b/src/app/components/require.config.js @@ -5,6 +5,7 @@ require.config({ baseUrl: '/public/app', paths: { + app: 'p_app', config: ['../config', '../config.sample'], settings: 'components/settings', kbn: 'components/kbn', @@ -27,7 +28,7 @@ require.config({ 'lodash-src': '../vendor/lodash', bootstrap: '../vendor/bootstrap/bootstrap', - jquery: '../vendor/jquery/jquery-1.8.0', + jquery: '../vendor/jquery/jquery-2.1.1.min', 'jquery-ui': '../vendor/jquery/jquery-ui-1.10.3', 'extend-jquery': 'components/extend-jquery', diff --git a/src/app/components/settings.js b/src/app/components/settings.js index 165bbc6c476..3edafc5a407 100644 --- a/src/app/components/settings.js +++ b/src/app/components/settings.js @@ -70,7 +70,7 @@ function (_, crypto) { _.each(settings.datasources, function(datasource, key) { datasource.name = key; - parseBasicAuth(datasource); + if (datasource.url) { parseBasicAuth(datasource); } if (datasource.type === 'influxdb') { parseMultipleHosts(datasource); } }); @@ -78,6 +78,10 @@ function (_, crypto) { settings.panels = _.union(settings.panels, settings.plugins.panels); } + if (!settings.plugins.dependencies) { + settings.plugins.dependencies = []; + } + return settings; }; }); diff --git a/src/app/controllers/console-ctrl.js b/src/app/controllers/console-ctrl.js index e2194b8eca9..f5ce873bbfd 100644 --- a/src/app/controllers/console-ctrl.js +++ b/src/app/controllers/console-ctrl.js @@ -15,20 +15,6 @@ function (angular, _, moment) { var events = []; - var oldLog = console.log; - console.log = function (message) { - try { - if (_.isObject(message)) { - message = angular.toJson(message); - if (message.length > 50) { - message = message.substring(0, 50); - } - } - events.push(new ConsoleEvent('log', message, {})); - oldLog.apply(console, arguments); - } catch (e) { } - }; - function ConsoleEvent(type, title, data) { this.type = type; this.title = title; diff --git a/src/app/controllers/dash.js b/src/app/controllers/dash.js index e40db93fd7e..a4022f1ca7e 100644 --- a/src/app/controllers/dash.js +++ b/src/app/controllers/dash.js @@ -11,7 +11,9 @@ function (angular, $, config, _) { var module = angular.module('grafana.controllers'); module.controller('DashCtrl', function( - $scope, $rootScope, dashboardKeybindings, filterSrv, dashboardSrv, panelMoveSrv, timer) { + $scope, $rootScope, dashboardKeybindings, + filterSrv, dashboardSrv, dashboardViewStateSrv, + panelMoveSrv, timer) { $scope.editor = { index: 0 }; $scope.panelNames = config.panels; @@ -24,9 +26,13 @@ function (angular, $, config, _) { $scope.setupDashboard = function(event, dashboardData) { timer.cancel_all(); - $rootScope.fullscreen = false; + $rootScope.performance.dashboardLoadStart = new Date().getTime(); + $rootScope.performance.panelsInitialized = 0; + $rootScope.performance.panelsRendered= 0; $scope.dashboard = dashboardSrv.create(dashboardData); + $scope.dashboardViewState = dashboardViewStateSrv.create($scope); + $scope.grafana.style = $scope.dashboard.style; $scope.filter = filterSrv; @@ -78,10 +84,6 @@ function (angular, $, config, _) { }; }; - $scope.row_style = function(row) { - return { 'min-height': row.collapse ? '5px' : row.height }; - }; - $scope.panel_path =function(type) { if(type) { return 'app/panels/'+type.replace(".","/"); diff --git a/src/app/controllers/dashLoader.js b/src/app/controllers/dashLoader.js index 27071213d8f..9f036b87fa0 100644 --- a/src/app/controllers/dashLoader.js +++ b/src/app/controllers/dashLoader.js @@ -14,6 +14,7 @@ function (angular, _, moment, config) { $scope.init = function() { $scope.db = datasourceSrv.getGrafanaDB(); + $scope.onAppEvent('save-dashboard', function() { $scope.saveDashboard(); }); @@ -21,10 +22,7 @@ function (angular, _, moment, config) { $scope.onAppEvent('zoom-out', function() { $scope.zoom(2); }); - }; - $scope.exitFullscreen = function() { - $scope.emitAppEvent('panel-fullscreen-exit'); }; $scope.set_default = function() { @@ -78,6 +76,7 @@ function (angular, _, moment, config) { .then(function(result) { alertSrv.set('Dashboard Saved', 'Dashboard has been saved as "' + result.title + '"','success', 5000); + $location.search({}); $location.path(result.url); $rootScope.$emit('dashboard-saved', $scope.dashboard); @@ -135,6 +134,7 @@ function (angular, _, moment, config) { $scope.openSaveDropdown = function() { $scope.isFavorite = playlistSrv.isCurrentFavorite($scope.dashboard); + $scope.saveDropdownOpened = true; }; $scope.markAsFavorite = function() { diff --git a/src/app/controllers/grafanaCtrl.js b/src/app/controllers/grafanaCtrl.js index eaf3dfd0422..2754595896f 100644 --- a/src/app/controllers/grafanaCtrl.js +++ b/src/app/controllers/grafanaCtrl.js @@ -2,8 +2,9 @@ define([ 'angular', 'config', 'lodash', + 'jquery', ], -function (angular, config, _) { +function (angular, config, _, $) { "use strict"; var module = angular.module('grafana.controllers'); @@ -11,15 +12,21 @@ function (angular, config, _) { module.controller('GrafanaCtrl', function($scope, alertSrv, grafanaVersion, $rootScope) { $scope.grafanaVersion = grafanaVersion[0] === '@' ? 'master' : grafanaVersion; + $scope.consoleEnabled = (window.localStorage && window.localStorage.grafanaConsole === 'true'); + + $rootScope.profilingEnabled = (window.localStorage && window.localStorage.profilingEnabled === 'true'); + $rootScope.performance = { loadStart: new Date().getTime() }; $scope.init = function() { $scope._ = _; + if ($rootScope.profilingEnabled) { + $scope.initProfiling(); + } + $scope.dashAlerts = alertSrv; $scope.grafana = { style: 'dark' }; - - $scope.consoleEnabled = (window.localStorage && window.localStorage.grafanaConsole === 'true'); }; $scope.toggleConsole = function() { @@ -46,6 +53,61 @@ function (angular, config, _) { "#E0F9D7","#FCEACA","#CFFAFF","#F9E2D2","#FCE2DE","#BADFF4","#F9D9F9","#DEDAF7" //7 ]; + $scope.getTotalWatcherCount = function() { + var count = 0; + var scopes = 0; + var root = $(document.getElementsByTagName('body')); + + var f = function (element) { + if (element.data().hasOwnProperty('$scope')) { + scopes++; + angular.forEach(element.data().$scope.$$watchers, function () { + count++; + }); + } + + angular.forEach(element.children(), function (childElement) { + f($(childElement)); + }); + }; + + f(root); + $rootScope.performance.scopeCount = scopes; + return count; + }; + + $scope.initProfiling = function() { + var count = 0; + + $scope.$watch(function digestCounter() { + count++; + }, function() { + }); + + $scope.onAppEvent('setup-dashboard', function() { + count = 0; + + setTimeout(function() { + console.log("Dashboard::Performance Total Digests: " + count); + console.log("Dashboard::Performance Total Watchers: " + $scope.getTotalWatcherCount()); + console.log("Dashboard::Performance Total ScopeCount: " + $rootScope.performance.scopeCount); + + var timeTaken = $rootScope.performance.allPanelsInitialized - $rootScope.performance.dashboardLoadStart; + console.log("Dashboard::Performance - All panels initialized in " + timeTaken + " ms"); + + // measure digest performance + var rootDigestStart = window.performance.now(); + for (var i = 0; i < 30; i++) { + $rootScope.$apply(); + } + console.log("Dashboard::Performance Root Digest " + ((window.performance.now() - rootDigestStart) / 30)); + + }, 3000); + + }); + + }; + $scope.init(); }); diff --git a/src/app/controllers/row.js b/src/app/controllers/row.js index 364cf051dac..314559405ea 100644 --- a/src/app/controllers/row.js +++ b/src/app/controllers/row.js @@ -32,36 +32,13 @@ function (angular, app, _) { } }; - $scope.rowSpan = function(row) { - return _.reduce(row.panels, function(p,v) { - return p + v.span; - },0); - }; - // This can be overridden by individual panels $scope.close_edit = function() { $scope.$broadcast('render'); }; $scope.add_panel = function(panel) { - var rowSpan = $scope.rowSpan($scope.row); - var panelCount = $scope.row.panels.length; - var space = (12 - rowSpan) - panel.span; - - // try to make room of there is no space left - if (space <= 0) { - if (panelCount === 1) { - $scope.row.panels[0].span = 6; - panel.span = 6; - } - else if (panelCount === 2) { - $scope.row.panels[0].span = 4; - $scope.row.panels[1].span = 4; - panel.span = 4; - } - } - - $scope.row.panels.push(panel); + $scope.dashboard.add_panel(panel, $scope.row); }; $scope.delete_row = function() { @@ -100,45 +77,17 @@ function (angular, app, _) { }; $scope.duplicatePanel = function(panel, row) { - row = row || $scope.row; - var currentRowSpan = $scope.rowSpan(row); - if (currentRowSpan <= 9) { - row.panels.push(angular.copy(panel)); - } - else { - var rowsList = $scope.dashboard.rows; - var rowIndex = _.indexOf(rowsList, row); - if (rowIndex === rowsList.length - 1) { - var newRow = angular.copy($scope.row); - newRow.panels = []; - $scope.dashboard.rows.push(newRow); - $scope.duplicatePanel(panel, newRow); - } - else { - $scope.duplicatePanel(panel, rowsList[rowIndex+1]); - } - } + $scope.dashboard.duplicatePanel(panel, row || $scope.row); }; $scope.reset_panel = function(type) { - var - defaultSpan = 12, - _as = 12-$scope.rowSpan($scope.row); + var defaultSpan = 12; + var _as = 12 - $scope.dashboard.rowSpan($scope.row); $scope.panel = { error : false, - /** @scratch /panels/1 - * span:: A number, 1-12, that describes the width of the panel. - */ span : _as < defaultSpan && _as > 0 ? _as : defaultSpan, - /** @scratch /panels/1 - * editable:: Enable or disable the edit button the the panel - */ editable: true, - /** @scratch /panels/1 - * type:: The type of panel this object contains. Each panel type will require additional - * properties. See the panel types list to the right. - */ type : type }; @@ -155,12 +104,37 @@ function (angular, app, _) { $scope.row.height = fixRowHeight($scope.row.height); }; - /** @scratch /panels/2 - * -- - */ - $scope.init(); }); + module.directive('rowHeight', function() { + return function(scope, element) { + scope.$watchGroup(['row.collapse', 'row.height'], function() { + element[0].style.minHeight = scope.row.collapse ? '5px' : scope.row.height; + }); + }; + }); + + module.directive('panelWidth', function() { + return function(scope, element) { + scope.$watch('panel.span', function() { + element[0].style.width = ((scope.panel.span / 1.2) * 10) + '%'; + }); + }; + }); + + module.directive('panelDropZone', function() { + return function(scope, element) { + scope.$watch('dashboard.$$panelDragging', function(newVal) { + if (newVal && scope.dashboard.rowSpan(scope.row) < 10) { + element.show(); + } + else { + element.hide(); + } + }); + }; + }); + }); diff --git a/src/app/controllers/search.js b/src/app/controllers/search.js index 6c504e1b925..fd4e97e836d 100644 --- a/src/app/controllers/search.js +++ b/src/app/controllers/search.js @@ -41,6 +41,7 @@ function (angular, _, config, $) { var selectedDash = $scope.results.dashboards[$scope.selectedIndex]; if (selectedDash) { + $location.search({}); $location.path("/dashboard/db/" + selectedDash.id); setTimeout(function() { $('body').click(); // hack to force dropdown to close; @@ -98,6 +99,7 @@ function (angular, _, config, $) { $element.next().find('.dropdown-toggle').dropdown('toggle'); } + $scope.searchOpened = true; $scope.giveSearchFocus = $scope.giveSearchFocus + 1; $scope.query.query = 'title:'; $scope.search(); diff --git a/src/app/directives/addPanel.js b/src/app/directives/addPanel.js deleted file mode 100644 index e3f0fe7fc3d..00000000000 --- a/src/app/directives/addPanel.js +++ /dev/null @@ -1,35 +0,0 @@ -define([ - 'angular', - 'app', - 'lodash' -], -function (angular, app, _) { - 'use strict'; - - angular - .module('grafana.directives') - .directive('addPanel', function($compile) { - return { - restrict: 'A', - link: function($scope, elem) { - - $scope.$on("$destroy",function() { - elem.remove(); - }); - - $scope.$watch('panel.type', function() { - var _type = $scope.panel.type; - $scope.reset_panel(_type); - if(!_.isUndefined($scope.panel.type)) { - $scope.panel.loadingEditor = true; - $scope.require(['panels/'+$scope.panel.type.replace(".","/") +'/module'], function () { - var template = '
'; - elem.html($compile(angular.element(template))($scope)); - $scope.panel.loadingEditor = false; - }); - } - }); - } - }; - }); -}); \ No newline at end of file diff --git a/src/app/directives/all.js b/src/app/directives/all.js index ed97de93780..5236a1a619d 100644 --- a/src/app/directives/all.js +++ b/src/app/directives/all.js @@ -1,5 +1,4 @@ define([ - './addPanel', './arrayJoin', './dashUpload', './grafanaPanel', @@ -17,4 +16,4 @@ define([ './graphiteFuncEditor', './grafanaVersionCheck', './influxdbFuncEditor' -], function () {}); \ No newline at end of file +], function () {}); diff --git a/src/app/directives/bodyClass.js b/src/app/directives/bodyClass.js index dc3e08053e9..6d3c6d32e15 100644 --- a/src/app/directives/bodyClass.js +++ b/src/app/directives/bodyClass.js @@ -15,7 +15,7 @@ function (angular, app, _) { var lastPulldownVal; var lastHideControlsVal; - $scope.$watch('dashboard.pulldowns', function() { + $scope.$watchCollection('dashboard.pulldowns', function() { if (!$scope.dashboard) { return; } @@ -26,7 +26,7 @@ function (angular, app, _) { elem.toggleClass('submenu-controls-visible', panelEnabled); lastPulldownVal = panelEnabled; } - }, true); + }); $scope.$watch('dashboard.hideControls', function() { if (!$scope.dashboard) { @@ -49,4 +49,4 @@ function (angular, app, _) { }; }); -}); \ No newline at end of file +}); diff --git a/src/app/directives/configModal.js b/src/app/directives/configModal.js index e3d21253260..b7e579e7026 100644 --- a/src/app/directives/configModal.js +++ b/src/app/directives/configModal.js @@ -45,4 +45,4 @@ function (angular, _, $) { } }; }); -}); \ No newline at end of file +}); diff --git a/src/app/directives/grafanaGraph.js b/src/app/directives/grafanaGraph.js index 323db2141bd..a950032a7a9 100755 --- a/src/app/directives/grafanaGraph.js +++ b/src/app/directives/grafanaGraph.js @@ -21,7 +21,6 @@ function (angular, $, kbn, moment, _) { var legendSideLastValue = null; scope.$on('refresh',function() { - if (scope.otherPanelInFullscreenMode()) { return; } scope.get_data(); }); @@ -39,6 +38,10 @@ function (angular, $, kbn, moment, _) { // Receive render events scope.$on('render',function(event, renderData) { data = renderData || data; + if (!data) { + scope.get_data(); + return; + } annotations = data.annotations || annotations; render_panel(); }); @@ -300,9 +303,7 @@ function (angular, $, kbn, moment, _) { } function configureAxisMode(axis, format) { - if (format !== 'none') { - axis.tickFormatter = kbn.getFormatFunction(format, 1); - } + axis.tickFormatter = kbn.getFormatFunction(format, 1); } function time_format(interval, ticks, min, max) { diff --git a/src/app/directives/grafanaPanel.js b/src/app/directives/grafanaPanel.js index 32817c9b161..0229277b038 100644 --- a/src/app/directives/grafanaPanel.js +++ b/src/app/directives/grafanaPanel.js @@ -8,7 +8,7 @@ function (angular, $) { angular .module('grafana.directives') - .directive('grafanaPanel', function($compile) { + .directive('grafanaPanel', function($compile, $parse) { var container = ''; var content = ''; @@ -18,11 +18,13 @@ function (angular, $) { '