diff --git a/.gitignore b/.gitignore index 90bfb2a52f6..6aabb82d6c3 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ dist # locally required config files web.config config.js +src/css/*.min.css # Editor junk *.sublime-workspace diff --git a/.travis.yml b/.travis.yml index cde245b0f6c..d7ae9eb315d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,8 @@ language: node_js node_js: - "0.10" +git: + depth: 1 before_script: - npm install -g grunt-cli after_script: diff --git a/CHANGELOG.md b/CHANGELOG.md index 257564d41a1..55cde63197f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,27 @@ -# 1.7.0 (unreleased) +# 1.8.0 (unreleased) + +**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 + +**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 + +# 1.7.0 (2014-08-11) **Fixes** -- [Issue #655](https://github.com/grafana/grafana/issues/655). General: Auto refresh not initiated / started after dashboard loading - [Issue #652](https://github.com/grafana/grafana/issues/652). Timepicker: Entering custom date range impossible when refresh is low (now is constantly reset) - [Issue #450](https://github.com/grafana/grafana/issues/450). Graph: Tooltip does not disappear sometimes and would get stuck +- [Issue #655](https://github.com/grafana/grafana/issues/655). General: Auto refresh not initiated / started after dashboard loading +- [Issue #657](https://github.com/grafana/grafana/issues/657). General: Fix for refresh icon in IE browsers +- [Issue #661](https://github.com/grafana/grafana/issues/661). Annotations: Elasticsearch querystring with filter template replacements was not interpolated +- [Issue #660](https://github.com/grafana/grafana/issues/660). OpenTSDB: fix opentsdb queries that returned more than one series + +**Change** +- [Issue #681](https://github.com/grafana/grafana/issues/681). Dashboard: The panel error bar has been replaced with a small error indicator, this indicator does not change panel height and is a lot less intrusive. Hover over it for short details, click on it for more details. # 1.7.0-rc1 (2014-08-05) diff --git a/README.md b/README.md index 16c20127ab0..ef956a72ba8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[Grafana](http://grafana.org) [](https://travis-ci.org/grafana/grafana) [](https://www.gittip.com/torkelo) +[Grafana](http://grafana.org) [](https://travis-ci.org/grafana/grafana) [](https://coveralls.io/r/grafana/grafana?branch=develop) ================ [Website](http://grafana.org) | [Twitter](http://twitter.com/grafana) | diff --git a/latest.json b/latest.json index c337a17fbdc..38e862a643a 100644 --- a/latest.json +++ b/latest.json @@ -1,4 +1,4 @@ { - "version": "1.7.0-rc1", - "url": "http://grafanarel.s3.amazonaws.com/grafana-1.7.0-rc1.tar.gz" -} \ No newline at end of file + "version": "1.7.0", + "url": "http://grafanarel.s3.amazonaws.com/grafana-1.7.0" +} diff --git a/package.json b/package.json index e95428e903e..31c552fe6c2 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "company": "Coding Instinct AB" }, "name": "grafana", - "version": "1.7.0-rc1", + "version": "1.7.0", "repository": { "type": "git", "url": "http://github.com/torkelo/grafana.git" @@ -26,6 +26,7 @@ "grunt-contrib-less": "~0.7.0", "grunt-contrib-requirejs": "~0.4.1", "grunt-contrib-uglify": "~0.2.4", + "grunt-contrib-watch": "^0.6.1", "grunt-filerev": "^0.2.1", "grunt-git-describe": "~2.3.2", "grunt-karma": "~0.8.3", diff --git a/src/app/app.js b/src/app/app.js index d41e54a5bde..973a74ab9e0 100644 --- a/src/app/app.js +++ b/src/app/app.js @@ -51,13 +51,13 @@ function (angular, $, _, appLevelRequire, config) { app.config(function ($routeProvider, $controllerProvider, $compileProvider, $filterProvider, $provide) { $routeProvider.otherwise({ redirectTo: config.default_route }); - // this is how the internet told me to dynamically add modules :/ register_fns.controller = $controllerProvider.register; register_fns.directive = $compileProvider.directive; register_fns.factory = $provide.factory; register_fns.service = $provide.service; register_fns.filter = $filterProvider.register; + }); var apps_deps = [ diff --git a/src/app/controllers/all.js b/src/app/controllers/all.js index 2857f6c33c6..d3de182589d 100644 --- a/src/app/controllers/all.js +++ b/src/app/controllers/all.js @@ -13,4 +13,5 @@ define([ './playlistCtrl', './inspectCtrl', './opentsdbTargetCtrl', + './console-ctrl', ], function () {}); diff --git a/src/app/controllers/console-ctrl.js b/src/app/controllers/console-ctrl.js new file mode 100644 index 00000000000..e2194b8eca9 --- /dev/null +++ b/src/app/controllers/console-ctrl.js @@ -0,0 +1,121 @@ +define([ + 'angular', + 'lodash', + 'moment', +], +function (angular, _, moment) { + 'use strict'; + + var module = angular.module('grafana.controllers'); + var consoleEnabled = window.localStorage && window.localStorage.grafanaConsole === 'true'; + + if (!consoleEnabled) { + return; + } + + 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; + this.data = data; + this.time = moment().format('hh:mm:ss'); + + if (data.config) { + this.method = data.config.method; + this.elapsed = (new Date().getTime() - data.config.$grafana_timestamp) + ' ms'; + if (data.config.params && data.config.params.q) { + this.field2 = data.config.params.q; + } + if (_.isString(data.config.data)) { + this.field2 = data.config.data; + } + if (data.status !== 200) { + this.error = true; + this.field3 = data.data; + } + + if (_.isArray(data.data)) { + this.extractTimeseriesInfo(data.data); + } + } + } + + ConsoleEvent.prototype.extractTimeseriesInfo = function(series) { + if (series.length === 0) { + return; + } + + var points = 0; + var ok = false; + + if (series[0].datapoints) { + points = _.reduce(series, function(memo, val) { + return memo + val.datapoints.length; + }, 0); + ok = true; + } + if (series[0].columns) { + points = _.reduce(series, function(memo, val) { + return memo + val.points.length; + }, 0); + ok = true; + } + + if (ok) { + this.field1 = '(' + series.length + ' series'; + this.field1 += ', ' + points + ' points)'; + } + }; + + module.config(function($provide, $httpProvider) { + $provide.factory('mupp', function($q) { + return { + 'request': function(config) { + if (config.inspect) { + config.$grafana_timestamp = new Date().getTime(); + } + return config; + }, + 'response': function(response) { + if (response.config.inspect) { + events.push(new ConsoleEvent(response.config.inspect.type, response.config.url, response)); + } + return response; + }, + 'requestError': function(rejection) { + console.log('requestError', rejection); + return $q.reject(rejection); + }, + 'responseError': function (rejection) { + var inspect = rejection.config.inspect || { type: 'error' }; + events.push(new ConsoleEvent(inspect.type, rejection.config.url, rejection)); + return $q.reject(rejection); + } + }; + }); + + $httpProvider.interceptors.push('mupp'); + }); + + module.controller('ConsoleCtrl', function($scope) { + + $scope.events = events; + + }); + +}); diff --git a/src/app/controllers/grafanaCtrl.js b/src/app/controllers/grafanaCtrl.js index 8254c02e9d9..eaf3dfd0422 100644 --- a/src/app/controllers/grafanaCtrl.js +++ b/src/app/controllers/grafanaCtrl.js @@ -18,6 +18,13 @@ function (angular, config, _) { $scope.grafana = { style: 'dark' }; + + $scope.consoleEnabled = (window.localStorage && window.localStorage.grafanaConsole === 'true'); + }; + + $scope.toggleConsole = function() { + $scope.consoleEnabled = !$scope.consoleEnabled; + window.localStorage.grafanaConsole = $scope.consoleEnabled ? 'true' : 'false'; }; $rootScope.onAppEvent = function(name, callback) { diff --git a/src/app/controllers/inspectCtrl.js b/src/app/controllers/inspectCtrl.js index e9a98506dab..d6034516a6b 100644 --- a/src/app/controllers/inspectCtrl.js +++ b/src/app/controllers/inspectCtrl.js @@ -1,7 +1,8 @@ define([ - 'angular' + 'angular', + 'lodash' ], -function (angular) { +function (angular, _) { 'use strict'; var module = angular.module('grafana.controllers'); @@ -28,6 +29,16 @@ function (angular) { return; } + if (_.isString(model.error.data)) { + $scope.response = model.error.data; + } + + if (model.error.config && model.error.config.params) { + $scope.request_parameters = _.map(model.error.config.params, function(value, key) { + return { key: key, value: value}; + }); + } + if (model.error.stack) { $scope.editor.index = 2; $scope.stack_trace = model.error.stack; @@ -72,4 +83,4 @@ function (angular) { }; }); -}); \ No newline at end of file +}); diff --git a/src/app/controllers/row.js b/src/app/controllers/row.js index eafa5ea8a31..364cf051dac 100644 --- a/src/app/controllers/row.js +++ b/src/app/controllers/row.js @@ -33,11 +33,8 @@ function (angular, app, _) { }; $scope.rowSpan = function(row) { - var panels = _.filter(row.panels, function(p) { - return $scope.isPanel(p); - }); - return _.reduce(_.pluck(panels,'span'), function(p,v) { - return p+v; + return _.reduce(row.panels, function(p,v) { + return p + v.span; },0); }; diff --git a/src/app/directives/grafanaPanel.js b/src/app/directives/grafanaPanel.js index dc2534011d0..32817c9b161 100644 --- a/src/app/directives/grafanaPanel.js +++ b/src/app/directives/grafanaPanel.js @@ -15,16 +15,12 @@ function (angular, $) { var panelHeader = '