diff --git a/.jsfmtrc b/.jsfmtrc new file mode 100644 index 00000000000..3ca3b4a3b50 --- /dev/null +++ b/.jsfmtrc @@ -0,0 +1,21 @@ +{ + "preset" : "default", + + "lineBreak" : { + "before" : { + "VariableDeclarationWithoutInit" : 0, + }, + + "after": { + "AssignmentOperator": -1, + "ArgumentListArrayExpression": ">=1" + } + }, + + "whiteSpace" : { + "before" : { + }, + "after" : { + } + } +} diff --git a/src/app/controllers/sharePanelCtrl.js b/src/app/controllers/sharePanelCtrl.js index 87903378416..b55da84263a 100644 --- a/src/app/controllers/sharePanelCtrl.js +++ b/src/app/controllers/sharePanelCtrl.js @@ -11,31 +11,64 @@ function (angular, _) { $scope.init = function() { $scope.editor = { index: 0 }; - - var currentUrl = $location.absUrl(); - var panelId = $scope.panel.id; - var range = timeSrv.timeRange(false); - var from = range.from; - var to = range.to; - if (_.isDate(from)) { - from = from.getTime(); - } - if (_.isDate(to)) { - to = to.getTime(); - } - - $scope.shareUrl = currentUrl + "?panelId=" + panelId + "&fullscreen"; - $scope.shareUrl += "&from=" + from; - $scope.shareUrl += "&to=" + to; - $scope.forCurrent = true; $scope.toPanel = true; + $scope.buildUrl(); + }; + + $scope.buildUrl = function() { + var baseUrl = $location.absUrl(); + var queryStart = baseUrl.indexOf('?'); + + if (queryStart !== -1) { + baseUrl = baseUrl.substring(0, queryStart); + } + + var panelId = $scope.panel.id; + var range = timeSrv.timeRange(false); + var params = $location.search(); + + if (_.isString(range.to) && range.to.indexOf('now')) { + range = timeSrv.timeRange(); + } + + params.from = range.from; + params.to = range.to; + + if (_.isDate(params.from)) { params.from = params.from.getTime(); } + if (_.isDate(params.to)) { params.to = params.to.getTime(); } + + if (!$scope.forCurrent) { + delete params.from; + delete params.to; + } + + if ($scope.toPanel) { + params.panelId = panelId; + params.fullscreen = true; + } else { + delete params.panelId; + delete params.fullscreen; + } + + var paramsArray = []; + _.each(params, function(value, key) { + var str = key; + if (value !== true) { + str += '=' + encodeURIComponent(value); + } + paramsArray.push(str); + }); + + $scope.shareUrl = baseUrl + "?" + paramsArray.join('&') ; + $timeout(function() { var input = $element.find('[data-share-panel-url]'); input.focus(); input.select(); - }); + }, 10); + }; $scope.init(); diff --git a/src/app/partials/dasheditor.html b/src/app/partials/dasheditor.html index 5cfad2d8d79..ae8608b97d1 100644 --- a/src/app/partials/dasheditor.html +++ b/src/app/partials/dasheditor.html @@ -39,7 +39,6 @@ Press enter to a add tag - diff --git a/src/app/partials/share-panel.html b/src/app/partials/share-panel.html index efbfcb42ab7..49c13315e69 100644 --- a/src/app/partials/share-panel.html +++ b/src/app/partials/share-panel.html @@ -15,13 +15,14 @@ diff --git a/src/config.sample.js b/src/config.sample.js index ab60995d251..07f29d7be28 100644 --- a/src/config.sample.js +++ b/src/config.sample.js @@ -2,108 +2,110 @@ // config.js is where you will find the core Grafana configuration. This file contains parameter that // must be set before Grafana is run for the first time. -define(['settings'], -function (Settings) { +define(['settings'], function(Settings) { "use strict"; return new Settings({ - /* Data sources - * ======================================================== - * Datasources are used to fetch metrics, annotations, and serve as dashboard storage - * - You can have multiple of the same type. - * - grafanaDB: true marks it for use for dashboard storage - * - default: true marks the datasource as the default metric source (if you have multiple) - * - basic authentication: use url syntax http://username:password@domain:port - */ + /* Data sources + * ======================================================== + * Datasources are used to fetch metrics, annotations, and serve as dashboard storage + * - You can have multiple of the same type. + * - grafanaDB: true marks it for use for dashboard storage + * - default: true marks the datasource as the default metric source (if you have multiple) + * - basic authentication: use url syntax http://username:password@domain:port + */ - // InfluxDB example setup (the InfluxDB databases specified need to exist) - /* - datasources: { - influxdb: { - type: 'influxdb', - url: "http://my_influxdb_server:8086/db/database_name", - username: 'admin', - password: 'admin', + // InfluxDB example setup (the InfluxDB databases specified need to exist) + /* + datasources: { + influxdb: { + type: 'influxdb', + url: "http://my_influxdb_server:8086/db/database_name", + username: 'admin', + password: 'admin', + }, + grafana: { + type: 'influxdb', + url: "http://my_influxdb_server:8086/db/grafana", + username: 'admin', + password: 'admin', + grafanaDB: true + }, }, - grafana: { - type: 'influxdb', - url: "http://my_influxdb_server:8086/db/grafana", - username: 'admin', - password: 'admin', - grafanaDB: true - }, - }, - */ + */ - // Graphite & Elasticsearch example setup - /* - datasources: { - graphite: { - type: 'graphite', - url: "http://my.graphite.server.com:8080", + // Graphite & Elasticsearch example setup + /* + datasources: { + graphite: { + type: 'graphite', + url: "http://my.graphite.server.com:8080", + }, + elasticsearch: { + type: 'elasticsearch', + url: "http://my.elastic.server.com:9200", + index: 'grafana-dash', + grafanaDB: true, + } }, - elasticsearch: { - type: 'elasticsearch', - url: "http://my.elastic.server.com:9200", - index: 'grafana-dash', - grafanaDB: true, + */ + + // OpenTSDB & Elasticsearch example setup + /* + datasources: { + opentsdb: { + type: 'opentsdb', + url: "http://opentsdb.server:4242", + }, + elasticsearch: { + type: 'elasticsearch', + url: "http://my.elastic.server.com:9200", + index: 'grafana-dash', + grafanaDB: true, + } + }, + */ + + /* Global configuration options + * ======================================================== + */ + + // specify the limit for dashboard search results + search: { + max_results: 20 + }, + + // default home dashboard + default_route: '/dashboard/file/default.json', + + // set to false to disable unsaved changes warning + unsaved_changes_warning: true, + + // set the default timespan for the playlist feature + // Example: "1m", "1h" + playlist_timespan: "1m", + + // If you want to specify password before saving, please specify it below + // The purpose of this password is not security, but to stop some users from accidentally changing dashboards + admin: { + password: '' + }, + + // Change window title prefix from 'Grafana - ' + window_title_prefix: 'Grafana - ', + + // Add your own custom panels + plugins: { + // list of plugin panels + panels: [], + // requirejs modules in plugins folder that should be loaded + // for example custom datasources + dependencies: [], } - }, - */ - // OpenTSDB & Elasticsearch example setup - /* - datasources: { - opentsdb: { - type: 'opentsdb', - url: "http://opentsdb.server:4242", - }, - elasticsearch: { - type: 'elasticsearch', - url: "http://my.elastic.server.com:9200", - index: 'grafana-dash', - grafanaDB: true, - } - }, - */ - - /* Global configuration options - * ======================================================== - */ - - // specify the limit for dashboard search results - search: { - max_results: 20 - }, - - // default home dashboard - default_route: '/dashboard/file/default.json', - - // set to false to disable unsaved changes warning - unsaved_changes_warning: true, - - // set the default timespan for the playlist feature - // Example: "1m", "1h" - playlist_timespan: "1m", - - // If you want to specify password before saving, please specify it below - // The purpose of this password is not security, but to stop some users from accidentally changing dashboards - admin: { - password: '' - }, - - // Change window title prefix from 'Grafana - ' - window_title_prefix: 'Grafana - ', - - // Add your own custom panels - plugins: { - // list of plugin panels - panels: [], - // requirejs modules in plugins folder that should be loaded - // for example custom datasources - dependencies: [], - } - - }); + }); }); + + + diff --git a/src/css/less/forms.less b/src/css/less/forms.less index ab17dc81c94..e1affd09ae9 100644 --- a/src/css/less/forms.less +++ b/src/css/less/forms.less @@ -2,6 +2,8 @@ input[type=text].input-fluid { width: 100%; box-sizing: border-box; padding: 14px; + -moz-box-sizing: border-box; + height: 100%; } input[type="checkbox"].cr1 { diff --git a/src/test/specs/helpers.js b/src/test/specs/helpers.js index 040905375b7..1859bfc10a1 100644 --- a/src/test/specs/helpers.js +++ b/src/test/specs/helpers.js @@ -8,6 +8,7 @@ define([ var self = this; this.datasource = {}; + this.$element = {}; this.annotationsSrv = {}; this.timeSrv = new TimeSrvStub(); this.templateSrv = new TemplateSrvStub(); @@ -16,18 +17,23 @@ define([ get: function() { return self.datasource; } }; - this.providePhase = function() { + this.providePhase = function(mocks) { return module(function($provide) { $provide.value('datasourceSrv', self.datasourceSrv); $provide.value('annotationsSrv', self.annotationsSrv); $provide.value('timeSrv', self.timeSrv); $provide.value('templateSrv', self.templateSrv); + $provide.value('$element', self.$element); + _.each(mocks, function(key, value) { + $provide.value(key, value); + }); }); }; this.createControllerPhase = function(controllerName) { - return inject(function($controller, $rootScope, $q) { + return inject(function($controller, $rootScope, $q, $location) { self.scope = $rootScope.$new(); + self.$location = $location; self.scope.panel = {}; self.scope.row = { panels:[] }; self.scope.dashboard = {}; diff --git a/src/test/specs/sharePanelCtrl-specs.js b/src/test/specs/sharePanelCtrl-specs.js new file mode 100644 index 00000000000..7a7f741ea52 --- /dev/null +++ b/src/test/specs/sharePanelCtrl-specs.js @@ -0,0 +1,59 @@ +define([ + './helpers', + 'controllers/sharePanelCtrl' +], function(helpers) { + 'use strict'; + + describe('SharePanelCtrl', function() { + var ctx = new helpers.ControllerTestContext(); + + beforeEach(module('grafana.controllers')); + + beforeEach(ctx.providePhase()); + beforeEach(ctx.createControllerPhase('SharePanelCtrl')); + + describe('shareUrl with current time range and panel', function() { + + it('should generate share url relative time', function() { + ctx.$location.path('/test'); + ctx.scope.panel = { id: 22 }; + ctx.timeSrv.time = { from: 'now-1h', to: 'now' }; + + ctx.scope.buildUrl(); + expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=now-1h&to=now&panelId=22&fullscreen'); + }); + + it('should generate share url absolute time', function() { + ctx.$location.path('/test'); + ctx.scope.panel = { id: 22 }; + ctx.timeSrv.time = { from: new Date(2012,1,1), to: new Date(2014,3,5) }; + + ctx.scope.buildUrl(); + expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=1328050800000&to=1396648800000&panelId=22&fullscreen'); + }); + + it('should generate share url with time as JSON strings', function() { + ctx.$location.path('/test'); + ctx.scope.panel = { id: 22 }; + ctx.timeSrv.time = { from: new Date(2012,1,1).toJSON(), to: new Date(2014,3,5).toJSON() }; + + ctx.scope.buildUrl(); + expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=1328050800000&to=1396648800000&panelId=22&fullscreen'); + }); + + it('should remove panel id when toPanel is false', function() { + ctx.$location.path('/test'); + ctx.scope.panel = { id: 22 }; + ctx.scope.toPanel = false; + ctx.timeSrv.time = { from: 'now-1h', to: 'now' }; + + ctx.scope.buildUrl(); + expect(ctx.scope.shareUrl).to.be('http://server/#/test?from=now-1h&to=now'); + }); + + }); + + }); + +}); + diff --git a/src/test/test-main.js b/src/test/test-main.js index 6862c9fa8ae..468c4fc8603 100644 --- a/src/test/test-main.js +++ b/src/test/test-main.js @@ -131,6 +131,7 @@ require([ 'specs/grafanaGraph-specs', 'specs/graph-tooltip-specs', 'specs/seriesOverridesCtrl-specs', + 'specs/sharePanelCtrl-specs', 'specs/timeSrv-specs', 'specs/templateSrv-specs', 'specs/templateValuesSrv-specs',