From 2b1dcaf5e38a1e912236b2ec78822021991169aa Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 11 Aug 2014 11:25:30 +0200 Subject: [PATCH 001/146] added global page title prefix setting --- src/app/components/settings.js | 1 + src/app/controllers/dash.js | 2 +- src/config.sample.js | 11 +++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/app/components/settings.js b/src/app/components/settings.js index 165bbc6c476..9601d80de79 100644 --- a/src/app/components/settings.js +++ b/src/app/components/settings.js @@ -14,6 +14,7 @@ function (_, crypto) { */ var defaults = { datasources : {}, + title : 'Grafana - ', panels : ['graph', 'text'], plugins : {}, default_route : '/dashboard/file/default.json', diff --git a/src/app/controllers/dash.js b/src/app/controllers/dash.js index e40db93fd7e..55f19905192 100644 --- a/src/app/controllers/dash.js +++ b/src/app/controllers/dash.js @@ -40,7 +40,7 @@ function (angular, $, config, _) { $scope.panelMoveOver = panelMove.onOver; $scope.panelMoveOut = panelMove.onOut; - window.document.title = 'Grafana - ' + $scope.dashboard.title; + window.document.title = (config.title ? config.title : '') + $scope.dashboard.title; // start auto refresh if($scope.dashboard.refresh) { diff --git a/src/config.sample.js b/src/config.sample.js index 9bce1540fa4..4bc6864e442 100644 --- a/src/config.sample.js +++ b/src/config.sample.js @@ -73,6 +73,17 @@ function (Settings) { * ======================================================== */ + /* title: + * The global page title prefix that is prepended before the specific dashboard titles. + * Defaults to 'Grafana - '. + * + * title: undefined, // default prefix, page title = 'Grafana - ' + * title: null, // no prefix, page title = + * title: '', // no prefix, page title = + * title: 'Custom | ', // custom prefix, page title = 'Custom | ' + */ + title: undefined, + // specify the limit for dashboard search results search: { max_results: 20 From b812b1c57992e239827bb16de28c3a024492b429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sat, 16 Aug 2014 19:02:50 +0200 Subject: [PATCH 002/146] Fixed link to playlist docs in readme.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dbbb566cf90..fd4fef2f364 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Graphite, InfluxDB & OpenTSDB. - Import dashboard from Graphite - Templating - [Scripted dashboards](http://grafana.org/docs/features/scripted_dashboards) -- [Dashboard playlists](http://grafana.org/docs/docs/features/playlist) +- [Dashboard playlists](http://grafana.org/docs/features/playlist) - [Time range controls](http://grafana.org/docs/features/time_range) ### InfluxDB From dc5973a0f361e769c251858f3bbd9e8b1df189a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sun, 17 Aug 2014 11:53:58 +0200 Subject: [PATCH 003/146] small css fix for alignment of legend values when shown in table style --- src/css/less/graph.less | 1 + 1 file changed, 1 insertion(+) diff --git a/src/css/less/graph.less b/src/css/less/graph.less index 07d7b7bb10d..79e87ab8f47 100644 --- a/src/css/less/graph.less +++ b/src/css/less/graph.less @@ -25,6 +25,7 @@ .graph-legend-value { float: left; white-space: nowrap; + text-align: left; &.current:before { content: "Current: " } From 27c536b1a1714743c6309e0494afca2178bbbb8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 18 Aug 2014 09:03:25 +0200 Subject: [PATCH 004/146] Small fix to 'none' axis formats and zero value when axis tickDecimals is high, Closes #707 --- src/app/components/kbn.js | 2 +- src/test/specs/kbn-format-specs.js | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/components/kbn.js b/src/app/components/kbn.js index 7f280cc99d6..3941715d9d9 100644 --- a/src/app/components/kbn.js +++ b/src/app/components/kbn.js @@ -536,7 +536,7 @@ function($, _, moment) { var formatted = String(Math.round(value * factor) / factor); // if exponent return directly - if (formatted.indexOf('e') !== -1) { + if (formatted.indexOf('e') !== -1 || value === 0) { return formatted; } diff --git a/src/test/specs/kbn-format-specs.js b/src/test/specs/kbn-format-specs.js index 7e80d1a91eb..0faa4728f2d 100644 --- a/src/test/specs/kbn-format-specs.js +++ b/src/test/specs/kbn-format-specs.js @@ -27,6 +27,10 @@ define([ var str = kbn.getFormatFunction('')(2.75e-10, { tickDecimals: 12 }); expect(str).to.be('2.75e-10'); }); + it('should format 0 correctly', function() { + var str = kbn.getFormatFunction('')(0.0, { tickDecimals: 12 }); + expect(str).to.be('0'); + }); }); describe('none format tests', function() { From ffd73e8bfbc62b8fe1400d2e7e2695912c5c110e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 18 Aug 2014 09:35:13 +0200 Subject: [PATCH 005/146] Fix for graphite queries with glob syntax ([1-9] and ?) that made the graphite parser / query editor bail and fallback to text edit mode. --- CHANGELOG.md | 3 ++- src/app/directives/ngModelOnBlur.js | 5 +++-- src/app/services/graphite/lexer.js | 3 +++ src/test/specs/lexer-specs.js | 15 +++++++++++---- src/test/specs/parser-specs.js | 4 ++-- 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8215e325a1f..77e6019a60c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ - [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) +- [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) +- [Issue #697](https://github.com/grafana/grafana/issues/697). Graphite: Fix for Glob syntax in graphite queries ([1-9] and ?) that made the query editor / parser bail and fallback to a text box. **Tech** - Upgraded from angularjs 1.1.5 to 1.3 beta 17; diff --git a/src/app/directives/ngModelOnBlur.js b/src/app/directives/ngModelOnBlur.js index 2a107e71808..0e9d94a282e 100644 --- a/src/app/directives/ngModelOnBlur.js +++ b/src/app/directives/ngModelOnBlur.js @@ -7,13 +7,14 @@ function (angular) { .directive('ngModelOnblur', function() { return { restrict: 'A', + priority: 1, require: 'ngModel', link: function(scope, elm, attr, ngModelCtrl) { if (attr.type === 'radio' || attr.type === 'checkbox') { return; } - elm.unbind('input').unbind('keydown').unbind('change'); + elm.off('input keydown change'); elm.bind('blur', function() { scope.$apply(function() { ngModelCtrl.$setViewValue(elm.val()); @@ -22,4 +23,4 @@ function (angular) { } }; }); -}); \ No newline at end of file +}); diff --git a/src/app/services/graphite/lexer.js b/src/app/services/graphite/lexer.js index 718d5206ee9..05249b9bff9 100644 --- a/src/app/services/graphite/lexer.js +++ b/src/app/services/graphite/lexer.js @@ -124,6 +124,9 @@ define([ i === 45 || // - i === 42 || // * i === 58 || // : + i === 91 || // templateStart [ + i === 93 || // templateEnd ] + i === 63 || // ? i === 37 || // % i >= 97 && i <= 122; // a-z } diff --git a/src/test/specs/lexer-specs.js b/src/test/specs/lexer-specs.js index 92dbb2c6931..04531423802 100644 --- a/src/test/specs/lexer-specs.js +++ b/src/test/specs/lexer-specs.js @@ -71,10 +71,17 @@ define([ it('should tokenize metric with template parameter', function() { var lexer = new Lexer("metric.[[server]].test"); var tokens = lexer.tokenize(); - expect(tokens[2].type).to.be('templateStart'); - expect(tokens[3].type).to.be('identifier'); - expect(tokens[3].value).to.be('server'); - expect(tokens[4].type).to.be('templateEnd'); + expect(tokens[2].type).to.be('identifier'); + expect(tokens[2].value).to.be('[[server]]'); + expect(tokens[4].type).to.be('identifier'); + }); + + it('should tokenize metric with question mark', function() { + var lexer = new Lexer("metric.server_??.test"); + var tokens = lexer.tokenize(); + expect(tokens[2].type).to.be('identifier'); + expect(tokens[2].value).to.be('server_??'); + expect(tokens[4].type).to.be('identifier'); }); it('should handle error with unterminated string', function() { diff --git a/src/test/specs/parser-specs.js b/src/test/specs/parser-specs.js index feb5367711d..802474a5814 100644 --- a/src/test/specs/parser-specs.js +++ b/src/test/specs/parser-specs.js @@ -106,8 +106,8 @@ define([ expect(rootNode.message).to.be(undefined); expect(rootNode.params[0].type).to.be('metric'); - expect(rootNode.params[0].segments[1].type).to.be('template'); - expect(rootNode.params[0].segments[1].value).to.be('server'); + expect(rootNode.params[0].segments[1].type).to.be('segment'); + expect(rootNode.params[0].segments[1].value).to.be('[[server]]'); }); it('invalid metric expression', function() { From 1a3dac0c17bf597836681381b3055bfbfc8530e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 18 Aug 2014 13:17:18 +0200 Subject: [PATCH 006/146] Fix for timepicker dates and tooltip when UTC timzone is selected, custom date modal is still local time, Closes #277 --- CHANGELOG.md | 1 + src/app/components/kbn.js | 6 +++++- src/app/directives/grafanaGraph.js | 5 +---- src/app/panels/timepicker/module.js | 17 +++++++++++------ src/app/services/dashboard/dashboardSrv.js | 11 ++++++++++- 5 files changed, 28 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77e6019a60c..e7ffc6bebf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ **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) - [Issue #697](https://github.com/grafana/grafana/issues/697). Graphite: Fix for Glob syntax in graphite queries ([1-9] and ?) that made the query editor / parser bail and fallback to a text box. +- [Issue #277](https://github.com/grafana/grafana/issues/277). Dashboard: Fix for timepicker date & tooltip when UTC timezone selected. Closes #277 **Tech** - Upgraded from angularjs 1.1.5 to 1.3 beta 17; diff --git a/src/app/components/kbn.js b/src/app/components/kbn.js index 3941715d9d9..71fe1b4b75a 100644 --- a/src/app/components/kbn.js +++ b/src/app/components/kbn.js @@ -1,4 +1,8 @@ -define(['jquery','lodash','moment'], +define([ + 'jquery', + 'lodash', + 'moment' +], function($, _, moment) { 'use strict'; diff --git a/src/app/directives/grafanaGraph.js b/src/app/directives/grafanaGraph.js index a950032a7a9..a91967b967b 100755 --- a/src/app/directives/grafanaGraph.js +++ b/src/app/directives/grafanaGraph.js @@ -354,10 +354,7 @@ function (angular, $, kbn, moment, _) { } value = kbn.getFormatFunction(format, 2)(value); - - timestamp = dashboard.timezone === 'browser' ? - moment(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss') : - moment.utc(item.datapoint[0]).format('YYYY-MM-DD HH:mm:ss'); + timestamp = dashboard.formatDate(item.datapoint[0]); $tooltip.html(group + value + " @ " + timestamp).place_tt(pos.pageX, pos.pageY); } else { diff --git a/src/app/panels/timepicker/module.js b/src/app/panels/timepicker/module.js index 3f29f1652dc..e080096ae60 100644 --- a/src/app/panels/timepicker/module.js +++ b/src/app/panels/timepicker/module.js @@ -80,9 +80,14 @@ function (angular, app, _, moment, kbn) { $scope.temptime = cloneTime($scope.time); $scope.tempnow = $scope.panel.now; + $scope.temptime.from.date.setHours(0,0,0,0); + $scope.temptime.to.date.setHours(0,0,0,0); + // Date picker needs the date to be at the start of the day - $scope.temptime.from.date.setHours(1,0,0,0); - $scope.temptime.to.date.setHours(1,0,0,0); + if(new Date().getTimezoneOffset() < 0) { + $scope.temptime.from.date = moment($scope.temptime.from.date).add('days',1).toDate(); + $scope.temptime.to.date = moment($scope.temptime.to.date).add('days',1).toDate(); + } $q.when(customTimeModal).then(function(modalEl) { modalEl.modal('show'); @@ -175,8 +180,8 @@ function (angular, app, _, moment, kbn) { var model = { from: getTimeObj(from), to: getTimeObj(to), }; if (model.from.date) { - model.tooltip = moment(model.from.date).format('YYYY-MM-DD HH:mm:ss') + '
to
'; - model.tooltip += moment(model.to.date).format('YYYY-MM-DD HH:mm:ss'); + model.tooltip = $scope.dashboard.formatDate(model.from.date) + '
to
'; + model.tooltip += $scope.dashboard.formatDate(model.to.date); } else { model.tooltip = 'Click to set time filter'; @@ -188,8 +193,8 @@ function (angular, app, _, moment, kbn) { moment(model.to.date).fromNow(); } else { - model.rangeString = moment(model.from.date).format('MMM D, YYYY hh:mm:ss') + ' to ' + - moment(model.to.date).format('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'); } } diff --git a/src/app/services/dashboard/dashboardSrv.js b/src/app/services/dashboard/dashboardSrv.js index 43c93379dd9..ff1e149c3b7 100644 --- a/src/app/services/dashboard/dashboardSrv.js +++ b/src/app/services/dashboard/dashboardSrv.js @@ -3,9 +3,10 @@ define([ 'jquery', 'kbn', 'lodash', + 'moment', '../timer', ], -function (angular, $, kbn, _) { +function (angular, $, kbn, _, moment) { 'use strict'; var module = angular.module('grafana.services'); @@ -108,6 +109,14 @@ function (angular, $, kbn, _) { this.rows.push(newRow); }; + p.formatDate = function(date, format) { + format = format || 'YYYY-MM-DD HH:mm:ss'; + + return this.timezone === 'browser' ? + moment(date).format(format) : + moment.utc(date).format(format); + }; + p.emit_refresh = function() { $rootScope.$broadcast('refresh'); }; From a64604de6bdc6a39bdb478096642b2a57c63f5cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 18 Aug 2014 16:38:04 +0200 Subject: [PATCH 007/146] UI improvements to search result list (larger click are for dashboard title link, plus UI look polish), Closes #709 --- CHANGELOG.md | 1 + src/app/controllers/dashboardNavCtrl.js | 4 +- src/app/controllers/graphiteTarget.js | 2 +- src/app/controllers/search.js | 7 +- src/app/partials/search.html | 80 +++++++++---------- .../dashboard/dashboardKeyBindings.js | 2 +- src/css/less/grafana.less | 51 +++++++++--- src/css/less/variables.dark.less | 10 ++- src/css/less/variables.light.less | 11 ++- 9 files changed, 109 insertions(+), 59 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7ffc6bebf5..793a0740e4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,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 **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) diff --git a/src/app/controllers/dashboardNavCtrl.js b/src/app/controllers/dashboardNavCtrl.js index 7fe6e03f78b..a58a2647945 100644 --- a/src/app/controllers/dashboardNavCtrl.js +++ b/src/app/controllers/dashboardNavCtrl.js @@ -87,7 +87,9 @@ function (angular, _, moment, config, store) { }); }; - $scope.deleteDashboard = function(id) { + $scope.deleteDashboard = function(id, $event) { + $event.stopPropagation(); + if (!confirm('Are you sure you want to delete dashboard?')) { return; } diff --git a/src/app/controllers/graphiteTarget.js b/src/app/controllers/graphiteTarget.js index 6b74455f59c..935ee1aeef0 100644 --- a/src/app/controllers/graphiteTarget.js +++ b/src/app/controllers/graphiteTarget.js @@ -289,7 +289,7 @@ function (angular, _, config, gfunc, Parser) { this.expandable = options.expandable; if (options.type === 'template') { - this.html = $sce.trustAsHtml("" + options.value + ""); + this.html = $sce.trustAsHtml(options.value); } else { this.html = $sce.trustAsHtml(this.value); diff --git a/src/app/controllers/search.js b/src/app/controllers/search.js index fd4e97e836d..de0ec44045a 100644 --- a/src/app/controllers/search.js +++ b/src/app/controllers/search.js @@ -50,7 +50,12 @@ function (angular, _, config, $) { } }; - $scope.shareDashboard = function(title, id) { + $scope.goToDashboard = function(id) { + $location.path("/dashboard/db/" + id); + }; + + $scope.shareDashboard = function(title, id, $event) { + $event.stopPropagation(); var baseUrl = window.location.href.replace(window.location.hash,''); $scope.share = { diff --git a/src/app/partials/search.html b/src/app/partials/search.html index 651928488e2..e1c9f158e0b 100644 --- a/src/app/partials/search.html +++ b/src/app/partials/search.html @@ -47,50 +47,46 @@
No dashboards or metrics matching your query found
- - - - - -
- - {{tag.term}}  ({{tag.count}}) - - -
+ - - - - - - - + - - - - - - - -
metric - {{row.id}} - - -
- - - - {{tag}} - -
diff --git a/src/app/services/dashboard/dashboardKeyBindings.js b/src/app/services/dashboard/dashboardKeyBindings.js index f4e2b6f0df7..da285e48809 100644 --- a/src/app/services/dashboard/dashboardKeyBindings.js +++ b/src/app/services/dashboard/dashboardKeyBindings.js @@ -18,8 +18,8 @@ function(angular, $) { keyboardManager.unbind('ctrl+s'); keyboardManager.unbind('ctrl+r'); keyboardManager.unbind('ctrl+z'); - keyboardManager.unbind('esc'); }); + keyboardManager.unbind('esc'); keyboardManager.bind('ctrl+f', function(evt) { scope.emitAppEvent('open-search', evt); diff --git a/src/css/less/grafana.less b/src/css/less/grafana.less index 0be54a23adc..0fc9c72bf2f 100644 --- a/src/css/less/grafana.less +++ b/src/css/less/grafana.less @@ -35,9 +35,8 @@ // Search .grafana-search-panel { - padding: 6px 10px; - .search-field-wrapper { + padding: 6px 10px; input { width: 100%; } @@ -50,25 +49,55 @@ padding-right: 25px; } } +} - .selected td, tr.selected:nth-child(odd)>td { - background: @blue; - color: white; - text-shadow: -1px -1px 1px rgba(0,0,0,0.3); - a { - color: white; +.search-results-container { + .search-result-item a { + } + + .search-result-item:hover, .search-result-item.selected { + .search-result-link, .icon { + color: @grafanaListHighlight; + } + .search-result-link .label { + background-color: @blue; } } - .selected-tag .label-tag { - background-color: @blue; + + .search-result-link { + color: @grafanaListMainLinkColor; + .icon { + padding-right: 10px; + color: @grafanaListHighlightContrast; + } + } + + .search-result-item:nth-child(odd) { + background-color: @grafanaListAccent; + } + + .search-result-item { + padding: 6px 10px; + white-space: nowrap; + border-top: 1px solid @grafanaListBorderTop; + border-bottom: 1px solid @grafanaListBorderBottom; + } + + .search-result-tags { + float: right; + } + + .search-result-actions { + float: right; + padding-left: 10px; } } .search-tagview-switch { position: absolute; top: 15px; - right: 263px; + right: 266px; color: darken(@linkColor, 30%); &.active { color: @linkColor; diff --git a/src/css/less/variables.dark.less b/src/css/less/variables.dark.less index 3f4551ee4c5..3397ceed020 100644 --- a/src/css/less/variables.dark.less +++ b/src/css/less/variables.dark.less @@ -57,7 +57,7 @@ // Links // ------------------------- -@linkColor: darken(@white,5%); +@linkColor: darken(@white,11%); @linkColorHover: @white; @@ -93,6 +93,14 @@ @borderRadiusLarge: 4px; @borderRadiusSmall: 2px; +// Lists +@grafanaListBackground: transparent; +@grafanaListAccent: #232323; +@grafanaListBorderTop: #3E3E3E; +@grafanaListBorderBottom: #1c1919; +@grafanaListHighlight: @blue; +@grafanaListHighlightContrast: #4F4F4F; +@grafanaListMainLinkColor: @linkColor; // Tables // ------------------------- diff --git a/src/css/less/variables.light.less b/src/css/less/variables.light.less index a54218ebd4a..562114a9e54 100644 --- a/src/css/less/variables.light.less +++ b/src/css/less/variables.light.less @@ -20,7 +20,7 @@ // Accent colors // ------------------------- -@blue: #01A6E6; +@blue: #007FFF; @blueDark: #75CAEB; @green: #28B62C; @red: #FF4136; @@ -97,6 +97,15 @@ @borderRadiusLarge: 4px; @borderRadiusSmall: 2px; +// Lists +@grafanaListBackground: transparent; +@grafanaListAccent: #f9f9f9; +@grafanaListBorderTop: #eee; +@grafanaListBorderBottom: #efefef; +@grafanaListHighlight: @blue; +@grafanaListHighlightContrast: #ddd; +@grafanaListMainLinkColor: @textColor; + // Tables // ------------------------- From 5c0d1355a5911c28141be54eb9fe83b9792e2383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 18 Aug 2014 19:33:38 +0200 Subject: [PATCH 008/146] Second take on dashboard tags search result colors --- src/app/controllers/search.js | 26 ++++++++++++++++++++++++++ src/app/partials/search.html | 4 ++-- src/css/less/grafana.less | 3 +++ src/css/less/overrides.less | 3 +++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/app/controllers/search.js b/src/app/controllers/search.js index de0ec44045a..6e9de7ba585 100644 --- a/src/app/controllers/search.js +++ b/src/app/controllers/search.js @@ -9,6 +9,32 @@ function (angular, _, config, $) { var module = angular.module('grafana.controllers'); + function djb2(str) { + var hash = 5381; + for (var i = 0; i < str.length; i++) { + hash = ((hash << 5) + hash) + str.charCodeAt(i); /* hash * 33 + c */ + } + return hash; + } + + module.directive('tagColorFromName', function() { + return function (scope, element) { + var name = _.isString(scope.tag) ? scope.tag : scope.tag.term; + var hash = djb2(name.toLowerCase()); + var colors = [ + "#E24D42","#1F78C1","#BA43A9","#705DA0", + "#508642","#447EBC","#C15C17","#890F02", + "#0A437C","#6D1F62","#584477","#629E51", + "#BF1B00","#EA6460","#D683CE","#806EB7", + "#3F6833","#2F575E","#99440A","#E0752D", + "#58140C","#052B51","#511749","#3F2B5B", + ]; + var color = colors[Math.abs(hash % colors.length)]; + console.log("namei " + name + " color: " + color, hash % 4); + element.css("background-color", color); + }; + }); + module.controller('SearchCtrl', function($scope, $rootScope, $element, $location, datasourceSrv) { $scope.init = function() { diff --git a/src/app/partials/search.html b/src/app/partials/search.html index e1c9f158e0b..297edadedfc 100644 --- a/src/app/partials/search.html +++ b/src/app/partials/search.html @@ -54,7 +54,7 @@ ng-click="filterByTag(tag.term, $event)"> - {{tag.term}}  ({{tag.count}}) + {{tag.term}}  ({{tag.count}}) @@ -79,7 +79,7 @@ diff --git a/src/css/less/grafana.less b/src/css/less/grafana.less index 0fc9c72bf2f..9ddc8568258 100644 --- a/src/css/less/grafana.less +++ b/src/css/less/grafana.less @@ -86,6 +86,9 @@ .search-result-tags { float: right; + .label { + margin-left: 6px; + } } .search-result-actions { diff --git a/src/css/less/overrides.less b/src/css/less/overrides.less index 7615f35d6f8..dd806cb46ca 100644 --- a/src/css/less/overrides.less +++ b/src/css/less/overrides.less @@ -589,6 +589,9 @@ div.flot-text { .label-tag { background-color: @purple; color: darken(@white, 5%); + text-shadow: none; + font-size: 13px; + padding: 3px 6px; } .label-tag:hover { From 937ac84538492571367c71a0513439133b8792a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 18 Aug 2014 21:47:56 +0200 Subject: [PATCH 009/146] Began work on per series style overrides, #425 --- src/app/directives/addGraphiteFunc.js | 2 +- src/app/directives/grafanaGraph.js | 4 ++- src/app/panels/graph/module.js | 48 +++++++++++++++++++++++++++ src/app/panels/graph/styleEditor.html | 39 ++++++++++++++++++++-- src/app/services/panelSrv.js | 2 +- 5 files changed, 90 insertions(+), 5 deletions(-) diff --git a/src/app/directives/addGraphiteFunc.js b/src/app/directives/addGraphiteFunc.js index ca5943da508..6898b838845 100644 --- a/src/app/directives/addGraphiteFunc.js +++ b/src/app/directives/addGraphiteFunc.js @@ -97,4 +97,4 @@ function (angular, app, _, $, gfunc) { }; }); } -}); \ No newline at end of file +}); diff --git a/src/app/directives/grafanaGraph.js b/src/app/directives/grafanaGraph.js index a91967b967b..f0ffd14dd67 100755 --- a/src/app/directives/grafanaGraph.js +++ b/src/app/directives/grafanaGraph.js @@ -156,9 +156,11 @@ 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; + data[0].lines = { show: false }; + data[0].bars = { show: true }; } - 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; } diff --git a/src/app/panels/graph/module.js b/src/app/panels/graph/module.js index 647e52c257e..d8a31f59312 100644 --- a/src/app/panels/graph/module.js +++ b/src/app/panels/graph/module.js @@ -180,6 +180,8 @@ function (angular, app, $, _, kbn, moment, timeSeries) { aliasColors: {}, aliasYAxis: {}, + + seriesOverrides: [], }; _.defaults($scope.panel,_d); @@ -357,7 +359,53 @@ function (angular, app, $, _, kbn, moment, timeSeries) { $scope.render(); }; + $scope.addSeriesOverride = function() { + $scope.panel.seriesOverrides.push({}); + }; + panelSrv.init($scope); }); + angular + .module('grafana.directives') + .directive('seriesOverrideOption', function($compile) { + var template = + ''; + + return { + scope: true, + link: function($scope, elem, attrs) { + var $template = $(template); + elem.append($template); + var $link = $(elem).find('a'); + + $scope.options = $scope.$eval(attrs.options); + $scope.options.unshift(null); + + $scope.options = _.map($scope.options, function(option, index) { + return { + text: option === null ? '' : String(option), + value: option, + click: 'setValue(' + index + ')' + }; + }); + + $scope.setValue = function(index) { + var value = $scope.options[index].value; + if (value === null) { + $link.html(''); + } + else { + $link.html(value); + } + }; + + $compile(elem.contents())($scope); + } + }; + }); + }); diff --git a/src/app/panels/graph/styleEditor.html b/src/app/panels/graph/styleEditor.html index ec18121cba7..c1b68e25beb 100644 --- a/src/app/panels/graph/styleEditor.html +++ b/src/app/panels/graph/styleEditor.html @@ -1,5 +1,3 @@ - -
Chart Options
@@ -64,3 +62,40 @@
+ +
+
+
Series specific overrides
+ + + + + + + + + + + + + + + + + + + + +
Series alias/regexBarsLinesPointsFillWidthRadiusStaircaseStackY-axisZ-indexColor
+ + + + + + + +
+ + +
+
diff --git a/src/app/services/panelSrv.js b/src/app/services/panelSrv.js index 3c362818f2d..cae4f14ff98 100644 --- a/src/app/services/panelSrv.js +++ b/src/app/services/panelSrv.js @@ -104,7 +104,7 @@ function (angular, _) { // Post init phase $scope.fullscreen = false; - $scope.editor = { index: 1 }; + $scope.editor = { index: 3 }; if ($scope.panelMeta.fullEditorTabs) { $scope.editorTabs = _.pluck($scope.panelMeta.fullEditorTabs, 'title'); } From 299053f2d574542eb888bd01126dade40c50e262 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 19 Aug 2014 11:12:48 +0200 Subject: [PATCH 010/146] Fix for utc in timepicker, Closes #713 --- src/app/panels/timepicker/module.js | 4 +- src/vendor/moment.js | 1330 +++++++++++++++++---------- 2 files changed, 871 insertions(+), 463 deletions(-) diff --git a/src/app/panels/timepicker/module.js b/src/app/panels/timepicker/module.js index e080096ae60..f00ad5f7a87 100644 --- a/src/app/panels/timepicker/module.js +++ b/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'); } } diff --git a/src/vendor/moment.js b/src/vendor/moment.js index 4efd7d50b94..03a2460105d 100644 --- a/src/vendor/moment.js +++ b/src/vendor/moment.js @@ -1,18 +1,19 @@ //! moment.js -//! version : 2.5.1 +//! version : 2.8.1 //! authors : Tim Wood, Iskren Chernev, Moment.js contributors //! license : MIT //! momentjs.com (function (undefined) { - /************************************ Constants ************************************/ var moment, - VERSION = "2.5.1", - global = this, + VERSION = '2.8.1', + // the global-scope this is NOT the global object in Node.js + globalScope = typeof global !== 'undefined' ? global : this, + oldGlobalMoment, round = Math.round, i, @@ -24,24 +25,14 @@ SECOND = 5, MILLISECOND = 6, - // internal storage for language config files - languages = {}, + // internal storage for locale config files + locales = {}, - // moment internal properties - momentProperties = { - _isAMomentObject: null, - _i : null, - _f : null, - _l : null, - _strict : null, - _isUTC : null, - _offset : null, // optional. Combine with _isUTC - _pf : null, - _lang : null // optional - }, + // extra moment internal properties (plugins register props here) + momentProperties = [], // check for nodeJS - hasModule = (typeof module !== 'undefined' && module.exports && typeof require !== 'undefined'), + hasModule = (typeof module !== 'undefined' && module.exports), // ASP.NET json date format regex aspNetJsonRegex = /^\/?Date\((\-?\d+)/i, @@ -52,7 +43,7 @@ isoDurationRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/, // format tokens - formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g, + formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g, localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g, // parsing token regexes @@ -65,6 +56,7 @@ parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z parseTokenT = /T/i, // T (ISO separator) parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123 + parseTokenOrdinal = /\d{1,2}/, //strict parsing regexes parseTokenOneDigit = /\d/, // 0 - 9 @@ -90,7 +82,7 @@ // iso time formats and regexes isoTimes = [ - ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d{1,3}/], + ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d+/], ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/], ['HH:mm', /(T| )\d\d:\d\d/], ['HH', /(T| )\d\d/] @@ -121,6 +113,7 @@ w : 'week', W : 'isoWeek', M : 'month', + Q : 'quarter', y : 'year', DDD : 'dayOfYear', e : 'weekday', @@ -140,6 +133,15 @@ // format function strings formatFunctions = {}, + // default relative time thresholds + relativeTimeThresholds = { + s: 45, // seconds to minute + m: 45, // minutes to hour + h: 22, // hours to day + d: 26, // days to month + M: 11 // months to year + }, + // tokens to ordinalize and pad ordinalizeTokens = 'DDD w W M D d'.split(' '), paddedTokens = 'M D H h m s w W'.split(' '), @@ -149,10 +151,10 @@ return this.month() + 1; }, MMM : function (format) { - return this.lang().monthsShort(this, format); + return this.localeData().monthsShort(this, format); }, MMMM : function (format) { - return this.lang().months(this, format); + return this.localeData().months(this, format); }, D : function () { return this.date(); @@ -164,13 +166,13 @@ return this.day(); }, dd : function (format) { - return this.lang().weekdaysMin(this, format); + return this.localeData().weekdaysMin(this, format); }, ddd : function (format) { - return this.lang().weekdaysShort(this, format); + return this.localeData().weekdaysShort(this, format); }, dddd : function (format) { - return this.lang().weekdays(this, format); + return this.localeData().weekdays(this, format); }, w : function () { return this.week(); @@ -216,10 +218,10 @@ return this.isoWeekday(); }, a : function () { - return this.lang().meridiem(this.hours(), this.minutes(), true); + return this.localeData().meridiem(this.hours(), this.minutes(), true); }, A : function () { - return this.lang().meridiem(this.hours(), this.minutes(), false); + return this.localeData().meridiem(this.hours(), this.minutes(), false); }, H : function () { return this.hours(); @@ -247,19 +249,19 @@ }, Z : function () { var a = -this.zone(), - b = "+"; + b = '+'; if (a < 0) { a = -a; - b = "-"; + b = '-'; } - return b + leftZeroFill(toInt(a / 60), 2) + ":" + leftZeroFill(toInt(a) % 60, 2); + return b + leftZeroFill(toInt(a / 60), 2) + ':' + leftZeroFill(toInt(a) % 60, 2); }, ZZ : function () { var a = -this.zone(), - b = "+"; + b = '+'; if (a < 0) { a = -a; - b = "-"; + b = '-'; } return b + leftZeroFill(toInt(a / 60), 2) + leftZeroFill(toInt(a) % 60, 2); }, @@ -277,8 +279,20 @@ } }, + deprecations = {}, + lists = ['months', 'monthsShort', 'weekdays', 'weekdaysShort', 'weekdaysMin']; + // Pick the first defined of two or three arguments. dfl comes from + // default. + function dfl(a, b, c) { + switch (arguments.length) { + case 2: return a != null ? a : b; + case 3: return a != null ? a : b != null ? b : c; + default: throw new Error('Implement me'); + } + } + function defaultParsingFlags() { // We need to deep clone this object, and es5 standard is not very // helpful. @@ -296,6 +310,31 @@ }; } + function printMsg(msg) { + if (moment.suppressDeprecationWarnings === false && + typeof console !== 'undefined' && console.warn) { + console.warn("Deprecation warning: " + msg); + } + } + + function deprecate(msg, fn) { + var firstTime = true; + return extend(function () { + if (firstTime) { + printMsg(msg); + firstTime = false; + } + return fn.apply(this, arguments); + }, fn); + } + + function deprecateSimple(name, msg) { + if (!deprecations[name]) { + printMsg(msg); + deprecations[name] = true; + } + } + function padToken(func, count) { return function (a) { return leftZeroFill(func.call(this, a), count); @@ -303,7 +342,7 @@ } function ordinalizeToken(func, period) { return function (a) { - return this.lang().ordinal(func.call(this, a), period); + return this.localeData().ordinal(func.call(this, a), period); }; } @@ -322,20 +361,23 @@ Constructors ************************************/ - function Language() { - + function Locale() { } // Moment prototype object - function Moment(config) { - checkOverflow(config); - extend(this, config); + function Moment(config, skipOverflow) { + if (skipOverflow !== false) { + checkOverflow(config); + } + copyConfig(this, config); + this._d = new Date(+config._d); } // Duration Constructor function Duration(duration) { var normalizedInput = normalizeObjectUnits(duration), years = normalizedInput.year || 0, + quarters = normalizedInput.quarter || 0, months = normalizedInput.month || 0, weeks = normalizedInput.week || 0, days = normalizedInput.day || 0, @@ -357,10 +399,13 @@ // which months you are are talking about, so we have to store // it separately. this._months = +months + + quarters * 3 + years * 12; this._data = {}; + this._locale = moment.localeData(); + this._bubble(); } @@ -376,26 +421,62 @@ } } - if (b.hasOwnProperty("toString")) { + if (b.hasOwnProperty('toString')) { a.toString = b.toString; } - if (b.hasOwnProperty("valueOf")) { + if (b.hasOwnProperty('valueOf')) { a.valueOf = b.valueOf; } return a; } - function cloneMoment(m) { - var result = {}, i; - for (i in m) { - if (m.hasOwnProperty(i) && momentProperties.hasOwnProperty(i)) { - result[i] = m[i]; + function copyConfig(to, from) { + var i, prop, val; + + if (typeof from._isAMomentObject !== 'undefined') { + to._isAMomentObject = from._isAMomentObject; + } + if (typeof from._i !== 'undefined') { + to._i = from._i; + } + if (typeof from._f !== 'undefined') { + to._f = from._f; + } + if (typeof from._l !== 'undefined') { + to._l = from._l; + } + if (typeof from._strict !== 'undefined') { + to._strict = from._strict; + } + if (typeof from._tzm !== 'undefined') { + to._tzm = from._tzm; + } + if (typeof from._isUTC !== 'undefined') { + to._isUTC = from._isUTC; + } + if (typeof from._offset !== 'undefined') { + to._offset = from._offset; + } + if (typeof from._pf !== 'undefined') { + to._pf = from._pf; + } + if (typeof from._locale !== 'undefined') { + to._locale = from._locale; + } + + if (momentProperties.length > 0) { + for (i in momentProperties) { + prop = momentProperties[i]; + val = from[prop]; + if (typeof val !== 'undefined') { + to[prop] = val; + } } } - return result; + return to; } function absRound(number) { @@ -418,35 +499,68 @@ return (sign ? (forceSign ? '+' : '') : '-') + output; } - // helper function for _.addTime and _.subtractTime - function addOrSubtractDurationFromMoment(mom, duration, isAdding, ignoreUpdateOffset) { + function positiveMomentsDifference(base, other) { + var res = {milliseconds: 0, months: 0}; + + res.months = other.month() - base.month() + + (other.year() - base.year()) * 12; + if (base.clone().add(res.months, 'M').isAfter(other)) { + --res.months; + } + + res.milliseconds = +other - +(base.clone().add(res.months, 'M')); + + return res; + } + + function momentsDifference(base, other) { + var res; + other = makeAs(other, base); + if (base.isBefore(other)) { + res = positiveMomentsDifference(base, other); + } else { + res = positiveMomentsDifference(other, base); + res.milliseconds = -res.milliseconds; + res.months = -res.months; + } + + return res; + } + + // TODO: remove 'name' arg after deprecation is removed + function createAdder(direction, name) { + return function (val, period) { + var dur, tmp; + //invert the arguments, but complain about it + if (period !== null && !isNaN(+period)) { + deprecateSimple(name, "moment()." + name + "(period, number) is deprecated. Please use moment()." + name + "(number, period)."); + tmp = val; val = period; period = tmp; + } + + val = typeof val === 'string' ? +val : val; + dur = moment.duration(val, period); + addOrSubtractDurationFromMoment(this, dur, direction); + return this; + }; + } + + function addOrSubtractDurationFromMoment(mom, duration, isAdding, updateOffset) { var milliseconds = duration._milliseconds, days = duration._days, - months = duration._months, - minutes, - hours; + months = duration._months; + updateOffset = updateOffset == null ? true : updateOffset; if (milliseconds) { mom._d.setTime(+mom._d + milliseconds * isAdding); } - // store the minutes and hours so we can restore them - if (days || months) { - minutes = mom.minute(); - hours = mom.hour(); - } if (days) { - mom.date(mom.date() + days * isAdding); + rawSetter(mom, 'Date', rawGetter(mom, 'Date') + days * isAdding); } if (months) { - mom.month(mom.month() + months * isAdding); + rawMonthSetter(mom, rawGetter(mom, 'Month') + months * isAdding); } - if (milliseconds && !ignoreUpdateOffset) { - moment.updateOffset(mom); - } - // restore the minutes and hours after possibly changing dst - if (days || months) { - mom.minute(minutes); - mom.hour(hours); + if (updateOffset) { + moment.updateOffset(mom, days || months); } } @@ -456,8 +570,8 @@ } function isDate(input) { - return Object.prototype.toString.call(input) === '[object Date]' || - input instanceof Date; + return Object.prototype.toString.call(input) === '[object Date]' || + input instanceof Date; } // compare two arrays, return the number of differences @@ -517,7 +631,7 @@ moment[field] = function (format, index) { var i, getter, - method = moment.fn._lang[field], + method = moment._locale[field], results = []; if (typeof format === 'number') { @@ -527,7 +641,7 @@ getter = function (i) { var m = moment().utc().set(setter, i); - return method.call(moment.fn._lang, m, format || ''); + return method.call(moment._locale, m, format || ''); }; if (index != null) { @@ -561,6 +675,10 @@ return new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); } + function weeksInYear(year, dow, doy) { + return weekOfYear(moment([year, 11, 31 + dow - doy]), dow, doy).week; + } + function daysInYear(year) { return isLeapYear(year) ? 366 : 365; } @@ -608,10 +726,50 @@ return m._isValid; } - function normalizeLanguage(key) { + function normalizeLocale(key) { return key ? key.toLowerCase().replace('_', '-') : key; } + // pick the locale from the array + // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each + // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root + function chooseLocale(names) { + var i = 0, j, next, locale, split; + + while (i < names.length) { + split = normalizeLocale(names[i]).split('-'); + j = split.length; + next = normalizeLocale(names[i + 1]); + next = next ? next.split('-') : null; + while (j > 0) { + locale = loadLocale(split.slice(0, j).join('-')); + if (locale) { + return locale; + } + if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { + //the next array item is better than a shallower substring of this one + break; + } + j--; + } + i++; + } + return null; + } + + function loadLocale(name) { + var oldLocale = null; + if (!locales[name] && hasModule) { + try { + oldLocale = moment.locale(); + require('./locale/' + name); + // because defineLocale currently also sets the global locale, we want to undo that for lazy loaded locales + moment.locale(oldLocale); + } catch (e) { } + } + return locales[name]; + } + // Return a moment from input, that is local/utc/zone equivalent to model. function makeAs(input, model) { return model._isUTC ? moment(input).zone(model._offset || 0) : @@ -619,11 +777,11 @@ } /************************************ - Languages + Locale ************************************/ - extend(Language.prototype, { + extend(Locale.prototype, { set : function (config) { var prop, i; @@ -637,12 +795,12 @@ } }, - _months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), + _months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'), months : function (m) { return this._months[m.month()]; }, - _monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"), + _monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'), monthsShort : function (m) { return this._monthsShort[m.month()]; }, @@ -668,17 +826,17 @@ } }, - _weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), + _weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'), weekdays : function (m) { return this._weekdays[m.day()]; }, - _weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"), + _weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'), weekdaysShort : function (m) { return this._weekdaysShort[m.day()]; }, - _weekdaysMin : "Su_Mo_Tu_We_Th_Fr_Sa".split("_"), + _weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'), weekdaysMin : function (m) { return this._weekdaysMin[m.day()]; }, @@ -705,11 +863,11 @@ }, _longDateFormat : { - LT : "h:mm A", - L : "MM/DD/YYYY", - LL : "MMMM D YYYY", - LLL : "MMMM D YYYY LT", - LLLL : "dddd, MMMM D YYYY LT" + LT : 'h:mm A', + L : 'MM/DD/YYYY', + LL : 'MMMM D, YYYY', + LLL : 'MMMM D, YYYY LT', + LLLL : 'dddd, MMMM D, YYYY LT' }, longDateFormat : function (key) { var output = this._longDateFormat[key]; @@ -751,35 +909,37 @@ }, _relativeTime : { - future : "in %s", - past : "%s ago", - s : "a few seconds", - m : "a minute", - mm : "%d minutes", - h : "an hour", - hh : "%d hours", - d : "a day", - dd : "%d days", - M : "a month", - MM : "%d months", - y : "a year", - yy : "%d years" + future : 'in %s', + past : '%s ago', + s : 'a few seconds', + m : 'a minute', + mm : '%d minutes', + h : 'an hour', + hh : '%d hours', + d : 'a day', + dd : '%d days', + M : 'a month', + MM : '%d months', + y : 'a year', + yy : '%d years' }, + relativeTime : function (number, withoutSuffix, string, isFuture) { var output = this._relativeTime[string]; return (typeof output === 'function') ? output(number, withoutSuffix, string, isFuture) : output.replace(/%d/i, number); }, + pastFuture : function (diff, output) { var format = this._relativeTime[diff > 0 ? 'future' : 'past']; return typeof format === 'function' ? format(output) : format.replace(/%s/i, output); }, ordinal : function (number) { - return this._ordinal.replace("%d", number); + return this._ordinal.replace('%d', number); }, - _ordinal : "%d", + _ordinal : '%d', preparse : function (string) { return string; @@ -804,78 +964,6 @@ } }); - // Loads a language definition into the `languages` cache. The function - // takes a key and optionally values. If not in the browser and no values - // are provided, it will load the language file module. As a convenience, - // this function also returns the language values. - function loadLang(key, values) { - values.abbr = key; - if (!languages[key]) { - languages[key] = new Language(); - } - languages[key].set(values); - return languages[key]; - } - - // Remove a language from the `languages` cache. Mostly useful in tests. - function unloadLang(key) { - delete languages[key]; - } - - // Determines which language definition to use and returns it. - // - // With no parameters, it will return the global language. If you - // pass in a language key, such as 'en', it will return the - // definition for 'en', so long as 'en' has already been loaded using - // moment.lang. - function getLangDefinition(key) { - var i = 0, j, lang, next, split, - get = function (k) { - if (!languages[k] && hasModule) { - try { - require('./lang/' + k); - } catch (e) { } - } - return languages[k]; - }; - - if (!key) { - return moment.fn._lang; - } - - if (!isArray(key)) { - //short-circuit everything else - lang = get(key); - if (lang) { - return lang; - } - key = [key]; - } - - //pick the language from the array - //try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each - //substring from most specific to least, but move to the next array item if it's a more specific variant than the current root - while (i < key.length) { - split = normalizeLanguage(key[i]).split('-'); - j = split.length; - next = normalizeLanguage(key[i + 1]); - next = next ? next.split('-') : null; - while (j > 0) { - lang = get(split.slice(0, j).join('-')); - if (lang) { - return lang; - } - if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { - //the next array item is better than a shallower substring of this one - break; - } - j--; - } - i++; - } - return moment.fn._lang; - } - /************************************ Formatting ************************************/ @@ -883,9 +971,9 @@ function removeFormattingTokens(input) { if (input.match(/\[[\s\S]/)) { - return input.replace(/^\[|\]$/g, ""); + return input.replace(/^\[|\]$/g, ''); } - return input.replace(/\\/g, ""); + return input.replace(/\\/g, ''); } function makeFormatFunction(format) { @@ -900,7 +988,7 @@ } return function (mom) { - var output = ""; + var output = ''; for (i = 0; i < length; i++) { output += array[i] instanceof Function ? array[i].call(mom, format) : array[i]; } @@ -910,12 +998,11 @@ // format date using native date object function formatMoment(m, format) { - if (!m.isValid()) { - return m.lang().invalidDate(); + return m.localeData().invalidDate(); } - format = expandFormat(format, m.lang()); + format = expandFormat(format, m.localeData()); if (!formatFunctions[format]) { formatFunctions[format] = makeFormatFunction(format); @@ -924,11 +1011,11 @@ return formatFunctions[format](m); } - function expandFormat(format, lang) { + function expandFormat(format, locale) { var i = 5; function replaceLongDateFormatTokens(input) { - return lang.longDateFormat(input) || input; + return locale.longDateFormat(input) || input; } localFormattingTokens.lastIndex = 0; @@ -951,6 +1038,8 @@ function getParseRegexForToken(token, config) { var a, strict = config._strict; switch (token) { + case 'Q': + return parseTokenOneDigit; case 'DDDD': return parseTokenThreeDigits; case 'YYYY': @@ -967,13 +1056,19 @@ case 'ggggg': return strict ? parseTokenSixDigits : parseTokenOneToSixDigits; case 'S': - if (strict) { return parseTokenOneDigit; } + if (strict) { + return parseTokenOneDigit; + } /* falls through */ case 'SS': - if (strict) { return parseTokenTwoDigits; } + if (strict) { + return parseTokenTwoDigits; + } /* falls through */ case 'SSS': - if (strict) { return parseTokenThreeDigits; } + if (strict) { + return parseTokenThreeDigits; + } /* falls through */ case 'DDD': return parseTokenOneToThreeDigits; @@ -985,7 +1080,7 @@ return parseTokenWord; case 'a': case 'A': - return getLangDefinition(config._l)._meridiemParse; + return config._locale._meridiemParse; case 'X': return parseTokenTimestampMs; case 'Z': @@ -1019,14 +1114,16 @@ case 'e': case 'E': return parseTokenOneOrTwoDigits; + case 'Do': + return parseTokenOrdinal; default : - a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), "i")); + a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), 'i')); return a; } } function timezoneMinutesFromString(string) { - string = string || ""; + string = string || ''; var possibleTzMatches = (string.match(parseTokenTimezone) || []), tzChunk = possibleTzMatches[possibleTzMatches.length - 1] || [], parts = (tzChunk + '').match(parseTimezoneChunker) || ['-', 0, 0], @@ -1040,6 +1137,12 @@ var a, datePartArray = config._a; switch (token) { + // QUARTER + case 'Q': + if (input != null) { + datePartArray[MONTH] = (toInt(input) - 1) * 3; + } + break; // MONTH case 'M' : // fall through to MM case 'MM' : @@ -1049,7 +1152,7 @@ break; case 'MMM' : // fall through to MMMM case 'MMMM' : - a = getLangDefinition(config._l).monthsParse(input); + a = config._locale.monthsParse(input); // if we didn't find a month name, mark the date as invalid. if (a != null) { datePartArray[MONTH] = a; @@ -1064,6 +1167,11 @@ datePartArray[DATE] = toInt(input); } break; + case 'Do' : + if (input != null) { + datePartArray[DATE] = toInt(parseInt(input, 10)); + } + break; // DAY OF YEAR case 'DDD' : // fall through to DDDD case 'DDDD' : @@ -1074,7 +1182,7 @@ break; // YEAR case 'YY' : - datePartArray[YEAR] = toInt(input) + (toInt(input) > 68 ? 1900 : 2000); + datePartArray[YEAR] = moment.parseTwoDigitYear(input); break; case 'YYYY' : case 'YYYYY' : @@ -1084,7 +1192,7 @@ // AM / PM case 'a' : // fall through to A case 'A' : - config._isPm = getLangDefinition(config._l).isPM(input); + config._isPm = config._locale.isPM(input); break; // 24 HOUR case 'H' : // fall through to hh @@ -1120,39 +1228,93 @@ config._useUTC = true; config._tzm = timezoneMinutesFromString(input); break; + // WEEKDAY - human + case 'dd': + case 'ddd': + case 'dddd': + a = config._locale.weekdaysParse(input); + // if we didn't get a weekday name, mark the date as invalid + if (a != null) { + config._w = config._w || {}; + config._w['d'] = a; + } else { + config._pf.invalidWeekday = input; + } + break; + // WEEK, WEEK DAY - numeric case 'w': case 'ww': case 'W': case 'WW': case 'd': - case 'dd': - case 'ddd': - case 'dddd': case 'e': case 'E': token = token.substr(0, 1); /* falls through */ - case 'gg': case 'gggg': - case 'GG': case 'GGGG': case 'GGGGG': token = token.substr(0, 2); if (input) { config._w = config._w || {}; - config._w[token] = input; + config._w[token] = toInt(input); } break; + case 'gg': + case 'GG': + config._w = config._w || {}; + config._w[token] = moment.parseTwoDigitYear(input); } } + function dayOfYearFromWeekInfo(config) { + var w, weekYear, week, weekday, dow, doy, temp; + + w = config._w; + if (w.GG != null || w.W != null || w.E != null) { + dow = 1; + doy = 4; + + // TODO: We need to take the current isoWeekYear, but that depends on + // how we interpret now (local, utc, fixed offset). So create + // a now version of current config (take local/utc/offset flags, and + // create now). + weekYear = dfl(w.GG, config._a[YEAR], weekOfYear(moment(), 1, 4).year); + week = dfl(w.W, 1); + weekday = dfl(w.E, 1); + } else { + dow = config._locale._week.dow; + doy = config._locale._week.doy; + + weekYear = dfl(w.gg, config._a[YEAR], weekOfYear(moment(), dow, doy).year); + week = dfl(w.w, 1); + + if (w.d != null) { + // weekday -- low day numbers are considered next week + weekday = w.d; + if (weekday < dow) { + ++week; + } + } else if (w.e != null) { + // local weekday -- counting starts from begining of week + weekday = w.e + dow; + } else { + // default to begining of week + weekday = dow; + } + } + temp = dayOfYearFromWeeks(weekYear, week, weekday, doy, dow); + + config._a[YEAR] = temp.year; + config._dayOfYear = temp.dayOfYear; + } + // convert an array to a date. // the array should mirror the parameters below // note: all values past the year are optional and will default to the lowest possible value. // [year, month, day , hour, minute, second, millisecond] function dateFromConfig(config) { - var i, date, input = [], currentDate, - yearToUse, fixYear, w, temp, lang, weekday, week; + var i, date, input = [], currentDate, yearToUse; if (config._d) { return; @@ -1162,39 +1324,12 @@ //compute day of the year from weeks and weekdays if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { - fixYear = function (val) { - var int_val = parseInt(val, 10); - return val ? - (val.length < 3 ? (int_val > 68 ? 1900 + int_val : 2000 + int_val) : int_val) : - (config._a[YEAR] == null ? moment().weekYear() : config._a[YEAR]); - }; - - w = config._w; - if (w.GG != null || w.W != null || w.E != null) { - temp = dayOfYearFromWeeks(fixYear(w.GG), w.W || 1, w.E, 4, 1); - } - else { - lang = getLangDefinition(config._l); - weekday = w.d != null ? parseWeekday(w.d, lang) : - (w.e != null ? parseInt(w.e, 10) + lang._week.dow : 0); - - week = parseInt(w.w, 10) || 1; - - //if we're parsing 'd', then the low day numbers may be next week - if (w.d != null && weekday < lang._week.dow) { - week++; - } - - temp = dayOfYearFromWeeks(fixYear(w.gg), week, weekday, lang._week.doy, lang._week.dow); - } - - config._a[YEAR] = temp.year; - config._dayOfYear = temp.dayOfYear; + dayOfYearFromWeekInfo(config); } //if the day of the year is set, figure out what it is if (config._dayOfYear) { - yearToUse = config._a[YEAR] == null ? currentDate[YEAR] : config._a[YEAR]; + yearToUse = dfl(config._a[YEAR], currentDate[YEAR]); if (config._dayOfYear > daysInYear(yearToUse)) { config._pf._overflowDayOfYear = true; @@ -1219,11 +1354,12 @@ config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; } - // add the offsets to the time to be parsed so that we can have a clean array for checking isValid - input[HOUR] += toInt((config._tzm || 0) / 60); - input[MINUTE] += toInt((config._tzm || 0) % 60); - config._d = (config._useUTC ? makeUTCDate : makeDate).apply(null, input); + // Apply timezone offset from input. The actual zone can be changed + // with parseZone. + if (config._tzm != null) { + config._d.setUTCMinutes(config._d.getUTCMinutes() + config._tzm); + } } function dateFromObject(config) { @@ -1262,18 +1398,21 @@ // date from string and format string function makeDateFromStringAndFormat(config) { + if (config._f === moment.ISO_8601) { + parseISO(config); + return; + } config._a = []; config._pf.empty = true; // This array is used to make a Date, either with `new Date` or `Date.UTC` - var lang = getLangDefinition(config._l), - string = '' + config._i, + var string = '' + config._i, i, parsedInput, tokens, token, skipped, stringLength = string.length, totalParsedInputLength = 0; - tokens = expandFormat(config._f, lang).match(formattingTokens) || []; + tokens = expandFormat(config._f, config._locale).match(formattingTokens) || []; for (i = 0; i < tokens.length; i++) { token = tokens[i]; @@ -1348,7 +1487,7 @@ for (i = 0; i < config._f.length; i++) { currentScore = 0; - tempConfig = extend({}, config); + tempConfig = copyConfig({}, config); tempConfig._pf = defaultParsingFlags(); tempConfig._f = config._f[i]; makeDateFromStringAndFormat(tempConfig); @@ -1375,7 +1514,7 @@ } // date from iso format - function makeDateFromString(config) { + function parseISO(config) { var i, l, string = config._i, match = isoRegex.exec(string); @@ -1385,7 +1524,7 @@ for (i = 0, l = isoDates.length; i < l; i++) { if (isoDates[i][1].exec(string)) { // match[5] should be "T" or undefined - config._f = isoDates[i][0] + (match[6] || " "); + config._f = isoDates[i][0] + (match[6] || ' '); break; } } @@ -1396,34 +1535,43 @@ } } if (string.match(parseTokenTimezone)) { - config._f += "Z"; + config._f += 'Z'; } makeDateFromStringAndFormat(config); + } else { + config._isValid = false; } - else { - config._d = new Date(string); + } + + // date from iso format or fallback + function makeDateFromString(config) { + parseISO(config); + if (config._isValid === false) { + delete config._isValid; + moment.createFromInputFallback(config); } } function makeDateFromInput(config) { - var input = config._i, - matched = aspNetJsonRegex.exec(input); - + var input = config._i, matched; if (input === undefined) { config._d = new Date(); - } else if (matched) { + } else if (isDate(input)) { + config._d = new Date(+input); + } else if ((matched = aspNetJsonRegex.exec(input)) !== null) { config._d = new Date(+matched[1]); } else if (typeof input === 'string') { makeDateFromString(config); } else if (isArray(input)) { config._a = input.slice(0); dateFromConfig(config); - } else if (isDate(input)) { - config._d = new Date(+input); } else if (typeof(input) === 'object') { dateFromObject(config); - } else { + } else if (typeof(input) === 'number') { + // from milliseconds config._d = new Date(input); + } else { + moment.createFromInputFallback(config); } } @@ -1447,13 +1595,13 @@ return date; } - function parseWeekday(input, language) { + function parseWeekday(input, locale) { if (typeof input === 'string') { if (!isNaN(input)) { input = parseInt(input, 10); } else { - input = language.weekdaysParse(input); + input = locale.weekdaysParse(input); if (typeof input !== 'number') { return null; } @@ -1468,29 +1616,33 @@ // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize - function substituteTimeAgo(string, number, withoutSuffix, isFuture, lang) { - return lang.relativeTime(number || 1, !!withoutSuffix, string, isFuture); + function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) { + return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture); } - function relativeTime(milliseconds, withoutSuffix, lang) { - var seconds = round(Math.abs(milliseconds) / 1000), - minutes = round(seconds / 60), - hours = round(minutes / 60), - days = round(hours / 24), - years = round(days / 365), - args = seconds < 45 && ['s', seconds] || + function relativeTime(posNegDuration, withoutSuffix, locale) { + var duration = moment.duration(posNegDuration).abs(), + seconds = round(duration.as('s')), + minutes = round(duration.as('m')), + hours = round(duration.as('h')), + days = round(duration.as('d')), + months = round(duration.as('M')), + years = round(duration.as('y')), + + args = seconds < relativeTimeThresholds.s && ['s', seconds] || minutes === 1 && ['m'] || - minutes < 45 && ['mm', minutes] || + minutes < relativeTimeThresholds.m && ['mm', minutes] || hours === 1 && ['h'] || - hours < 22 && ['hh', hours] || + hours < relativeTimeThresholds.h && ['hh', hours] || days === 1 && ['d'] || - days <= 25 && ['dd', days] || - days <= 45 && ['M'] || - days < 345 && ['MM', round(days / 30)] || + days < relativeTimeThresholds.d && ['dd', days] || + months === 1 && ['M'] || + months < relativeTimeThresholds.M && ['MM', months] || years === 1 && ['y'] || ['yy', years]; + args[2] = withoutSuffix; - args[3] = milliseconds > 0; - args[4] = lang; + args[3] = +posNegDuration > 0; + args[4] = locale; return substituteTimeAgo.apply({}, args); } @@ -1521,7 +1673,7 @@ daysToDayOfWeek += 7; } - adjustedMoment = moment(mom).add('d', daysToDayOfWeek); + adjustedMoment = moment(mom).add(daysToDayOfWeek, 'd'); return { week: Math.ceil(adjustedMoment.dayOfYear() / 7), year: adjustedMoment.year() @@ -1532,6 +1684,7 @@ function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) { var d = makeUTCDate(year, 0, 1).getUTCDay(), daysToAdd, dayOfYear; + d = d === 0 ? 7 : d; weekday = weekday != null ? weekday : firstDayOfWeek; daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0) - (d < firstDayOfWeek ? 7 : 0); dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1; @@ -1550,18 +1703,18 @@ var input = config._i, format = config._f; - if (input === null) { + config._locale = config._locale || moment.localeData(config._l); + + if (input === null || (format === undefined && input === '')) { return moment.invalid({nullInput: true}); } if (typeof input === 'string') { - config._i = input = getLangDefinition().preparse(input); + config._i = input = config._locale.preparse(input); } if (moment.isMoment(input)) { - config = cloneMoment(input); - - config._d = new Date(+input._d); + return new Moment(input, true); } else if (format) { if (isArray(format)) { makeDateFromStringAndArray(config); @@ -1575,12 +1728,12 @@ return new Moment(config); } - moment = function (input, format, lang, strict) { + moment = function (input, format, locale, strict) { var c; - if (typeof(lang) === "boolean") { - strict = lang; - lang = undefined; + if (typeof(locale) === "boolean") { + strict = locale; + locale = undefined; } // object construction must be done this way. // https://github.com/moment/moment/issues/1423 @@ -1588,7 +1741,7 @@ c._isAMomentObject = true; c._i = input; c._f = format; - c._l = lang; + c._l = locale; c._strict = strict; c._isUTC = false; c._pf = defaultParsingFlags(); @@ -1596,13 +1749,59 @@ return makeMoment(c); }; + moment.suppressDeprecationWarnings = false; + + moment.createFromInputFallback = deprecate( + 'moment construction falls back to js Date. This is ' + + 'discouraged and will be removed in upcoming major ' + + 'release. Please refer to ' + + 'https://github.com/moment/moment/issues/1407 for more info.', + function (config) { + config._d = new Date(config._i); + } + ); + + // Pick a moment m from moments so that m[fn](other) is true for all + // other. This relies on the function fn to be transitive. + // + // moments should either be an array of moment objects or an array, whose + // first element is an array of moment objects. + function pickBy(fn, moments) { + var res, i; + if (moments.length === 1 && isArray(moments[0])) { + moments = moments[0]; + } + if (!moments.length) { + return moment(); + } + res = moments[0]; + for (i = 1; i < moments.length; ++i) { + if (moments[i][fn](res)) { + res = moments[i]; + } + } + return res; + } + + moment.min = function () { + var args = [].slice.call(arguments, 0); + + return pickBy('isBefore', args); + }; + + moment.max = function () { + var args = [].slice.call(arguments, 0); + + return pickBy('isAfter', args); + }; + // creating with utc - moment.utc = function (input, format, lang, strict) { + moment.utc = function (input, format, locale, strict) { var c; - if (typeof(lang) === "boolean") { - strict = lang; - lang = undefined; + if (typeof(locale) === "boolean") { + strict = locale; + locale = undefined; } // object construction must be done this way. // https://github.com/moment/moment/issues/1423 @@ -1610,7 +1809,7 @@ c._isAMomentObject = true; c._useUTC = true; c._isUTC = true; - c._l = lang; + c._l = locale; c._i = input; c._f = format; c._strict = strict; @@ -1631,7 +1830,8 @@ match = null, sign, ret, - parseIso; + parseIso, + diffRes; if (moment.isDuration(input)) { duration = { @@ -1647,7 +1847,7 @@ duration.milliseconds = input; } } else if (!!(match = aspNetTimeSpanJsonRegex.exec(input))) { - sign = (match[1] === "-") ? -1 : 1; + sign = (match[1] === '-') ? -1 : 1; duration = { y: 0, d: toInt(match[DATE]) * sign, @@ -1657,7 +1857,7 @@ ms: toInt(match[MILLISECOND]) * sign }; } else if (!!(match = isoDurationRegex.exec(input))) { - sign = (match[1] === "-") ? -1 : 1; + sign = (match[1] === '-') ? -1 : 1; parseIso = function (inp) { // We'd normally use ~~inp for this, but unfortunately it also // converts floats to ints. @@ -1675,12 +1875,19 @@ s: parseIso(match[7]), w: parseIso(match[8]) }; + } else if (typeof duration === 'object' && + ('from' in duration || 'to' in duration)) { + diffRes = momentsDifference(moment(duration.from), moment(duration.to)); + + duration = {}; + duration.ms = diffRes.milliseconds; + duration.M = diffRes.months; } ret = new Duration(duration); - if (moment.isDuration(input) && input.hasOwnProperty('_lang')) { - ret._lang = input._lang; + if (moment.isDuration(input) && input.hasOwnProperty('_locale')) { + ret._locale = input._locale; } return ret; @@ -1692,36 +1899,105 @@ // default format moment.defaultFormat = isoFormat; + // constant that refers to the ISO standard + moment.ISO_8601 = function () {}; + + // Plugins that add properties should also add the key here (null value), + // so we can properly clone ourselves. + moment.momentProperties = momentProperties; + // This function will be called whenever a moment is mutated. // It is intended to keep the offset in sync with the timezone. moment.updateOffset = function () {}; - // This function will load languages and then set the global language. If - // no arguments are passed in, it will simply return the current global - // language key. - moment.lang = function (key, values) { - var r; - if (!key) { - return moment.fn._lang._abbr; + // This function allows you to set a threshold for relative time strings + moment.relativeTimeThreshold = function (threshold, limit) { + if (relativeTimeThresholds[threshold] === undefined) { + return false; } - if (values) { - loadLang(normalizeLanguage(key), values); - } else if (values === null) { - unloadLang(key); - key = 'en'; - } else if (!languages[key]) { - getLangDefinition(key); + if (limit === undefined) { + return relativeTimeThresholds[threshold]; } - r = moment.duration.fn._lang = moment.fn._lang = getLangDefinition(key); - return r._abbr; + relativeTimeThresholds[threshold] = limit; + return true; }; - // returns language data - moment.langData = function (key) { - if (key && key._lang && key._lang._abbr) { - key = key._lang._abbr; + moment.lang = deprecate( + "moment.lang is deprecated. Use moment.locale instead.", + function (key, value) { + return moment.locale(key, value); } - return getLangDefinition(key); + ); + + // This function will load locale and then set the global locale. If + // no arguments are passed in, it will simply return the current global + // locale key. + moment.locale = function (key, values) { + var data; + if (key) { + if (typeof(values) !== "undefined") { + data = moment.defineLocale(key, values); + } + else { + data = moment.localeData(key); + } + + if (data) { + moment.duration._locale = moment._locale = data; + } + } + + return moment._locale._abbr; + }; + + moment.defineLocale = function (name, values) { + if (values !== null) { + values.abbr = name; + if (!locales[name]) { + locales[name] = new Locale(); + } + locales[name].set(values); + + // backwards compat for now: also set the locale + moment.locale(name); + + return locales[name]; + } else { + // useful for testing + delete locales[name]; + return null; + } + }; + + moment.langData = deprecate( + "moment.langData is deprecated. Use moment.localeData instead.", + function (key) { + return moment.localeData(key); + } + ); + + // returns locale data + moment.localeData = function (key) { + var locale; + + if (key && key._locale && key._locale._abbr) { + key = key._locale._abbr; + } + + if (!key) { + return moment._locale; + } + + if (!isArray(key)) { + //short-circuit everything else + locale = loadLocale(key); + if (locale) { + return locale; + } + key = [key]; + } + + return chooseLocale(key); }; // compare moment object @@ -1755,8 +2031,12 @@ return m; }; - moment.parseZone = function (input) { - return moment(input).parseZone(); + moment.parseZone = function () { + return moment.apply(null, arguments).parseZone(); + }; + + moment.parseTwoDigitYear = function (input) { + return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); }; /************************************ @@ -1779,7 +2059,7 @@ }, toString : function () { - return this.clone().lang('en').format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ"); + return this.clone().locale('en').format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ"); }, toDate : function () { @@ -1813,7 +2093,6 @@ }, isDSTShifted : function () { - if (this._a) { return this.isValid() && compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()) > 0; } @@ -1829,44 +2108,30 @@ return this._pf.overflow; }, - utc : function () { - return this.zone(0); + utc : function (keepLocalTime) { + return this.zone(0, keepLocalTime); }, - local : function () { - this.zone(0); - this._isUTC = false; + local : function (keepLocalTime) { + if (this._isUTC) { + this.zone(0, keepLocalTime); + this._isUTC = false; + + if (keepLocalTime) { + this.add(this._d.getTimezoneOffset(), 'm'); + } + } return this; }, format : function (inputString) { var output = formatMoment(this, inputString || moment.defaultFormat); - return this.lang().postformat(output); + return this.localeData().postformat(output); }, - add : function (input, val) { - var dur; - // switch args to support add('s', 1) and add(1, 's') - if (typeof input === 'string') { - dur = moment.duration(+val, input); - } else { - dur = moment.duration(input, val); - } - addOrSubtractDurationFromMoment(this, dur, 1); - return this; - }, + add : createAdder(1, 'add'), - subtract : function (input, val) { - var dur; - // switch args to support subtract('s', 1) and subtract(1, 's') - if (typeof input === 'string') { - dur = moment.duration(+val, input); - } else { - dur = moment.duration(input, val); - } - addOrSubtractDurationFromMoment(this, dur, -1); - return this; - }, + subtract : createAdder(-1, 'subtract'), diff : function (input, units, asFloat) { var that = makeAs(input, this), @@ -1903,17 +2168,18 @@ }, from : function (time, withoutSuffix) { - return moment.duration(this.diff(time)).lang(this.lang()._abbr).humanize(!withoutSuffix); + return moment.duration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix); }, fromNow : function (withoutSuffix) { return this.from(moment(), withoutSuffix); }, - calendar : function () { + calendar : function (time) { // We want to compare the start of today, vs this. // Getting start-of-today depends on whether we're zone'd or not. - var sod = makeAs(moment(), this).startOf('day'), + var now = time || moment(), + sod = makeAs(now, this).startOf('day'), diff = this.diff(sod, 'days', true), format = diff < -6 ? 'sameElse' : diff < -1 ? 'lastWeek' : @@ -1921,7 +2187,7 @@ diff < 1 ? 'sameDay' : diff < 2 ? 'nextDay' : diff < 7 ? 'nextWeek' : 'sameElse'; - return this.format(this.lang().calendar(format, this)); + return this.format(this.localeData().calendar(format, this)); }, isLeapYear : function () { @@ -1936,38 +2202,16 @@ day : function (input) { var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); if (input != null) { - input = parseWeekday(input, this.lang()); - return this.add({ d : input - day }); + input = parseWeekday(input, this.localeData()); + return this.add(input - day, 'd'); } else { return day; } }, - month : function (input) { - var utc = this._isUTC ? 'UTC' : '', - dayOfMonth; + month : makeAccessor('Month', true), - if (input != null) { - if (typeof input === 'string') { - input = this.lang().monthsParse(input); - if (typeof input !== 'number') { - return this; - } - } - - dayOfMonth = this.date(); - this.date(1); - this._d['set' + utc + 'Month'](input); - this.date(Math.min(dayOfMonth, this.daysInMonth())); - - moment.updateOffset(this); - return this; - } else { - return this._d['get' + utc + 'Month'](); - } - }, - - startOf: function (units) { + startOf : function (units) { units = normalizeUnits(units); // the following switch intentionally omits break keywords // to utilize falling through the cases. @@ -1975,6 +2219,7 @@ case 'year': this.month(0); /* falls through */ + case 'quarter': case 'month': this.date(1); /* falls through */ @@ -2001,12 +2246,17 @@ this.isoWeekday(1); } + // quarters are also special + if (units === 'quarter') { + this.month(Math.floor(this.month() / 3) * 3); + } + return this; }, endOf: function (units) { units = normalizeUnits(units); - return this.startOf(units).add((units === 'isoWeek' ? 'week' : units), 1).subtract('ms', 1); + return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms'); }, isAfter: function (input, units) { @@ -2024,29 +2274,59 @@ return +this.clone().startOf(units) === +makeAs(input, this).startOf(units); }, - min: function (other) { - other = moment.apply(null, arguments); - return other < this ? this : other; - }, + min: deprecate( + 'moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548', + function (other) { + other = moment.apply(null, arguments); + return other < this ? this : other; + } + ), - max: function (other) { - other = moment.apply(null, arguments); - return other > this ? this : other; - }, + max: deprecate( + 'moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548', + function (other) { + other = moment.apply(null, arguments); + return other > this ? this : other; + } + ), - zone : function (input) { - var offset = this._offset || 0; + // keepLocalTime = true means only change the timezone, without + // affecting the local hour. So 5:31:26 +0300 --[zone(2, true)]--> + // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist int zone + // +0200, so we adjust the time as needed, to be valid. + // + // Keeping the time actually adds/subtracts (one hour) + // from the actual represented time. That is why we call updateOffset + // a second time. In case it wants us to change the offset again + // _changeInProgress == true case, then we have to adjust, because + // there is no such time in the given timezone. + zone : function (input, keepLocalTime) { + var offset = this._offset || 0, + localAdjust; if (input != null) { - if (typeof input === "string") { + if (typeof input === 'string') { input = timezoneMinutesFromString(input); } if (Math.abs(input) < 16) { input = input * 60; } + if (!this._isUTC && keepLocalTime) { + localAdjust = this._d.getTimezoneOffset(); + } this._offset = input; this._isUTC = true; + if (localAdjust != null) { + this.subtract(localAdjust, 'm'); + } if (offset !== input) { - addOrSubtractDurationFromMoment(this, moment.duration(offset - input, 'm'), 1, true); + if (!keepLocalTime || this._changeInProgress) { + addOrSubtractDurationFromMoment(this, + moment.duration(offset - input, 'm'), 1, false); + } else if (!this._changeInProgress) { + this._changeInProgress = true; + moment.updateOffset(this, true); + this._changeInProgress = null; + } } } else { return this._isUTC ? offset : this._d.getTimezoneOffset(); @@ -2055,11 +2335,11 @@ }, zoneAbbr : function () { - return this._isUTC ? "UTC" : ""; + return this._isUTC ? 'UTC' : ''; }, zoneName : function () { - return this._isUTC ? "Coordinated Universal Time" : ""; + return this._isUTC ? 'Coordinated Universal Time' : ''; }, parseZone : function () { @@ -2088,36 +2368,36 @@ dayOfYear : function (input) { var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1; - return input == null ? dayOfYear : this.add("d", (input - dayOfYear)); + return input == null ? dayOfYear : this.add((input - dayOfYear), 'd'); }, - quarter : function () { - return Math.ceil((this.month() + 1.0) / 3.0); + quarter : function (input) { + return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3); }, weekYear : function (input) { - var year = weekOfYear(this, this.lang()._week.dow, this.lang()._week.doy).year; - return input == null ? year : this.add("y", (input - year)); + var year = weekOfYear(this, this.localeData()._week.dow, this.localeData()._week.doy).year; + return input == null ? year : this.add((input - year), 'y'); }, isoWeekYear : function (input) { var year = weekOfYear(this, 1, 4).year; - return input == null ? year : this.add("y", (input - year)); + return input == null ? year : this.add((input - year), 'y'); }, week : function (input) { - var week = this.lang().week(this); - return input == null ? week : this.add("d", (input - week) * 7); + var week = this.localeData().week(this); + return input == null ? week : this.add((input - week) * 7, 'd'); }, isoWeek : function (input) { var week = weekOfYear(this, 1, 4).week; - return input == null ? week : this.add("d", (input - week) * 7); + return input == null ? week : this.add((input - week) * 7, 'd'); }, weekday : function (input) { - var weekday = (this.day() + 7 - this.lang()._week.dow) % 7; - return input == null ? weekday : this.add("d", input - weekday); + var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; + return input == null ? weekday : this.add(input - weekday, 'd'); }, isoWeekday : function (input) { @@ -2127,6 +2407,15 @@ return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7); }, + isoWeeksInYear : function () { + return weeksInYear(this.year(), 1, 4); + }, + + weeksInYear : function () { + var weekInfo = this.localeData()._week; + return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); + }, + get : function (units) { units = normalizeUnits(units); return this[units](); @@ -2140,46 +2429,97 @@ return this; }, - // If passed a language key, it will set the language for this - // instance. Otherwise, it will return the language configuration + // If passed a locale key, it will set the locale for this + // instance. Otherwise, it will return the locale configuration // variables for this instance. - lang : function (key) { + locale : function (key) { if (key === undefined) { - return this._lang; + return this._locale._abbr; } else { - this._lang = getLangDefinition(key); + this._locale = moment.localeData(key); return this; } + }, + + lang : deprecate( + "moment().lang() is deprecated. Use moment().localeData() instead.", + function (key) { + if (key === undefined) { + return this.localeData(); + } else { + this._locale = moment.localeData(key); + return this; + } + } + ), + + localeData : function () { + return this._locale; } }); - // helper for adding shortcuts - function makeGetterAndSetter(name, key) { - moment.fn[name] = moment.fn[name + 's'] = function (input) { - var utc = this._isUTC ? 'UTC' : ''; - if (input != null) { - this._d['set' + utc + key](input); - moment.updateOffset(this); + function rawMonthSetter(mom, value) { + var dayOfMonth; + + // TODO: Move this out of here! + if (typeof value === 'string') { + value = mom.localeData().monthsParse(value); + // TODO: Another silent failure? + if (typeof value !== 'number') { + return mom; + } + } + + dayOfMonth = Math.min(mom.date(), + daysInMonth(mom.year(), value)); + mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); + return mom; + } + + function rawGetter(mom, unit) { + return mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit](); + } + + function rawSetter(mom, unit, value) { + if (unit === 'Month') { + return rawMonthSetter(mom, value); + } else { + return mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); + } + } + + function makeAccessor(unit, keepTime) { + return function (value) { + if (value != null) { + rawSetter(this, unit, value); + moment.updateOffset(this, keepTime); return this; } else { - return this._d['get' + utc + key](); + return rawGetter(this, unit); } }; } - // loop through and add shortcuts (Month, Date, Hours, Minutes, Seconds, Milliseconds) - for (i = 0; i < proxyGettersAndSetters.length; i ++) { - makeGetterAndSetter(proxyGettersAndSetters[i].toLowerCase().replace(/s$/, ''), proxyGettersAndSetters[i]); - } - - // add shortcut for year (uses different syntax than the getter/setter 'year' == 'FullYear') - makeGetterAndSetter('year', 'FullYear'); + moment.fn.millisecond = moment.fn.milliseconds = makeAccessor('Milliseconds', false); + moment.fn.second = moment.fn.seconds = makeAccessor('Seconds', false); + moment.fn.minute = moment.fn.minutes = makeAccessor('Minutes', false); + // Setting the hour should keep the time, because the user explicitly + // specified which hour he wants. So trying to maintain the same hour (in + // a new timezone) makes sense. Adding/subtracting hours does not follow + // this rule. + moment.fn.hour = moment.fn.hours = makeAccessor('Hours', true); + // moment.fn.month is defined separately + moment.fn.date = makeAccessor('Date', true); + moment.fn.dates = deprecate('dates accessor is deprecated. Use date instead.', makeAccessor('Date', true)); + moment.fn.year = makeAccessor('FullYear', true); + moment.fn.years = deprecate('years accessor is deprecated. Use year instead.', makeAccessor('FullYear', true)); // add plural methods moment.fn.days = moment.fn.day; moment.fn.months = moment.fn.month; moment.fn.weeks = moment.fn.week; moment.fn.isoWeeks = moment.fn.isoWeek; + moment.fn.quarters = moment.fn.quarter; // add aliased format methods moment.fn.toJSON = moment.fn.toISOString; @@ -2189,6 +2529,17 @@ ************************************/ + function daysToYears (days) { + // 400 years have 146097 days (taking into account leap year rules) + return days * 400 / 146097; + } + + function yearsToDays (years) { + // years * 365 + absRound(years / 4) - + // absRound(years / 100) + absRound(years / 400); + return years * 146097 / 400; + } + extend(moment.duration.fn = Duration.prototype, { _bubble : function () { @@ -2196,7 +2547,7 @@ days = this._days, months = this._months, data = this._data, - seconds, minutes, hours, years; + seconds, minutes, hours, years = 0; // The following code bubbles up values, see the tests for // examples of what that means. @@ -2212,15 +2563,40 @@ data.hours = hours % 24; days += absRound(hours / 24); - data.days = days % 30; + // Accurately convert days to years, assume start from year 0. + years = absRound(daysToYears(days)); + days -= absRound(yearsToDays(years)); + + // 30 days to a month + // TODO (iskren): Use anchor date (like 1st Jan) to compute this. months += absRound(days / 30); - data.months = months % 12; + days %= 30; - years = absRound(months / 12); + // 12 months -> 1 year + years += absRound(months / 12); + months %= 12; + + data.days = days; + data.months = months; data.years = years; }, + abs : function () { + this._milliseconds = Math.abs(this._milliseconds); + this._days = Math.abs(this._days); + this._months = Math.abs(this._months); + + this._data.milliseconds = Math.abs(this._data.milliseconds); + this._data.seconds = Math.abs(this._data.seconds); + this._data.minutes = Math.abs(this._data.minutes); + this._data.hours = Math.abs(this._data.hours); + this._data.months = Math.abs(this._data.months); + this._data.years = Math.abs(this._data.years); + + return this; + }, + weeks : function () { return absRound(this.days() / 7); }, @@ -2233,14 +2609,13 @@ }, humanize : function (withSuffix) { - var difference = +this, - output = relativeTime(difference, !withSuffix, this.lang()); + var output = relativeTime(this, !withSuffix, this.localeData()); if (withSuffix) { - output = this.lang().pastFuture(difference, output); + output = this.localeData().pastFuture(+this, output); } - return this.lang().postformat(output); + return this.localeData().postformat(output); }, add : function (input, val) { @@ -2274,13 +2649,39 @@ }, as : function (units) { + var days, months; units = normalizeUnits(units); - return this['as' + units.charAt(0).toUpperCase() + units.slice(1) + 's'](); + + days = this._days + this._milliseconds / 864e5; + if (units === 'month' || units === 'year') { + months = this._months + daysToYears(days) * 12; + return units === 'month' ? months : months / 12; + } else { + days += yearsToDays(this._months / 12); + switch (units) { + case 'week': return days / 7; + case 'day': return days; + case 'hour': return days * 24; + case 'minute': return days * 24 * 60; + case 'second': return days * 24 * 60 * 60; + case 'millisecond': return days * 24 * 60 * 60 * 1000; + default: throw new Error('Unknown unit ' + units); + } + } }, lang : moment.fn.lang, + locale : moment.fn.locale, - toIsoString : function () { + toIsoString : deprecate( + "toIsoString() is deprecated. Please use toISOString() instead " + + "(notice the capitals)", + function () { + return this.toISOString(); + } + ), + + toISOString : function () { // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js var years = Math.abs(this.years()), months = Math.abs(this.months()), @@ -2304,6 +2705,10 @@ (hours ? hours + 'H' : '') + (minutes ? minutes + 'M' : '') + (seconds ? seconds + 'S' : ''); + }, + + localeData : function () { + return this._locale; } }); @@ -2313,32 +2718,44 @@ }; } - function makeDurationAsGetter(name, factor) { - moment.duration.fn['as' + name] = function () { - return +this / factor; - }; - } - for (i in unitMillisecondFactors) { if (unitMillisecondFactors.hasOwnProperty(i)) { - makeDurationAsGetter(i, unitMillisecondFactors[i]); makeDurationGetter(i.toLowerCase()); } } - makeDurationAsGetter('Weeks', 6048e5); + moment.duration.fn.asMilliseconds = function () { + return this.as('ms'); + }; + moment.duration.fn.asSeconds = function () { + return this.as('s'); + }; + moment.duration.fn.asMinutes = function () { + return this.as('m'); + }; + moment.duration.fn.asHours = function () { + return this.as('h'); + }; + moment.duration.fn.asDays = function () { + return this.as('d'); + }; + moment.duration.fn.asWeeks = function () { + return this.as('weeks'); + }; moment.duration.fn.asMonths = function () { - return (+this - this.years() * 31536e6) / 2592e6 + this.years() * 12; + return this.as('M'); + }; + moment.duration.fn.asYears = function () { + return this.as('y'); }; - /************************************ - Default Lang + Default Locale ************************************/ - // Set default language, other languages will inherit from English. - moment.lang('en', { + // Set default locale, other locale will inherit from English. + moment.locale('en', { ordinal : function (number) { var b = number % 10, output = (toInt(number % 100 / 10) === 1) ? 'th' : @@ -2349,52 +2766,43 @@ } }); - /* EMBED_LANGUAGES */ + /* EMBED_LOCALES */ /************************************ Exposing Moment ************************************/ - function makeGlobal(deprecate) { - var warned = false, local_moment = moment; + function makeGlobal(shouldDeprecate) { /*global ender:false */ if (typeof ender !== 'undefined') { return; } - // here, `this` means `window` in the browser, or `global` on the server - // add `moment` as a global object via a string identifier, - // for Closure Compiler "advanced" mode - if (deprecate) { - global.moment = function () { - if (!warned && console && console.warn) { - warned = true; - console.warn( - "Accessing Moment through the global scope is " + - "deprecated, and will be removed in an upcoming " + - "release."); - } - return local_moment.apply(null, arguments); - }; - extend(global.moment, local_moment); + oldGlobalMoment = globalScope.moment; + if (shouldDeprecate) { + globalScope.moment = deprecate( + 'Accessing Moment through the global scope is ' + + 'deprecated, and will be removed in an upcoming ' + + 'release.', + moment); } else { - global['moment'] = moment; + globalScope.moment = moment; } } // CommonJS module is defined if (hasModule) { module.exports = moment; - makeGlobal(true); - } else if (typeof define === "function" && define.amd) { - define("moment", function (require, exports, module) { - if (module.config && module.config() && module.config().noGlobal !== true) { - // If user provided noGlobal, he is aware of global - makeGlobal(module.config().noGlobal === undefined); + } else if (typeof define === 'function' && define.amd) { + define('moment', function (require, exports, module) { + if (module.config && module.config() && module.config().noGlobal === true) { + // release the global variable + globalScope.moment = oldGlobalMoment; } return moment; }); + makeGlobal(true); } else { makeGlobal(); } -}).call(this); \ No newline at end of file +}).call(this); From c6489d9b0112b5a1268b9b8da645f157fae1a85d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 19 Aug 2014 10:46:08 +0200 Subject: [PATCH 011/146] Lots of progress on per series overrides --- package.json | 2 +- src/app/directives/grafanaGraph.js | 2 +- src/app/panels/graph/module.html | 2 +- src/app/panels/graph/module.js | 63 ++----------------- src/app/panels/graph/seriesOverridesCtrl.js | 66 ++++++++++++++++++++ src/app/panels/graph/styleEditor.html | 67 +++++++++++--------- src/css/less/graph.less | 14 +++++ src/test/specs/grafanaGraph-specs.js | 68 +++++++++++++++++++++ src/test/specs/helpers.js | 2 +- src/test/specs/seriesOverridesCtrl-specs.js | 40 ++++++++++++ src/test/test-main.js | 2 + 11 files changed, 237 insertions(+), 91 deletions(-) create mode 100644 src/app/panels/graph/seriesOverridesCtrl.js create mode 100644 src/test/specs/grafanaGraph-specs.js create mode 100644 src/test/specs/seriesOverridesCtrl-specs.js diff --git a/package.json b/package.json index 31c552fe6c2..eecf826776e 100644 --- a/package.json +++ b/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", diff --git a/src/app/directives/grafanaGraph.js b/src/app/directives/grafanaGraph.js index f0ffd14dd67..c2be3d90d13 100755 --- a/src/app/directives/grafanaGraph.js +++ b/src/app/directives/grafanaGraph.js @@ -156,7 +156,7 @@ 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; - data[0].lines = { show: false }; + data[0].lines = { show: false, dashes: true }; data[0].bars = { show: true }; } diff --git a/src/app/panels/graph/module.html b/src/app/panels/graph/module.html index 102cad84a40..0d418e5e8cd 100644 --- a/src/app/panels/graph/module.html +++ b/src/app/panels/graph/module.html @@ -27,7 +27,7 @@ -
+
diff --git a/src/app/panels/graph/module.js b/src/app/panels/graph/module.js index d8a31f59312..557503023a1 100644 --- a/src/app/panels/graph/module.js +++ b/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', @@ -19,6 +6,7 @@ define([ 'kbn', 'moment', './timeSeries', + './seriesOverridesCtrl', 'services/panelSrv', 'services/annotationsSrv', 'services/datasourceSrv', @@ -30,10 +18,9 @@ define([ 'jquery.flot.stackpercent' ], 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) { @@ -363,49 +350,11 @@ function (angular, app, $, _, kbn, moment, timeSeries) { $scope.panel.seriesOverrides.push({}); }; + $scope.removeSeriesOverride = function(override) { + $scope.panel.seriesOverrides = _.without($scope.panel.seriesOverrides, override); + }; + panelSrv.init($scope); }); - angular - .module('grafana.directives') - .directive('seriesOverrideOption', function($compile) { - var template = - ''; - - return { - scope: true, - link: function($scope, elem, attrs) { - var $template = $(template); - elem.append($template); - var $link = $(elem).find('a'); - - $scope.options = $scope.$eval(attrs.options); - $scope.options.unshift(null); - - $scope.options = _.map($scope.options, function(option, index) { - return { - text: option === null ? '' : String(option), - value: option, - click: 'setValue(' + index + ')' - }; - }); - - $scope.setValue = function(index) { - var value = $scope.options[index].value; - if (value === null) { - $link.html(''); - } - else { - $link.html(value); - } - }; - - $compile(elem.contents())($scope); - } - }; - }); - }); diff --git a/src/app/panels/graph/seriesOverridesCtrl.js b/src/app/panels/graph/seriesOverridesCtrl.js new file mode 100644 index 00000000000..2123510400b --- /dev/null +++ b/src/app/panels/graph/seriesOverridesCtrl.js @@ -0,0 +1,66 @@ +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.removeOverride = function(option) { + delete $scope.override[option.propertyName]; + $scope.updateCurrentOverrides(); + }; + + $scope.updateCurrentOverrides = function() { + $scope.currentOverrides = []; + _.each($scope.overrideMenu, function(option) { + if (!_.isUndefined($scope.override[option.propertyName])) { + $scope.currentOverrides.push({ + name: option.text, + propertyName: option.propertyName, + value: String($scope.override[option.propertyName]) + }); + } + }); + }; + + $scope.addOverrideOption('Bars', 'bars', [true, false]); + $scope.addOverrideOption('Lines', 'lines', [true, false]); + $scope.addOverrideOption('Points', 'points', [true, false]); + $scope.addOverrideOption('Line fill', 'fill', [1,2,3,4,5,6,7,8]); + $scope.updateCurrentOverrides(); + + }); + +}); diff --git a/src/app/panels/graph/styleEditor.html b/src/app/panels/graph/styleEditor.html index c1b68e25beb..c436ba10ceb 100644 --- a/src/app/panels/graph/styleEditor.html +++ b/src/app/panels/graph/styleEditor.html @@ -66,36 +66,43 @@
Series specific overrides
- - - - - - - - - - - - - - - - - - - - -
Series alias/regexBarsLinesPointsFillWidthRadiusStaircaseStackY-axisZ-indexColor
- - - - - - - -
- +
+
+
+ + + +
    +
  • + alias or regex +
  • +
  • + +
  • +
  • + + + + {{option.name}}: {{option.value}} +
  • + +
+
+
+
+
+ +
diff --git a/src/css/less/graph.less b/src/css/less/graph.less index 79e87ab8f47..8f049a44ae5 100644 --- a/src/css/less/graph.less +++ b/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; + } +} diff --git a/src/test/specs/grafanaGraph-specs.js b/src/test/specs/grafanaGraph-specs.js new file mode 100644 index 00000000000..f8b5878ce66 --- /dev/null +++ b/src/test/specs/grafanaGraph-specs.js @@ -0,0 +1,68 @@ +define([ + './helpers', + 'angular', + 'jquery', + 'directives/grafanaGraph' +], function(helpers, angular, $) { + '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("
"); + + scope.height = '200px'; + scope.panel = { + legend: {}, + grid: {}, + y_formats: [] + }; + scope.dashboard = { timezone: 'browser' }; + scope.range = { + from: new Date('2014-08-09 10:00:00'), + to: new Date('2014-09-09 13:00:00') + }; + + setupFunc(scope); + + $compile(element)(scope); + scope.$digest(); + $.plot = ctx.plotSpy = sinon.spy(); + + scope.$emit('render', []); + 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); + }); + + }); + + }); +}); + diff --git a/src/test/specs/helpers.js b/src/test/specs/helpers.js index cd31779cc99..de2be527f9f 100644 --- a/src/test/specs/helpers.js +++ b/src/test/specs/helpers.js @@ -69,7 +69,7 @@ define([ } return { from : kbn.parseDate(this.time.from), - to : kbn.parseDate(this.time.to) + to : kbn.parseDate(this.time.to) }; }; diff --git a/src/test/specs/seriesOverridesCtrl-specs.js b/src/test/specs/seriesOverridesCtrl-specs.js new file mode 100644 index 00000000000..04f0dcdca99 --- /dev/null +++ b/src/test/specs/seriesOverridesCtrl-specs.js @@ -0,0 +1,40 @@ +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')); + + 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'); + }); + }); + + }); + +}); + diff --git a/src/test/test-main.js b/src/test/test-main.js index 9c7cfe73341..8d2014b0664 100644 --- a/src/test/test-main.js +++ b/src/test/test-main.js @@ -122,6 +122,8 @@ require([ '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', From 048763053c534bc9d2bf58c4e3be40bd2c624862 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 19 Aug 2014 16:22:18 +0200 Subject: [PATCH 012/146] Began work on applying per series options to flot options --- .../graph => components}/timeSeries.js | 13 ++++--- src/app/directives/grafanaGraph.js | 20 ++++++++--- src/app/panels/graph/module.js | 6 ++-- src/app/panels/graph/seriesOverridesCtrl.js | 1 + src/app/panels/graph/styleEditor.html | 2 +- src/test/specs/grafanaGraph-specs.js | 36 ++++++++++++++++--- src/test/specs/seriesOverridesCtrl-specs.js | 3 ++ 7 files changed, 62 insertions(+), 19 deletions(-) rename src/app/{panels/graph => components}/timeSeries.js (92%) diff --git a/src/app/panels/graph/timeSeries.js b/src/app/components/timeSeries.js similarity index 92% rename from src/app/panels/graph/timeSeries.js rename to src/app/components/timeSeries.js index f1794cd6a30..8858b01a139 100644 --- a/src/app/panels/graph/timeSeries.js +++ b/src/app/components/timeSeries.js @@ -5,15 +5,13 @@ 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; - }; + } - ts.ZeroFilled.prototype.getFlotPairs = function (fillStyle, yFormats) { + TimeSeries.prototype.getFlotPairs = function (fillStyle, yFormats) { var result = []; this.color = this.info.color; @@ -74,5 +72,6 @@ function (_, kbn) { return result; }; - return ts; -}); \ No newline at end of file + return TimeSeries; + +}); diff --git a/src/app/directives/grafanaGraph.js b/src/app/directives/grafanaGraph.js index c2be3d90d13..21d47bcdf63 100755 --- a/src/app/directives/grafanaGraph.js +++ b/src/app/directives/grafanaGraph.js @@ -154,10 +154,9 @@ 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; - data[0].lines = { show: false, dashes: true }; - data[0].bars = { show: true }; + var series = data[i]; + series.data = series.getFlotPairs(panel.nullPointMode, panel.y_formats); + applySeriesOverrideOptions(series); } if (data.length && data[0].info.timeStep) { @@ -184,6 +183,19 @@ function (angular, $, kbn, moment, _) { } } + function applySeriesOverrideOptions(series) { + for (var i = 0; i < scope.panel.seriesOverrides.length; i++) { + var override = scope.panel.seriesOverrides[i]; + if (override.alias === series.info.alias) { + if (!_.isUndefined(override.fill)) { + series.lines = { + fill: override.fill === 0 ? 0.001 : override.fill/10 + }; + } + } + } + } + function shouldDelayDraw(panel) { if (panel.legend.rightSide) { return true; diff --git a/src/app/panels/graph/module.js b/src/app/panels/graph/module.js index 557503023a1..9d2253b0147 100644 --- a/src/app/panels/graph/module.js +++ b/src/app/panels/graph/module.js @@ -5,7 +5,7 @@ define([ 'lodash', 'kbn', 'moment', - './timeSeries', + 'components/timeSeries', './seriesOverridesCtrl', 'services/panelSrv', 'services/annotationsSrv', @@ -17,7 +17,7 @@ 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'); @@ -258,7 +258,7 @@ function (angular, app, $, _, kbn, moment, timeSeries) { $scope.legend.push(seriesInfo); - var series = new timeSeries.ZeroFilled({ + var series = new TimeSeries({ datapoints: datapoints, info: seriesInfo, }); diff --git a/src/app/panels/graph/seriesOverridesCtrl.js b/src/app/panels/graph/seriesOverridesCtrl.js index 2123510400b..ed2e27fdf7c 100644 --- a/src/app/panels/graph/seriesOverridesCtrl.js +++ b/src/app/panels/graph/seriesOverridesCtrl.js @@ -35,6 +35,7 @@ define([ var value = option.values[valueIndex]; $scope.override[option.propertyName] = value; $scope.updateCurrentOverrides(); + $scope.render(); }; $scope.removeOverride = function(option) { diff --git a/src/app/panels/graph/styleEditor.html b/src/app/panels/graph/styleEditor.html index c436ba10ceb..9b0d2b4fbba 100644 --- a/src/app/panels/graph/styleEditor.html +++ b/src/app/panels/graph/styleEditor.html @@ -84,7 +84,7 @@ alias or regex
  • - +
  • diff --git a/src/test/specs/grafanaGraph-specs.js b/src/test/specs/grafanaGraph-specs.js index f8b5878ce66..63f5055f238 100644 --- a/src/test/specs/grafanaGraph-specs.js +++ b/src/test/specs/grafanaGraph-specs.js @@ -2,8 +2,9 @@ define([ './helpers', 'angular', 'jquery', + 'components/timeSeries', 'directives/grafanaGraph' -], function(helpers, angular, $) { +], function(helpers, angular, $, TimeSeries) { 'use strict'; describe('grafanaGraph', function() { @@ -22,21 +23,31 @@ define([ scope.panel = { legend: {}, grid: {}, - y_formats: [] + 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); + setupFunc(scope, ctx.data); $compile(element)(scope); scope.$digest(); $.plot = ctx.plotSpy = sinon.spy(); - scope.$emit('render', []); + scope.$emit('render', ctx.data); ctx.plotData = ctx.plotSpy.getCall(0).args[1]; ctx.plotOptions = ctx.plotSpy.getCall(0).args[2]; })); @@ -63,6 +74,23 @@ define([ }); + graphScenario('series option fill override', function(ctx) { + ctx.setup(function(scope, data) { + scope.panel.lines = true; + scope.panel.fill = 5; + scope.panel.seriesOverrides = [ + { alias: 'test', fill: 0 } + ]; + + data[1].info.alias = 'test'; + }); + + it('should match second series and set line fill', function() { + expect(ctx.plotOptions.series.lines.fill).to.be(0.5); + expect(ctx.plotData[1].lines.fill).to.be(0.001); + }); + + }); }); }); diff --git a/src/test/specs/seriesOverridesCtrl-specs.js b/src/test/specs/seriesOverridesCtrl-specs.js index 04f0dcdca99..e029c8a30cd 100644 --- a/src/test/specs/seriesOverridesCtrl-specs.js +++ b/src/test/specs/seriesOverridesCtrl-specs.js @@ -12,6 +12,9 @@ define([ 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() { From cdcbb872d58fba33d5ccad07c9867c3898e841b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 19 Aug 2014 16:57:33 +0200 Subject: [PATCH 013/146] options per series is starting to work nicely --- src/app/directives/grafanaGraph.js | 20 +++++++++++------ src/app/panels/graph/module.js | 1 + src/app/panels/graph/seriesOverridesCtrl.js | 4 +++- src/app/panels/graph/styleEditor.html | 5 ++++- src/test/specs/grafanaGraph-specs.js | 24 ++++++++++++++++++--- 5 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/app/directives/grafanaGraph.js b/src/app/directives/grafanaGraph.js index 21d47bcdf63..e4f527bb2f3 100755 --- a/src/app/directives/grafanaGraph.js +++ b/src/app/directives/grafanaGraph.js @@ -184,18 +184,26 @@ function (angular, $, kbn, moment, _) { } function applySeriesOverrideOptions(series) { + series.lines = {}; + series.points = {}; + series.bars = {}; + for (var i = 0; i < scope.panel.seriesOverrides.length; i++) { var override = scope.panel.seriesOverrides[i]; - if (override.alias === series.info.alias) { - if (!_.isUndefined(override.fill)) { - series.lines = { - fill: override.fill === 0 ? 0.001 : override.fill/10 - }; - } + if (override.alias !== series.info.alias) { + continue; } + if (override.lines !== void 0) { series.lines.show = override.lines; } + if (override.points !== void 0) { series.points.show = override.points; } + if (override.bars !== void 0) { series.bars.show = override.bars; } + if (override.fill !== void 0) { series.lines.fill = translateFillOption(override.fill); } } } + function translateFillOption(fill) { + return fill === 0 ? 0.001 : fill/10; + } + function shouldDelayDraw(panel) { if (panel.legend.rightSide) { return true; diff --git a/src/app/panels/graph/module.js b/src/app/panels/graph/module.js index 9d2253b0147..b0de8f190c7 100644 --- a/src/app/panels/graph/module.js +++ b/src/app/panels/graph/module.js @@ -352,6 +352,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries) { $scope.removeSeriesOverride = function(override) { $scope.panel.seriesOverrides = _.without($scope.panel.seriesOverrides, override); + $scope.render(); }; panelSrv.init($scope); diff --git a/src/app/panels/graph/seriesOverridesCtrl.js b/src/app/panels/graph/seriesOverridesCtrl.js index ed2e27fdf7c..9f42f3ef75d 100644 --- a/src/app/panels/graph/seriesOverridesCtrl.js +++ b/src/app/panels/graph/seriesOverridesCtrl.js @@ -41,6 +41,7 @@ define([ $scope.removeOverride = function(option) { delete $scope.override[option.propertyName]; $scope.updateCurrentOverrides(); + $scope.render(); }; $scope.updateCurrentOverrides = function() { @@ -59,7 +60,8 @@ define([ $scope.addOverrideOption('Bars', 'bars', [true, false]); $scope.addOverrideOption('Lines', 'lines', [true, false]); $scope.addOverrideOption('Points', 'points', [true, false]); - $scope.addOverrideOption('Line fill', 'fill', [1,2,3,4,5,6,7,8]); + $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.updateCurrentOverrides(); }); diff --git a/src/app/panels/graph/styleEditor.html b/src/app/panels/graph/styleEditor.html index 9b0d2b4fbba..ca7ec99180d 100644 --- a/src/app/panels/graph/styleEditor.html +++ b/src/app/panels/graph/styleEditor.html @@ -84,7 +84,10 @@ alias or regex
  • - +
  • diff --git a/src/test/specs/grafanaGraph-specs.js b/src/test/specs/grafanaGraph-specs.js index 63f5055f238..b070c03ec44 100644 --- a/src/test/specs/grafanaGraph-specs.js +++ b/src/test/specs/grafanaGraph-specs.js @@ -74,23 +74,41 @@ define([ }); - graphScenario('series option fill override', function(ctx) { + 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 } + { alias: 'test', fill: 0, points: true } ]; data[1].info.alias = 'test'; }); - it('should match second series and set line fill', function() { + 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('series option overrides, bars, true & lines false', function(ctx) { + ctx.setup(function(scope, data) { + scope.panel.lines = true; + scope.panel.seriesOverrides = [ + { alias: 'test', bars: true, lines: false } + ]; + + data[1].info.alias = 'test'; }); + it('should match second series and disable lines, and enable bars', function() { + expect(ctx.plotOptions.series.lines.show).to.be(true); + expect(ctx.plotData[1].lines.show).to.be(false); + expect(ctx.plotData[1].bars.show).to.be(true); + }); }); + }); }); From 062fe72030ebf466cec4d06cbb1d131505f50666 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 19 Aug 2014 17:24:37 +0200 Subject: [PATCH 014/146] More options can now be set on pre series basis, this is awesome! --- src/app/directives/grafanaGraph.js | 5 +++ src/app/panels/graph/seriesOverridesCtrl.js | 5 ++- src/test/specs/grafanaGraph-specs.js | 36 +++++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/app/directives/grafanaGraph.js b/src/app/directives/grafanaGraph.js index e4f527bb2f3..f624c90494d 100755 --- a/src/app/directives/grafanaGraph.js +++ b/src/app/directives/grafanaGraph.js @@ -187,6 +187,7 @@ function (angular, $, kbn, moment, _) { series.lines = {}; series.points = {}; series.bars = {}; + delete series.stack; for (var i = 0; i < scope.panel.seriesOverrides.length; i++) { var override = scope.panel.seriesOverrides[i]; @@ -197,6 +198,10 @@ function (angular, $, kbn, moment, _) { if (override.points !== void 0) { series.points.show = override.points; } if (override.bars !== void 0) { series.bars.show = override.bars; } if (override.fill !== void 0) { series.lines.fill = translateFillOption(override.fill); } + if (override.stack !== void 0) { series.stack = override.stack; } + if (override.linewidth !== void 0) { series.lines.lineWidth = override.linewidth; } + if (override.pointradius !== void 0) { series.points.radius = override.pointradius; } + if (override.steppedLine !== void 0) { series.lines.steps = override.steppedLine; } } } diff --git a/src/app/panels/graph/seriesOverridesCtrl.js b/src/app/panels/graph/seriesOverridesCtrl.js index 9f42f3ef75d..d2f083b2699 100644 --- a/src/app/panels/graph/seriesOverridesCtrl.js +++ b/src/app/panels/graph/seriesOverridesCtrl.js @@ -59,9 +59,12 @@ define([ $scope.addOverrideOption('Bars', 'bars', [true, false]); $scope.addOverrideOption('Lines', 'lines', [true, false]); - $scope.addOverrideOption('Points', 'points', [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.updateCurrentOverrides(); }); diff --git a/src/test/specs/grafanaGraph-specs.js b/src/test/specs/grafanaGraph-specs.js index b070c03ec44..162ad278ce3 100644 --- a/src/test/specs/grafanaGraph-specs.js +++ b/src/test/specs/grafanaGraph-specs.js @@ -109,6 +109,42 @@ define([ }); }); + graphScenario('series option overrides, linewidth, stack', function(ctx) { + ctx.setup(function(scope, data) { + scope.panel.lines = true; + scope.panel.stack = true; + scope.panel.linewidth = 2; + scope.panel.seriesOverrides = [ + { alias: 'test', linewidth: 5, stack: false } + ]; + + data[1].info.alias = 'test'; + }); + + it('should match second series and disable stack, and set lineWidth', function() { + expect(ctx.plotOptions.series.stack).to.be(true); + expect(ctx.plotData[1].stack).to.be(false); + expect(ctx.plotData[1].lines.lineWidth).to.be(5); + }); + }); + + graphScenario('series option overrides, pointradius, steppedLine', function(ctx) { + ctx.setup(function(scope, data) { + scope.panel.seriesOverrides = [ + { alias: 'test', pointradius: 5, steppedLine: true } + ]; + + data[1].info.alias = 'test'; + }); + + it('should match second series and set pointradius, and set steppedLine', function() { + expect(ctx.plotData[1].points.radius).to.be(5); + expect(ctx.plotData[1].lines.steps).to.be(true); + }); + }); + + + }); }); From 939e957fda02d6794307fabd6811c13fb74b2d59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 20 Aug 2014 08:35:17 +0200 Subject: [PATCH 015/146] Made regex match work for per series overrides, #425, #700 --- src/app/directives/grafanaGraph.js | 12 +++++++++++- src/app/panels/graph/seriesOverridesCtrl.js | 14 +++++++------- src/app/panels/graph/styleEditor.html | 2 +- src/test/specs/grafanaGraph-specs.js | 14 ++++++++++++++ src/test/specs/seriesOverridesCtrl-specs.js | 8 ++++++++ 5 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/app/directives/grafanaGraph.js b/src/app/directives/grafanaGraph.js index f624c90494d..1affce16e5c 100755 --- a/src/app/directives/grafanaGraph.js +++ b/src/app/directives/grafanaGraph.js @@ -183,6 +183,16 @@ function (angular, $, kbn, moment, _) { } } + function matchSeriesOverride(aliasOrRegex, seriesAlias) { + 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 applySeriesOverrideOptions(series) { series.lines = {}; series.points = {}; @@ -191,7 +201,7 @@ function (angular, $, kbn, moment, _) { for (var i = 0; i < scope.panel.seriesOverrides.length; i++) { var override = scope.panel.seriesOverrides[i]; - if (override.alias !== series.info.alias) { + if (!matchSeriesOverride(override.alias, series.info.alias)) { continue; } if (override.lines !== void 0) { series.lines.show = override.lines; } diff --git a/src/app/panels/graph/seriesOverridesCtrl.js b/src/app/panels/graph/seriesOverridesCtrl.js index d2f083b2699..0d7f3b4838d 100644 --- a/src/app/panels/graph/seriesOverridesCtrl.js +++ b/src/app/panels/graph/seriesOverridesCtrl.js @@ -47,13 +47,13 @@ define([ $scope.updateCurrentOverrides = function() { $scope.currentOverrides = []; _.each($scope.overrideMenu, function(option) { - if (!_.isUndefined($scope.override[option.propertyName])) { - $scope.currentOverrides.push({ - name: option.text, - propertyName: option.propertyName, - value: String($scope.override[option.propertyName]) - }); - } + var value = $scope.override[option.propertyName]; + if (_.isUndefined(value)) { return; } + $scope.currentOverrides.push({ + name: option.text, + propertyName: option.propertyName, + value: String(value) + }); }); }; diff --git a/src/app/panels/graph/styleEditor.html b/src/app/panels/graph/styleEditor.html index ca7ec99180d..5a50f4995d0 100644 --- a/src/app/panels/graph/styleEditor.html +++ b/src/app/panels/graph/styleEditor.html @@ -65,7 +65,7 @@
    -
    Series specific overrides
    +
    Series specific overrides Regex match example: /server[0-3]/i
    diff --git a/src/test/specs/grafanaGraph-specs.js b/src/test/specs/grafanaGraph-specs.js index 162ad278ce3..6d83467daa7 100644 --- a/src/test/specs/grafanaGraph-specs.js +++ b/src/test/specs/grafanaGraph-specs.js @@ -143,7 +143,21 @@ define([ }); }); + graphScenario('override match on regex', function(ctx) { + ctx.setup(function(scope, data) { + scope.panel.lines = true; + scope.panel.seriesOverrides = [ + { alias: '/.*01/', lines: false } + ]; + data[1].info.alias = 'test_01'; + }); + + it('should match second series and set pointradius, and set steppedLine', function() { + expect(ctx.plotData[0].lines.show).to.be(undefined); + expect(ctx.plotData[1].lines.show).to.be(false); + }); + }); }); }); diff --git a/src/test/specs/seriesOverridesCtrl-specs.js b/src/test/specs/seriesOverridesCtrl-specs.js index e029c8a30cd..e211b6dab35 100644 --- a/src/test/specs/seriesOverridesCtrl-specs.js +++ b/src/test/specs/seriesOverridesCtrl-specs.js @@ -37,6 +37,14 @@ define([ }); }); + 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); + }); + }); + }); }); From b2f9f81eaf0dd812c3a4decd6bcb0b7cae995c68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 20 Aug 2014 09:31:22 +0200 Subject: [PATCH 016/146] Moved yaxis override from aliasYAxis map to the new seriesOverride array --- src/app/directives/grafanaGraph.js | 4 + src/app/panels/graph/module.js | 12 +- src/app/panels/graph/seriesOverridesCtrl.js | 1 + src/app/services/dashboard/dashboardSrv.js | 125 +++++++++++--------- src/test/specs/dashboardSrv-specs.js | 8 +- src/test/specs/grafanaGraph-specs.js | 17 ++- 6 files changed, 105 insertions(+), 62 deletions(-) diff --git a/src/app/directives/grafanaGraph.js b/src/app/directives/grafanaGraph.js index 1affce16e5c..6004b6e366c 100755 --- a/src/app/directives/grafanaGraph.js +++ b/src/app/directives/grafanaGraph.js @@ -212,6 +212,10 @@ function (angular, $, kbn, moment, _) { if (override.linewidth !== void 0) { series.lines.lineWidth = override.linewidth; } if (override.pointradius !== void 0) { series.points.radius = override.pointradius; } if (override.steppedLine !== void 0) { series.lines.steps = override.steppedLine; } + if (override.yaxis !== void 0) { + series.yaxis = override.yaxis; + series.info.yaxis = override.yaxis; + } } } diff --git a/src/app/panels/graph/module.js b/src/app/panels/graph/module.js index b0de8f190c7..6897cc090b0 100644 --- a/src/app/panels/graph/module.js +++ b/src/app/panels/graph/module.js @@ -166,7 +166,6 @@ function (angular, app, $, _, kbn, moment, TimeSeries) { targets: [{}], aliasColors: {}, - aliasYAxis: {}, seriesOverrides: [], }; @@ -247,13 +246,10 @@ 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); @@ -336,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(); }; diff --git a/src/app/panels/graph/seriesOverridesCtrl.js b/src/app/panels/graph/seriesOverridesCtrl.js index 0d7f3b4838d..4616e6ac1f8 100644 --- a/src/app/panels/graph/seriesOverridesCtrl.js +++ b/src/app/panels/graph/seriesOverridesCtrl.js @@ -65,6 +65,7 @@ define([ $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.updateCurrentOverrides(); }); diff --git a/src/app/services/dashboard/dashboardSrv.js b/src/app/services/dashboard/dashboardSrv.js index ff1e149c3b7..f2d67bf1839 100644 --- a/src/app/services/dashboard/dashboardSrv.js +++ b/src/app/services/dashboard/dashboardSrv.js @@ -144,80 +144,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 (old.services) { + if (old.services.filter) { + this.time = old.services.filter.time; + this.templating.list = old.services.filter.list; } + delete this.services; } - } - 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; - } - 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 (_.isBoolean(panel.legend)) { panel.legend = { show: panel.legend }; } + + if (panel.grid) { + if (panel.grid.min) { + panel.grid.leftMin = panel.grid.min; + delete panel.grid.min; } - if (panel.grid) { - if (panel.grid.min) { - panel.grid.leftMin = panel.grid.min; - delete panel.grid.min; - } - - if (panel.grid.max) { - panel.grid.leftMax = panel.grid.max; - delete panel.grid.max; - } + if (panel.grid.max) { + panel.grid.leftMax = panel.grid.max; + delete panel.grid.max; } + } - if (panel.y_format) { - panel.y_formats[0] = panel.y_format; - delete panel.y_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; - } + 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; + } + + 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]); } } } - - this.version = 3; }; return { diff --git a/src/test/specs/dashboardSrv-specs.js b/src/test/specs/dashboardSrv-specs.js index 5e4e96401de..a2c7c1772f6 100644 --- a/src/test/specs/dashboardSrv-specs.js +++ b/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); }); }); diff --git a/src/test/specs/grafanaGraph-specs.js b/src/test/specs/grafanaGraph-specs.js index 6d83467daa7..f5ad344c1c5 100644 --- a/src/test/specs/grafanaGraph-specs.js +++ b/src/test/specs/grafanaGraph-specs.js @@ -153,12 +153,27 @@ define([ data[1].info.alias = 'test_01'; }); - it('should match second series and set pointradius, and set steppedLine', function() { + it('should match second series', function() { expect(ctx.plotData[0].lines.show).to.be(undefined); expect(ctx.plotData[1].lines.show).to.be(false); }); }); + graphScenario('override series y-axis', function(ctx) { + ctx.setup(function(scope, data) { + scope.panel.lines = true; + scope.panel.seriesOverrides = [ + { alias: 'test', yaxis: 2 } + ]; + + data[1].info.alias = 'test'; + }); + + it('should match second series and set yaxis', function() { + expect(ctx.plotData[1].yaxis).to.be(2); + }); + }); + }); }); From 3ec053bea70f99d2f125de1063a8f391cb35fd57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 20 Aug 2014 10:27:30 +0200 Subject: [PATCH 017/146] Moved series override code to TimeSeries --- src/app/components/timeSeries.js | 40 ++++++++++ src/app/directives/grafanaGraph.js | 40 +--------- src/app/panels/graph/legend.html | 4 +- src/test/specs/grafanaGraph-specs.js | 82 -------------------- src/test/specs/timeSeries-specs.js | 111 +++++++++++++++++++++++++++ src/test/test-main.js | 1 + 6 files changed, 156 insertions(+), 122 deletions(-) create mode 100644 src/test/specs/timeSeries-specs.js diff --git a/src/app/components/timeSeries.js b/src/app/components/timeSeries.js index 8858b01a139..e49b45e17e4 100644 --- a/src/app/components/timeSeries.js +++ b/src/app/components/timeSeries.js @@ -11,6 +11,46 @@ function (_, kbn) { this.label = opts.info.alias; } + function matchSeriesOverride(aliasOrRegex, seriesAlias) { + 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; + 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.yaxis !== void 0) { + this.info.yaxis = override.yaxis; + } + } + }; + TimeSeries.prototype.getFlotPairs = function (fillStyle, yFormats) { var result = []; diff --git a/src/app/directives/grafanaGraph.js b/src/app/directives/grafanaGraph.js index 6004b6e366c..3525e3a98f4 100755 --- a/src/app/directives/grafanaGraph.js +++ b/src/app/directives/grafanaGraph.js @@ -118,7 +118,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 }, @@ -155,8 +155,8 @@ function (angular, $, kbn, moment, _) { for (var i = 0; i < data.length; i++) { var series = data[i]; + series.applySeriesOverrides(panel.seriesOverrides); series.data = series.getFlotPairs(panel.nullPointMode, panel.y_formats); - applySeriesOverrideOptions(series); } if (data.length && data[0].info.timeStep) { @@ -183,42 +183,6 @@ function (angular, $, kbn, moment, _) { } } - function matchSeriesOverride(aliasOrRegex, seriesAlias) { - 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 applySeriesOverrideOptions(series) { - series.lines = {}; - series.points = {}; - series.bars = {}; - delete series.stack; - - for (var i = 0; i < scope.panel.seriesOverrides.length; i++) { - var override = scope.panel.seriesOverrides[i]; - if (!matchSeriesOverride(override.alias, series.info.alias)) { - continue; - } - if (override.lines !== void 0) { series.lines.show = override.lines; } - if (override.points !== void 0) { series.points.show = override.points; } - if (override.bars !== void 0) { series.bars.show = override.bars; } - if (override.fill !== void 0) { series.lines.fill = translateFillOption(override.fill); } - if (override.stack !== void 0) { series.stack = override.stack; } - if (override.linewidth !== void 0) { series.lines.lineWidth = override.linewidth; } - if (override.pointradius !== void 0) { series.points.radius = override.pointradius; } - if (override.steppedLine !== void 0) { series.lines.steps = override.steppedLine; } - if (override.yaxis !== void 0) { - series.yaxis = override.yaxis; - series.info.yaxis = override.yaxis; - } - } - } - function translateFillOption(fill) { return fill === 0 ? 0.001 : fill/10; } diff --git a/src/app/panels/graph/legend.html b/src/app/panels/graph/legend.html index 5524a8047fe..0e5edc459ce 100755 --- a/src/app/panels/graph/legend.html +++ b/src/app/panels/graph/legend.html @@ -34,12 +34,12 @@
    - -
  • - - - + {{option.name}}: {{option.value}}
  • + diff --git a/src/test/specs/grafanaGraph-specs.js b/src/test/specs/grafanaGraph-specs.js index 449d8db4c39..5f3ad0dd413 100644 --- a/src/test/specs/grafanaGraph-specs.js +++ b/src/test/specs/grafanaGraph-specs.js @@ -92,8 +92,8 @@ define([ }); graphScenario('should order series order according to zindex', function(ctx) { - ctx.setup(function(scope, data) { - data[0].zindex = 2; + ctx.setup(function(scope) { + scope.panel.seriesOverrides = [{ alias: 'series1', zindex: 2 }]; }); it('should move zindex 2 last', function() { diff --git a/src/test/specs/timeSeries-specs.js b/src/test/specs/timeSeries-specs.js index f5fa980c491..cf28c1aa450 100644 --- a/src/test/specs/timeSeries-specs.js +++ b/src/test/specs/timeSeries-specs.js @@ -101,8 +101,8 @@ define([ it('should set yaxis', function() { expect(series.info.yaxis).to.be(2); - }); + it('should set zindex', function() { expect(series.zindex).to.be(2); }); From a9cfb160c91d8b1bfcc57d768e88d39d218a0170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 20 Aug 2014 11:48:00 +0200 Subject: [PATCH 020/146] Added typeahead to series overrides, #425 --- src/app/components/timeSeries.js | 2 ++ src/app/panels/graph/seriesOverridesCtrl.js | 6 ++++++ src/app/panels/graph/styleEditor.html | 8 +++++--- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/app/components/timeSeries.js b/src/app/components/timeSeries.js index c64b5931e7f..85c27f1da88 100644 --- a/src/app/components/timeSeries.js +++ b/src/app/components/timeSeries.js @@ -12,6 +12,8 @@ function (_, kbn) { } 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]); diff --git a/src/app/panels/graph/seriesOverridesCtrl.js b/src/app/panels/graph/seriesOverridesCtrl.js index cc6b240d474..4f54c6d0e6f 100644 --- a/src/app/panels/graph/seriesOverridesCtrl.js +++ b/src/app/panels/graph/seriesOverridesCtrl.js @@ -44,6 +44,12 @@ define([ $scope.render(); }; + $scope.getSeriesNames = function() { + return _.map($scope.legend, function(info) { + return info.alias; + }); + }; + $scope.updateCurrentOverrides = function() { $scope.currentOverrides = []; _.each($scope.overrideMenu, function(option) { diff --git a/src/app/panels/graph/styleEditor.html b/src/app/panels/graph/styleEditor.html index ec4f59b50b4..de7318d4500 100644 --- a/src/app/panels/graph/styleEditor.html +++ b/src/app/panels/graph/styleEditor.html @@ -72,8 +72,8 @@
      -
    • +
    • +
    @@ -84,7 +84,9 @@
  • From c0d7ddf1fbe70a877670ef5f1cfe3382cac6e0c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 20 Aug 2014 12:11:14 +0200 Subject: [PATCH 021/146] Updated changelog with new display styles per series option feature, Closes #425, Closes #700 --- CHANGELOG.md | 1 + src/app/components/kbn.js | 28 ++++++++++++++-------------- src/test/specs/helpers.js | 2 +- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 793a0740e4e..0c6d39929f8 100644 --- a/CHANGELOG.md +++ b/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) diff --git a/src/app/components/kbn.js b/src/app/components/kbn.js index 71fe1b4b75a..46a1b8dfa79 100644 --- a/src/app/components/kbn.js +++ b/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: diff --git a/src/test/specs/helpers.js b/src/test/specs/helpers.js index de2be527f9f..4f2eb7ea680 100644 --- a/src/test/specs/helpers.js +++ b/src/test/specs/helpers.js @@ -64,7 +64,7 @@ define([ function FilterSrvStub() { this.time = { from:'now-1h', to: 'now'}; this.timeRange = function(parse) { - if (!parse) { + if (parse === false) { return this.time; } return { From c000f438ae7e9d21a8948651b3e11e2c7aa85bc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 20 Aug 2014 15:03:10 +0200 Subject: [PATCH 022/146] added unit tests for grid thresholds --- src/test/specs/grafanaGraph-specs.js | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/test/specs/grafanaGraph-specs.js b/src/test/specs/grafanaGraph-specs.js index 5f3ad0dd413..c7a27740f4d 100644 --- a/src/test/specs/grafanaGraph-specs.js +++ b/src/test/specs/grafanaGraph-specs.js @@ -73,6 +73,46 @@ define([ }); }); + 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; From 019d077f5a68e2ad78d3b824eaf64ffea5cb2fd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 20 Aug 2014 19:56:32 +0200 Subject: [PATCH 023/146] Added missing max-height to search results container --- src/css/less/grafana.less | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/css/less/grafana.less b/src/css/less/grafana.less index 0fc9c72bf2f..709d7fdb028 100644 --- a/src/css/less/grafana.less +++ b/src/css/less/grafana.less @@ -52,6 +52,9 @@ } .search-results-container { + max-height: 570px; + overflow: auto; + display: block; .search-result-item a { } From 17ffb167e2d0469d6baecbf0b78bd11c826cf06d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 20 Aug 2014 22:34:51 +0200 Subject: [PATCH 024/146] Small fixes for firefox --- src/app/components/settings.js | 2 +- src/app/controllers/dashboardCtrl.js | 9 ++++++++- src/app/directives/grafanaGraph.js | 5 ----- src/app/partials/search.html | 10 +++++----- src/app/services/graphite/graphiteDatasource.js | 4 ++-- src/css/less/grafana.less | 2 +- 6 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/app/components/settings.js b/src/app/components/settings.js index 3edafc5a407..b43237224b4 100644 --- a/src/app/components/settings.js +++ b/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 : {} }; diff --git a/src/app/controllers/dashboardCtrl.js b/src/app/controllers/dashboardCtrl.js index 5620b7305a2..7132ca8a571 100644 --- a/src/app/controllers/dashboardCtrl.js +++ b/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) { diff --git a/src/app/directives/grafanaGraph.js b/src/app/directives/grafanaGraph.js index 38ef36bc912..222bde78b26 100755 --- a/src/app/directives/grafanaGraph.js +++ b/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; diff --git a/src/app/partials/search.html b/src/app/partials/search.html index e1c9f158e0b..7e7ee2909db 100644 --- a/src/app/partials/search.html +++ b/src/app/partials/search.html @@ -64,11 +64,6 @@ bindonce ng-repeat="row in results.dashboards" ng-class="{'selected': $index === selectedIndex }" ng-click="goToDashboard(row.id)"> - - - - - + + + + +
  • diff --git a/src/app/services/graphite/graphiteDatasource.js b/src/app/services/graphite/graphiteDatasource.js index e048484d28c..761b29b3a9f 100644 --- a/src/app/services/graphite/graphiteDatasource.js +++ b/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'); } } diff --git a/src/css/less/grafana.less b/src/css/less/grafana.less index 709d7fdb028..fdc6051bc67 100644 --- a/src/css/less/grafana.less +++ b/src/css/less/grafana.less @@ -52,7 +52,7 @@ } .search-results-container { - max-height: 570px; + max-height: 600px; overflow: auto; display: block; .search-result-item a { From c6812f569fdbe08b6a5552e6180fba37fa748ecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 21 Aug 2014 08:42:09 +0200 Subject: [PATCH 025/146] Small js warning fix for firefox --- src/app/partials/dashboard_topnav.html | 2 +- src/vendor/bootstrap/bootstrap.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/partials/dashboard_topnav.html b/src/app/partials/dashboard_topnav.html index b49e528ffff..271b71cf5a4 100644 --- a/src/app/partials/dashboard_topnav.html +++ b/src/app/partials/dashboard_topnav.html @@ -22,7 +22,7 @@ - + + + -
    -
      -
    • - name:
      - -
    • -
    • - filter.query:
      - -
    • -
    • - - -
    • -
    -
    - - -
    -
    - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/services/annotationsSrv.js b/src/app/services/annotationsSrv.js index a219caa5d66..a59eae4cf07 100644 --- a/src/app/services/annotationsSrv.js +++ b/src/app/services/annotationsSrv.js @@ -9,7 +9,6 @@ define([ module.service('annotationsSrv', function(datasourceSrv, $q, alertSrv, $rootScope) { var promiseCached; - var annotationPanel; var list = []; var timezone; @@ -23,8 +22,7 @@ define([ }; this.getAnnotations = function(filterSrv, rangeUnparsed, dashboard) { - annotationPanel = _.findWhere(dashboard.pulldowns, { type: 'annotations' }); - if (!annotationPanel.enable) { + if (!dashboard.annotations.enable) { return $q.when(null); } @@ -33,7 +31,7 @@ define([ } timezone = dashboard.timezone; - var annotations = _.where(annotationPanel.annotations, { enable: true }); + var annotations = _.where(dashboard.annotations.list, { enable: true }); var promises = _.map(annotations, function(annotation) { var datasource = datasourceSrv.get(annotation.datasource); diff --git a/src/app/services/dashboard/dashboardSrv.js b/src/app/services/dashboard/dashboardSrv.js index 6a363837bc3..6139a41f83e 100644 --- a/src/app/services/dashboard/dashboardSrv.js +++ b/src/app/services/dashboard/dashboardSrv.js @@ -27,10 +27,10 @@ function (angular, $, kbn, _, moment) { this.timezone = data.timezone || 'browser'; this.editable = data.editble || true; this.rows = data.rows || []; - this.pulldowns = data.pulldowns || []; this.nav = data.nav || []; this.time = data.time || { from: 'now-6h', to: 'now' }; - this.templating = data.templating || { list: [] }; + this.templating = data.templating || { list: [], enable: false }; + this.annotations = data.annotations || { list: [], enable: false}; this.refresh = data.refresh; this.version = data.version || 0; @@ -38,14 +38,6 @@ function (angular, $, kbn, _, moment) { this.nav.push({ type: 'timepicker' }); } - if (!_.findWhere(this.pulldowns, {type: 'filtering'})) { - this.pulldowns.push({ type: 'filtering', enable: false }); - } - - if (!_.findWhere(this.pulldowns, {type: 'annotations'})) { - this.pulldowns.push({ type: 'annotations', enable: false }); - } - this.updateSchema(data); } @@ -147,9 +139,9 @@ function (angular, $, kbn, _, moment) { p.updateSchema = function(old) { var oldVersion = this.version; var panelUpgrades = []; - this.version = 4; + this.version = 5; - if (oldVersion === 4) { + if (oldVersion === 5) { return; } @@ -224,6 +216,21 @@ function (angular, $, kbn, _, moment) { }); } + if (oldVersion < 5) { + // move pulldowns to new schema + var filtering = _.findWhere(old.pulldowns, { type: 'filtering' }); + var annotations = _.findWhere(old.pulldowns, { type: 'annotations' }); + if (filtering) { + this.templating.enable = filtering.enable; + } + if (annotations) { + this.annotations = { + list: annotations.annotations, + enable: annotations.enable + }; + } + } + if (panelUpgrades.length === 0) { return; } diff --git a/src/test/specs/dashboardSrv-specs.js b/src/test/specs/dashboardSrv-specs.js index a2c7c1772f6..c141b746cd3 100644 --- a/src/test/specs/dashboardSrv-specs.js +++ b/src/test/specs/dashboardSrv-specs.js @@ -18,7 +18,6 @@ define([ it('should have default properties', function() { expect(model.rows.length).to.be(0); expect(model.nav.length).to.be(1); - expect(model.pulldowns.length).to.be(2); }); }); @@ -91,6 +90,17 @@ define([ beforeEach(inject(function(dashboardSrv) { model = dashboardSrv.create({ services: { filter: { time: { from: 'now-1d', to: 'now'}, list: [1] }}, + pulldowns: [ + { + type: 'filtering', + enable: true + }, + { + type: 'annotations', + enable: true, + annotations: [{name: 'old'}] + } + ], rows: [ { panels: [ @@ -140,8 +150,15 @@ define([ expect(graph.seriesOverrides[0].yaxis).to.be(2); }); + it('should move pulldowns to new schema', function() { + expect(model.templating.enable).to.be(true); + expect(model.annotations.enable).to.be(true); + expect(model.annotations.list[0].name).to.be('old'); + }); + + it('dashboard schema version should be set to latest', function() { - expect(model.version).to.be(4); + expect(model.version).to.be(5); }); }); From 6969a4121c7f1370764897eba4979df7fe5394bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 25 Aug 2014 13:31:31 +0200 Subject: [PATCH 038/146] removing pulldowns and simplifying submenu controls code --- src/app/controllers/dashboardCtrl.js | 3 +++ src/app/controllers/submenuCtrl.js | 2 +- src/app/partials/dashboard.html | 11 +------- src/app/partials/submenu.html | 38 ++++++++++++++++++++++++++++ 4 files changed, 43 insertions(+), 11 deletions(-) create mode 100644 src/app/partials/submenu.html diff --git a/src/app/controllers/dashboardCtrl.js b/src/app/controllers/dashboardCtrl.js index 694fe59a319..4b6bdb76382 100644 --- a/src/app/controllers/dashboardCtrl.js +++ b/src/app/controllers/dashboardCtrl.js @@ -48,6 +48,9 @@ function (angular, $, config, _) { $scope.filter = filterSrv; $scope.filter.init($scope.dashboard); + $scope.submenuEnabled = $scope.dashboard.templating.enable || + $scope.dashboard.annotations.enable; + var panelMove = panelMoveSrv.create($scope.dashboard); $scope.panelMoveDrop = panelMove.onDrop; diff --git a/src/app/controllers/submenuCtrl.js b/src/app/controllers/submenuCtrl.js index b75fab56043..beefcf15ff0 100644 --- a/src/app/controllers/submenuCtrl.js +++ b/src/app/controllers/submenuCtrl.js @@ -24,4 +24,4 @@ function (angular, app, _) { }); -}); \ No newline at end of file +}); diff --git a/src/app/partials/dashboard.html b/src/app/partials/dashboard.html index 368583cb5c5..95c22b574d8 100644 --- a/src/app/partials/dashboard.html +++ b/src/app/partials/dashboard.html @@ -3,16 +3,7 @@
    - diff --git a/src/app/partials/submenu.html b/src/app/partials/submenu.html index e1fba4a8bdf..ab432753bc0 100644 --- a/src/app/partials/submenu.html +++ b/src/app/partials/submenu.html @@ -5,29 +5,40 @@ diff --git a/src/css/less/grafana.less b/src/css/less/grafana.less index ca006b2b015..7773d4b31b9 100644 --- a/src/css/less/grafana.less +++ b/src/css/less/grafana.less @@ -284,6 +284,14 @@ &a:hover { background: @grafanaTargetFuncBackground; } + + &.template-param-name { + border-right: none; + padding-right: 3px; + } + &.annotation-segment { + padding: 8px 15px; + } } .grafana-target-function { @@ -405,7 +413,6 @@ select.grafana-target-segment-input { } } - .scrollable { max-height: 300px; overflow: auto; From 8b3c89e2676a743247d2a77c8a3db8bf27c45778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 25 Aug 2014 15:55:42 +0200 Subject: [PATCH 040/146] Starting work on new templating editor --- src/app/partials/submenu.html | 6 +++--- src/app/partials/templating_editor.html | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 src/app/partials/templating_editor.html diff --git a/src/app/partials/submenu.html b/src/app/partials/submenu.html index ab432753bc0..a9b2230237e 100644 --- a/src/app/partials/submenu.html +++ b/src/app/partials/submenu.html @@ -10,8 +10,8 @@ @@ -35,7 +35,7 @@ -
  • +
  • {{annotation.name}}
  • diff --git a/src/app/partials/templating_editor.html b/src/app/partials/templating_editor.html new file mode 100644 index 00000000000..0282ea26ade --- /dev/null +++ b/src/app/partials/templating_editor.html @@ -0,0 +1,15 @@ +
    + + + +
    From 061d1262d4a405ad2ed392fc6f0b7b942f290d4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 25 Aug 2014 16:14:47 +0200 Subject: [PATCH 041/146] Small html markup/css simplification --- src/app/partials/dashboard.html | 196 ++++++++++++++++---------------- src/css/less/grafana.less | 2 +- src/css/less/overrides.less | 2 +- 3 files changed, 98 insertions(+), 102 deletions(-) diff --git a/src/app/partials/dashboard.html b/src/app/partials/dashboard.html index 95c22b574d8..b2857205fa7 100644 --- a/src/app/partials/dashboard.html +++ b/src/app/partials/dashboard.html @@ -7,112 +7,108 @@
    -
    -
    -
    - -
    -
    -
    -
    -
    - - - - - - -
    - -
    -
    -
    - - - -
    - -
    -
    -
    -
    -
    +
    + +
    +
    +
    +
    +
    + + + + + + +
    + +
    +
    +
    + + + +
    + +
    +
    - -
    - -
    +
    +
    +
    -
    -
    + +
    + +
    -
    -
    -
    -
    -
    +
    +
    +
    +
    +
    +
    +
    -
    -
    - - ADD A ROW - -
    -
    -
    -
    +
    +
    + + ADD A ROW + +
    +
    diff --git a/src/css/less/grafana.less b/src/css/less/grafana.less index 7773d4b31b9..77381a4ebf5 100644 --- a/src/css/less/grafana.less +++ b/src/css/less/grafana.less @@ -179,7 +179,7 @@ } } -.dashboard-fullscreen .container-fluid.main { +.dashboard-fullscreen .main-view-container { height: 0px; width: 0px; position: fixed; diff --git a/src/css/less/overrides.less b/src/css/less/overrides.less index eeff1ef0087..14c1fabf9b8 100644 --- a/src/css/less/overrides.less +++ b/src/css/less/overrides.less @@ -14,7 +14,7 @@ padding-right: 0px; } -.container.grafana-container { +.main-view-container { padding: 5px 10px; width: 100%; box-sizing: border-box; From b506e7c26711082e8ca540070735355e7d4a3ac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 25 Aug 2014 16:44:33 +0200 Subject: [PATCH 042/146] fixed submenu in fullscreen mode --- src/app/directives/bodyClass.js | 10 +-- src/app/panels/filtering/module.js | 57 --------------- src/css/less/submenu.less | 110 +---------------------------- 3 files changed, 3 insertions(+), 174 deletions(-) diff --git a/src/app/directives/bodyClass.js b/src/app/directives/bodyClass.js index 6d3c6d32e15..86ff598d69e 100644 --- a/src/app/directives/bodyClass.js +++ b/src/app/directives/bodyClass.js @@ -12,20 +12,14 @@ function (angular, app, _) { return { link: function($scope, elem) { - var lastPulldownVal; var lastHideControlsVal; - $scope.$watchCollection('dashboard.pulldowns', function() { + $scope.$watch('submenuEnabled', function() { if (!$scope.dashboard) { return; } - var panel = _.find($scope.dashboard.pulldowns, function(pulldown) { return pulldown.enable; }); - var panelEnabled = panel ? panel.enable : false; - if (lastPulldownVal !== panelEnabled) { - elem.toggleClass('submenu-controls-visible', panelEnabled); - lastPulldownVal = panelEnabled; - } + elem.toggleClass('submenu-controls-visible', $scope.submenuEnabled); }); $scope.$watch('dashboard.hideControls', function() { diff --git a/src/app/panels/filtering/module.js b/src/app/panels/filtering/module.js index a8f64cad6ed..eb89cebafa0 100644 --- a/src/app/panels/filtering/module.js +++ b/src/app/panels/filtering/module.js @@ -34,63 +34,6 @@ function (angular, app, _) { $scope.filter.removeTemplateParameter(templateParameter); }; - $scope.filterOptionSelected = function(templateParameter, option, recursive) { - templateParameter.current = option; - - $scope.filter.updateTemplateData(); - - return $scope.applyFilterToOtherFilters(templateParameter) - .then(function() { - // only refresh in the outermost call - if (!recursive) { - $scope.dashboard.emit_refresh(); - } - }); - }; - - $scope.applyFilterToOtherFilters = function(updatedTemplatedParam) { - var promises = _.map($scope.filter.templateParameters, function(templateParam) { - if (templateParam === updatedTemplatedParam) { - return; - } - if (templateParam.query.indexOf('[[' + updatedTemplatedParam.name + ']]') !== -1) { - return $scope.applyFilter(templateParam); - } - }); - - return $q.all(promises); - }; - - $scope.applyFilter = function(templateParam) { - return datasourceSrv.default.metricFindQuery($scope.filter, templateParam.query) - .then(function (results) { - templateParam.editing = undefined; - templateParam.options = _.map(results, function(node) { - return { text: node.text, value: node.text }; - }); - - if (templateParam.includeAll) { - var allExpr = '{'; - _.each(templateParam.options, function(option) { - allExpr += option.text + ','; - }); - allExpr = allExpr.substring(0, allExpr.length - 1) + '}'; - templateParam.options.unshift({text: 'All', value: allExpr}); - } - - // if parameter has current value - // if it exists in options array keep value - if (templateParam.current) { - var currentExists = _.findWhere(templateParam.options, { value: templateParam.current.value }); - if (currentExists) { - return $scope.filterOptionSelected(templateParam, templateParam.current, true); - } - } - - return $scope.filterOptionSelected(templateParam, templateParam.options[0], true); - }); - }; - $scope.add = function() { $scope.filter.addTemplateParameter({ type : 'filter', diff --git a/src/css/less/submenu.less b/src/css/less/submenu.less index 514ccf6e29b..247ffda5175 100644 --- a/src/css/less/submenu.less +++ b/src/css/less/submenu.less @@ -1,118 +1,10 @@ -.submenu-controls { - background: @submenuBackground; - font-size: inherit; - label { - margin: 0; - padding-right: 4px; - display: inline; - } - input[type=checkbox] { - margin: 0; - } -} - .submenu-controls-visible:not(.hide-controls) { .panel-fullscreen { - top: 82px; + top: 90px; } } -.submenu-panel { - padding: 0 4px 0 8px; - border-right: 1px solid @submenuBorder; - float: left; -} - -.submenu-panel:first-child { - padding-left: 17px; -} - -.submenu-panel-title { - float: left; - text-transform: uppercase; - padding: 4px 10px 3px 0; -} - -.submenu-panel-wrapper { - float: left; -} - -.submenu-toggle { - padding: 4px 0 3px 8px; - float: left; - .annotation-color-icon { - position: relative; - top: 2px; - } -} - -.submenu-toggle:first-child { - padding-left: 0; -} - -.submenu-control-edit { - padding: 4px 4px 3px 8px; - float: right; - border-left: 1px solid @submenuBorder; - margin-left: 8px; -} - .annotation-disabled, .annotation-disabled a { color: darken(@textColor, 25%); } - -// Filter submenu -.filtering-container { - float: left; -} - -.filtering-container label { - float: left; -} - -.filtering-container input[type=checkbox] { - margin: 0; -} - -.filter-panel-filter { - display:inline-block; - vertical-align: top; - padding: 4px 10px 3px 10px; - border-right: 1px solid @submenuBorder; -} - -.filter-panel-filter:first-child { - padding-left: 0; -} - -.filter-panel-filter ul { - margin-bottom: 0px; -} - -.filter-deselected { - opacity: 0.5; -} - -.filtering-container .filter-action { - float:right; - padding-right: 2px; - margin-bottom: 0px !important; - margin-left: 0px; - margin-top: 4px; -} - -.add-filter-action { - padding: 3px 5px 0px 5px; - position: relative; - top: 4px; -} - -.filter-mandate { - text-decoration: underline; - cursor: pointer; -} - -.filter-apply { - float:right; -} \ No newline at end of file From 00e5bb61fc9f597ebc688c8a23d37842c3a2fb45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 25 Aug 2014 17:27:19 +0200 Subject: [PATCH 043/146] Trying out an alternative to modals --- src/app/controllers/annotationsEditorCtrl.js | 14 ++++++++++++++ src/app/controllers/search.js | 1 - src/app/directives/configModal.js | 6 ++++++ src/app/partials/dashboard.html | 19 ++++++++++++------- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/app/controllers/annotationsEditorCtrl.js b/src/app/controllers/annotationsEditorCtrl.js index 65561643896..ce832eee1d4 100644 --- a/src/app/controllers/annotationsEditorCtrl.js +++ b/src/app/controllers/annotationsEditorCtrl.js @@ -61,4 +61,18 @@ function (angular, app, _) { }; }); + + module.controller('EditViewCtrl', function($scope) { + //$scope.editPanelSrc = 'app/partials/test.html'; + + $scope.onAppEvent('show-edit-panel', function(evt, payload) { + $scope.editPanelSrc = payload.src; + }); + + $scope.dismiss = function() { + $scope.editPanelSrc = null; + }; + + }); + }); diff --git a/src/app/controllers/search.js b/src/app/controllers/search.js index a32aa64acf0..3d11bc68750 100644 --- a/src/app/controllers/search.js +++ b/src/app/controllers/search.js @@ -173,7 +173,6 @@ function (angular, _, config, $) { "#58140C","#052B51","#511749","#3F2B5B", ]; var color = colors[Math.abs(hash % colors.length)]; - console.log("namei " + name + " color: " + color, hash % 4); element.css("background-color", color); }; diff --git a/src/app/directives/configModal.js b/src/app/directives/configModal.js index b7e579e7026..6ca19cef435 100644 --- a/src/app/directives/configModal.js +++ b/src/app/directives/configModal.js @@ -16,6 +16,12 @@ function (angular, _, $) { var id = '#' + partial.replace('.html', '').replace(/[\/|\.|:]/g, '-') + '-' + scope.$id; elem.bind('click',function() { + $timeout(function() { + scope.exitFullscreen(); + scope.emitAppEvent('show-edit-panel', {src: partial}); + }); + return; + if ($(id).length) { elem.attr('data-target', id).attr('data-toggle', 'modal'); scope.$apply(function() { scope.$broadcast('modal-opened'); }); diff --git a/src/app/partials/dashboard.html b/src/app/partials/dashboard.html index b2857205fa7..73b23ed12a4 100644 --- a/src/app/partials/dashboard.html +++ b/src/app/partials/dashboard.html @@ -8,8 +8,12 @@
    +
    +
    +
    +
    +
    -
    @@ -100,14 +104,15 @@
    -
    -
    -
    - - ADD A ROW - +
    +
    + + ADD A ROW + +
    +
    From 6342571afebcbb1dc198519a906a825e82db3a39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 25 Aug 2014 22:39:40 +0200 Subject: [PATCH 044/146] Trying get rid of modals, new better design for dashboard settings and modals --- src/app/controllers/annotationsEditorCtrl.js | 6 +- src/app/directives/bodyClass.js | 2 +- src/app/directives/configModal.js | 2 +- src/app/panels/filtering/editor.html | 0 src/app/panels/filtering/module.html | 48 ----- src/app/panels/filtering/module.js | 47 ----- src/app/partials/dashboard.html | 2 +- src/app/partials/dasheditor.html | 209 ++++++++++--------- src/app/partials/import.html | 4 +- src/app/partials/templating_editor.html | 58 ++++- src/css/less/bootswatch.dark.less | 5 +- src/css/less/grafana.less | 48 +++++ src/css/less/variables.dark.less | 10 +- 13 files changed, 222 insertions(+), 219 deletions(-) delete mode 100644 src/app/panels/filtering/editor.html delete mode 100755 src/app/panels/filtering/module.html delete mode 100644 src/app/panels/filtering/module.js diff --git a/src/app/controllers/annotationsEditorCtrl.js b/src/app/controllers/annotationsEditorCtrl.js index ce832eee1d4..13cf4d29087 100644 --- a/src/app/controllers/annotationsEditorCtrl.js +++ b/src/app/controllers/annotationsEditorCtrl.js @@ -63,9 +63,13 @@ function (angular, app, _) { }); module.controller('EditViewCtrl', function($scope) { - //$scope.editPanelSrc = 'app/partials/test.html'; + $scope.editPanelSrc = 'app/partials/dasheditor.html'; $scope.onAppEvent('show-edit-panel', function(evt, payload) { + if (payload.src === $scope.editPanelSrc) { + $scope.dismiss(); + return; + } $scope.editPanelSrc = payload.src; }); diff --git a/src/app/directives/bodyClass.js b/src/app/directives/bodyClass.js index 86ff598d69e..0b1cac65614 100644 --- a/src/app/directives/bodyClass.js +++ b/src/app/directives/bodyClass.js @@ -3,7 +3,7 @@ define([ 'app', 'lodash' ], -function (angular, app, _) { +function (angular) { 'use strict'; angular diff --git a/src/app/directives/configModal.js b/src/app/directives/configModal.js index 6ca19cef435..9e65f6cb07c 100644 --- a/src/app/directives/configModal.js +++ b/src/app/directives/configModal.js @@ -20,7 +20,7 @@ function (angular, _, $) { scope.exitFullscreen(); scope.emitAppEvent('show-edit-panel', {src: partial}); }); - return; + //return; if ($(id).length) { elem.attr('data-target', id).attr('data-toggle', 'modal'); diff --git a/src/app/panels/filtering/editor.html b/src/app/panels/filtering/editor.html deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/app/panels/filtering/module.html b/src/app/panels/filtering/module.html deleted file mode 100755 index 40f4379a98d..00000000000 --- a/src/app/panels/filtering/module.html +++ /dev/null @@ -1,48 +0,0 @@ -
    - -
    - -
    - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - -
    diff --git a/src/app/panels/filtering/module.js b/src/app/panels/filtering/module.js deleted file mode 100644 index eb89cebafa0..00000000000 --- a/src/app/panels/filtering/module.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - - ## filtering - -*/ -define([ - 'angular', - 'app', - 'lodash' -], -function (angular, app, _) { - 'use strict'; - - var module = angular.module('grafana.panels.filtering', []); - app.useModule(module); - - module.controller('filtering', function($scope, datasourceSrv, $rootScope, $timeout, $q) { - - $scope.panelMeta = { - status : "Stable", - description : "graphite target filters" - }; - - // Set and populate defaults - var _d = { - }; - _.defaults($scope.panel,_d); - - $scope.init = function() { - // empty. Don't know if I need the function then. - }; - - $scope.remove = function(templateParameter) { - $scope.filter.removeTemplateParameter(templateParameter); - }; - - $scope.add = function() { - $scope.filter.addTemplateParameter({ - type : 'filter', - name : 'filter name', - editing : true, - query : 'metric.path.query.*', - }); - }; - - }); -}); diff --git a/src/app/partials/dashboard.html b/src/app/partials/dashboard.html index 73b23ed12a4..b528c1e1016 100644 --- a/src/app/partials/dashboard.html +++ b/src/app/partials/dashboard.html @@ -9,7 +9,7 @@
    -
    +
    diff --git a/src/app/partials/dasheditor.html b/src/app/partials/dasheditor.html index 5179893ea75..3169859da01 100644 --- a/src/app/partials/dasheditor.html +++ b/src/app/partials/dasheditor.html @@ -1,108 +1,119 @@ -