diff --git a/src/app/controllers/templateEditorCtrl.js b/src/app/controllers/templateEditorCtrl.js index e4e93b568b3..058a190658b 100644 --- a/src/app/controllers/templateEditorCtrl.js +++ b/src/app/controllers/templateEditorCtrl.js @@ -69,7 +69,7 @@ function (angular, _) { }; $scope.typeChanged = function () { - if ($scope.current.type === 'time period') { + if ($scope.current.type === 'interval') { $scope.current.query = '1m,10m,30m,1h,6h,12h,1d,7d,14d,30d'; } }; diff --git a/src/app/partials/panelgeneral.html b/src/app/partials/panelgeneral.html index c038c807519..e0df648db0a 100644 --- a/src/app/partials/panelgeneral.html +++ b/src/app/partials/panelgeneral.html @@ -8,7 +8,7 @@
- +
diff --git a/src/app/partials/templating_editor.html b/src/app/partials/templating_editor.html index 22ab029061e..afed151d616 100644 --- a/src/app/partials/templating_editor.html +++ b/src/app/partials/templating_editor.html @@ -58,7 +58,7 @@
- +
@@ -70,10 +70,22 @@
-
-
- - +
+
+
+ + +
+
+
+
+ + +
+
+ + +
@@ -101,7 +113,7 @@
- +
@@ -119,7 +131,7 @@ {{option.text}} -
+
diff --git a/src/app/services/templateSrv.js b/src/app/services/templateSrv.js index 907065e2fa6..26263780406 100644 --- a/src/app/services/templateSrv.js +++ b/src/app/services/templateSrv.js @@ -10,15 +10,18 @@ function (angular, _) { module.service('templateSrv', function($q, $routeParams) { var self = this; + this._regex = /\$(\w+)|\[\[([\s\S]+?)\]\]/g; + this._templateData = {}; + this._grafanaVariables = {}; + this.init = function(variables) { - this.templateSettings = { interpolate : /\[\[([\s\S]+?)\]\]/g }; this.variables = variables; - this.regex = /\$(\w+)|\[\[([\s\S]+?)\]\]/g; this.updateTemplateData(true); }; this.updateTemplateData = function(initial) { - var _templateData = {}; + var data = {}; + _.each(this.variables, function(variable) { if (initial) { var urlValue = $routeParams[ variable.name ]; @@ -26,31 +29,32 @@ function (angular, _) { variable.current = { text: urlValue, value: urlValue }; } } + if (!variable.current || !variable.current.value) { return; } - _templateData[variable.name] = variable.current.value; - + data[variable.name] = variable.current.value; }); - this._templateData = _templateData; + + this._templateData = data; }; - this.setGrafanaVariable = function(name, value) { - this._templateData[name] = value; + this.setGrafanaVariable = function (name, value) { + this._grafanaVariables[name] = value; }; this.variableExists = function(expression) { - this.regex.lastIndex = 0; - var match = this.regex.exec(expression); + this._regex.lastIndex = 0; + var match = this._regex.exec(expression); return match && (self._templateData[match[1] || match[2]] !== void 0); }; this.highlightVariablesAsHtml = function(str) { if (!str || !_.isString(str)) { return str; } - this.regex.lastIndex = 0; - return str.replace(this.regex, function(match, g1, g2) { + this._regex.lastIndex = 0; + return str.replace(this._regex, function(match, g1, g2) { if (self._templateData[g1 || g2]) { return '' + match + ''; } @@ -60,9 +64,14 @@ function (angular, _) { this.replace = function(target) { if (!target) { return; } - this.regex.lastIndex = 0; - return target.replace(this.regex, function(match, g1, g2) { - return self._templateData[g1 || g2] || match; + var value; + this._regex.lastIndex = 0; + + return target.replace(this._regex, function(match, g1, g2) { + value = self._templateData[g1 || g2]; + if (!value) { return match; } + + return self._grafanaVariables[value] || value; }); }; diff --git a/src/app/services/templateValuesSrv.js b/src/app/services/templateValuesSrv.js index 2f6b1cd4e94..e1dc2b89741 100644 --- a/src/app/services/templateValuesSrv.js +++ b/src/app/services/templateValuesSrv.js @@ -8,12 +8,18 @@ function (angular, _, kbn) { var module = angular.module('grafana.services'); - module.service('templateValuesSrv', function($q, $rootScope, datasourceSrv, $routeParams, templateSrv) { + module.service('templateValuesSrv', function($q, $rootScope, datasourceSrv, $routeParams, templateSrv, timeSrv) { var self = this; + $rootScope.onAppEvent('time-range-changed', function() { + var variable = _.findWhere(self.variables, { type: 'interval' }); + if (variable) { + self.updateAutoInterval(variable); + } + }); + this.init = function(dashboard) { this.variables = dashboard.templating.list; - templateSrv.init(this.variables); for (var i = 0; i < this.variables.length; i++) { @@ -21,9 +27,24 @@ function (angular, _, kbn) { if (param.refresh) { this.updateOptions(param); } + else if (param.type === 'interval') { + this.updateAutoInterval(param); + } } }; + this.updateAutoInterval = function(variable) { + if (!variable.auto) { return; } + + // add auto option if missing + if (variable.options[0].text !== 'auto') { + variable.options.unshift({ text: 'auto', value: '$__auto_interval' }); + } + + var interval = kbn.calculateInterval(timeSrv.timeRange(), variable.auto_count); + templateSrv.setGrafanaVariable('$__auto_interval', interval); + }; + this.setVariableValue = function(variable, option, recursive) { variable.current = option; @@ -51,10 +72,12 @@ function (angular, _, kbn) { }; this.updateOptions = function(variable) { - if (variable.type === 'time period') { + if (variable.type === 'interval') { variable.options = _.map(variable.query.split(','), function(text) { return { text: text, value: text }; }); + + self.updateAutoInterval(variable); self.setVariableValue(variable, variable.options[0]); return $q.when([]); } diff --git a/src/app/services/timeSrv.js b/src/app/services/timeSrv.js index d560956f90f..7d81d8fe375 100644 --- a/src/app/services/timeSrv.js +++ b/src/app/services/timeSrv.js @@ -61,6 +61,7 @@ define([ this.old_refresh = null; } + $rootScope.emitAppEvent('time-range-changed', this.time); $timeout(this.refreshDashboard, 0); }; diff --git a/src/test/specs/helpers.js b/src/test/specs/helpers.js index e973058da96..35cde542de6 100644 --- a/src/test/specs/helpers.js +++ b/src/test/specs/helpers.js @@ -49,20 +49,28 @@ define([ function ServiceTestContext() { var self = this; self.templateSrv = new TemplateSrvStub(); + self.timeSrv = new TimeSrvStub(); + self.datasourceSrv = {}; - this.providePhase = function() { + this.providePhase = function(mocks) { return module(function($provide) { - $provide.value('templateSrv', self.templateSrv); + _.each(mocks, function(key) { + $provide.value(key, self[key]); + }); }); }; this.createService = function(name) { - return inject([name, '$q', '$rootScope', '$httpBackend', function(service, $q, $rootScope, $httpBackend) { - self.service = service; + return inject(function($q, $rootScope, $httpBackend, $injector) { self.$q = $q; self.$rootScope = $rootScope; self.$httpBackend = $httpBackend; - }]); + + self.$rootScope.onAppEvent = function() {}; + self.$rootScope.emitAppEvent = function() {}; + + self.service = $injector.get(name); + }); }; } @@ -95,6 +103,7 @@ define([ this.replace = function(text) { return _.template(text, this.data, this.templateSettings); }; + this.updateTemplateData = function() { }; this.variableExists = function() { return false; }; this.highlightVariablesAsHtml = function(str) { return str; }; this.setGrafanaVariable = function(name, value) { diff --git a/src/test/specs/templateValuesSrv-specs.js b/src/test/specs/templateValuesSrv-specs.js index 3e3f34a8249..4461d93faf2 100644 --- a/src/test/specs/templateValuesSrv-specs.js +++ b/src/test/specs/templateValuesSrv-specs.js @@ -1,37 +1,22 @@ define([ 'mocks/dashboard-mock', - 'lodash', + './helpers', 'services/templateValuesSrv' -], function(dashboardMock) { +], function(dashboardMock, helpers) { 'use strict'; describe('templateValuesSrv', function() { - var _templateValuesSrv; - var _dashboard; - var _datasourceSrv = {}; - var _q; - var _rootScope; + var ctx = new helpers.ServiceTestContext(); beforeEach(module('grafana.services')); - beforeEach(module(function($provide) { - $provide.value('datasourceSrv', _datasourceSrv); - $provide.value('templateSrv', { - updateTemplateData: function() {} - }); - _dashboard = dashboardMock.create(); - })); - - beforeEach(inject(function(templateValuesSrv, $rootScope, $q) { - _templateValuesSrv = templateValuesSrv; - _rootScope = $rootScope; - _q = $q; - })); + beforeEach(ctx.providePhase(['datasourceSrv', 'timeSrv', 'templateSrv'])); + beforeEach(ctx.createService('templateValuesSrv')); describe('update time period variable options', function() { - var variable = { type: 'time period', query: 'auto,1s,2h,5h,1d', name: 'test' }; + var variable = { type: 'interval', query: 'auto,1s,2h,5h,1d', name: 'test' }; beforeEach(function() { - _templateValuesSrv.updateOptions(variable); + ctx.service.updateOptions(variable); }); it('should update options array', function() { @@ -43,179 +28,179 @@ define([ function describeUpdateVariable(desc, fn) { describe(desc, function() { - var ctx = {}; - ctx.setup = function(setupFn) { - ctx.setupFn = setupFn; + var scenario = {}; + scenario.setup = function(setupFn) { + scenario.setupFn = setupFn; }; beforeEach(function() { - ctx.setupFn(); + scenario.setupFn(); var ds = {}; - ds.metricFindQuery = sinon.stub().returns(_q.when(ctx.queryResult)); - _datasourceSrv.get = sinon.stub().returns(ds); + ds.metricFindQuery = sinon.stub().returns(ctx.$q.when(scenario.queryResult)); + ctx.datasourceSrv.get = sinon.stub().returns(ds); - _templateValuesSrv.updateOptions(ctx.variable); - _rootScope.$digest(); + ctx.service.updateOptions(scenario.variable); + ctx.$rootScope.$digest(); }); - fn(ctx); + fn(scenario); }); } - describeUpdateVariable('time period variable ', function(ctx) { - ctx.setup(function() { - ctx.variable = { type: 'time period', query: 'auto,1s,2h,5h,1d', name: 'test' }; + describeUpdateVariable('time period variable ', function(scenario) { + scenario.setup(function() { + scenario.variable = { type: 'interval', query: 'auto,1s,2h,5h,1d', name: 'test' }; }); it('should update options array', function() { - expect(ctx.variable.options.length).to.be(5); - expect(ctx.variable.options[1].text).to.be('1s'); - expect(ctx.variable.options[1].value).to.be('1s'); + expect(scenario.variable.options.length).to.be(5); + expect(scenario.variable.options[1].text).to.be('1s'); + expect(scenario.variable.options[1].value).to.be('1s'); }); }); - describeUpdateVariable('basic query variable', function(ctx) { - ctx.setup(function() { - ctx.variable = { type: 'query', query: 'apps.*', name: 'test' }; - ctx.queryResult = [{text: 'backend1'}, {text: 'backend2'}]; + describeUpdateVariable('basic query variable', function(scenario) { + scenario.setup(function() { + scenario.variable = { type: 'query', query: 'apps.*', name: 'test' }; + scenario.queryResult = [{text: 'backend1'}, {text: 'backend2'}]; }); it('should update options array', function() { - expect(ctx.variable.options.length).to.be(2); - expect(ctx.variable.options[0].text).to.be('backend1'); - expect(ctx.variable.options[0].value).to.be('backend1'); - expect(ctx.variable.options[1].value).to.be('backend2'); + expect(scenario.variable.options.length).to.be(2); + expect(scenario.variable.options[0].text).to.be('backend1'); + expect(scenario.variable.options[0].value).to.be('backend1'); + expect(scenario.variable.options[1].value).to.be('backend2'); }); it('should select first option as value', function() { - expect(ctx.variable.current.value).to.be('backend1'); + expect(scenario.variable.current.value).to.be('backend1'); }); }); - describeUpdateVariable('and existing value still exists in options', function(ctx) { - ctx.setup(function() { - ctx.variable = { type: 'query', query: 'apps.*', name: 'test' }; - ctx.variable.current = { value: 'backend2'}; - ctx.queryResult = [{text: 'backend1'}, {text: 'backend2'}]; + describeUpdateVariable('and existing value still exists in options', function(scenario) { + scenario.setup(function() { + scenario.variable = { type: 'query', query: 'apps.*', name: 'test' }; + scenario.variable.current = { value: 'backend2'}; + scenario.queryResult = [{text: 'backend1'}, {text: 'backend2'}]; }); it('should keep variable value', function() { - expect(ctx.variable.current.value).to.be('backend2'); + expect(scenario.variable.current.value).to.be('backend2'); }); }); - describeUpdateVariable('and regex pattern exists', function(ctx) { - ctx.setup(function() { - ctx.variable = { type: 'query', query: 'apps.*', name: 'test' }; - ctx.variable.regex = '/apps.*(backend_[0-9]+)/'; - ctx.queryResult = [{text: 'apps.backend.backend_01.counters.req'}, {text: 'apps.backend.backend_02.counters.req'}]; + describeUpdateVariable('and regex pattern exists', function(scenario) { + scenario.setup(function() { + scenario.variable = { type: 'query', query: 'apps.*', name: 'test' }; + scenario.variable.regex = '/apps.*(backend_[0-9]+)/'; + scenario.queryResult = [{text: 'apps.backend.backend_01.counters.req'}, {text: 'apps.backend.backend_02.counters.req'}]; }); it('should extract and use match group', function() { - expect(ctx.variable.options[0].value).to.be('backend_01'); + expect(scenario.variable.options[0].value).to.be('backend_01'); }); }); - describeUpdateVariable('and regex pattern exists and no match', function(ctx) { - ctx.setup(function() { - ctx.variable = { type: 'query', query: 'apps.*', name: 'test' }; - ctx.variable.regex = '/apps.*(backendasd[0-9]+)/'; - ctx.queryResult = [{text: 'apps.backend.backend_01.counters.req'}, {text: 'apps.backend.backend_02.counters.req'}]; + describeUpdateVariable('and regex pattern exists and no match', function(scenario) { + scenario.setup(function() { + scenario.variable = { type: 'query', query: 'apps.*', name: 'test' }; + scenario.variable.regex = '/apps.*(backendasd[0-9]+)/'; + scenario.queryResult = [{text: 'apps.backend.backend_01.counters.req'}, {text: 'apps.backend.backend_02.counters.req'}]; }); it('should not add non matching items', function() { - expect(ctx.variable.options.length).to.be(0); + expect(scenario.variable.options.length).to.be(0); }); }); - describeUpdateVariable('regex pattern without slashes', function(ctx) { - ctx.setup(function() { - ctx.variable = { type: 'query', query: 'apps.*', name: 'test' }; - ctx.variable.regex = 'backend_01'; - ctx.queryResult = [{text: 'apps.backend.backend_01.counters.req'}, {text: 'apps.backend.backend_02.counters.req'}]; + describeUpdateVariable('regex pattern without slashes', function(scenario) { + scenario.setup(function() { + scenario.variable = { type: 'query', query: 'apps.*', name: 'test' }; + scenario.variable.regex = 'backend_01'; + scenario.queryResult = [{text: 'apps.backend.backend_01.counters.req'}, {text: 'apps.backend.backend_02.counters.req'}]; }); it('should return matches options', function() { - expect(ctx.variable.options.length).to.be(1); + expect(scenario.variable.options.length).to.be(1); }); }); - describeUpdateVariable('regex pattern remove duplicates', function(ctx) { - ctx.setup(function() { - ctx.variable = { type: 'query', query: 'apps.*', name: 'test' }; - ctx.variable.regex = 'backend_01'; - ctx.queryResult = [{text: 'apps.backend.backend_01.counters.req'}, {text: 'apps.backend.backend_01.counters.req'}]; + describeUpdateVariable('regex pattern remove duplicates', function(scenario) { + scenario.setup(function() { + scenario.variable = { type: 'query', query: 'apps.*', name: 'test' }; + scenario.variable.regex = 'backend_01'; + scenario.queryResult = [{text: 'apps.backend.backend_01.counters.req'}, {text: 'apps.backend.backend_01.counters.req'}]; }); it('should return matches options', function() { - expect(ctx.variable.options.length).to.be(1); + expect(scenario.variable.options.length).to.be(1); }); }); - describeUpdateVariable('and existing value still exists in options', function(ctx) { - ctx.setup(function() { - ctx.variable = { type: 'query', query: 'apps.*', name: 'test' }; - ctx.variable.current = { value: 'backend2'}; - ctx.queryResult = [{text: 'backend1'}, {text: 'backend2'}]; + describeUpdateVariable('and existing value still exists in options', function(scenario) { + scenario.setup(function() { + scenario.variable = { type: 'query', query: 'apps.*', name: 'test' }; + scenario.variable.current = { value: 'backend2'}; + scenario.queryResult = [{text: 'backend1'}, {text: 'backend2'}]; }); it('should keep variable value', function() { - expect(ctx.variable.current.value).to.be('backend2'); + expect(scenario.variable.current.value).to.be('backend2'); }); }); - describeUpdateVariable('with include All glob syntax', function(ctx) { - ctx.setup(function() { - ctx.variable = { type: 'query', query: 'apps.*', name: 'test', includeAll: true, allFormat: 'glob' }; - ctx.queryResult = [{text: 'backend1'}, {text: 'backend2'}, { text: 'backend3'}]; + describeUpdateVariable('with include All glob syntax', function(scenario) { + scenario.setup(function() { + scenario.variable = { type: 'query', query: 'apps.*', name: 'test', includeAll: true, allFormat: 'glob' }; + scenario.queryResult = [{text: 'backend1'}, {text: 'backend2'}, { text: 'backend3'}]; }); it('should add All Glob option', function() { - expect(ctx.variable.options[0].value).to.be('{backend1,backend2,backend3}'); + expect(scenario.variable.options[0].value).to.be('{backend1,backend2,backend3}'); }); }); - describeUpdateVariable('with include all wildcard', function(ctx) { - ctx.setup(function() { - ctx.variable = { type: 'query', query: 'apps.*', name: 'test', includeAll: true, allFormat: 'wildcard' }; - ctx.queryResult = [{text: 'backend1'}, {text: 'backend2'}, { text: 'backend3'}]; + describeUpdateVariable('with include all wildcard', function(scenario) { + scenario.setup(function() { + scenario.variable = { type: 'query', query: 'apps.*', name: 'test', includeAll: true, allFormat: 'wildcard' }; + scenario.queryResult = [{text: 'backend1'}, {text: 'backend2'}, { text: 'backend3'}]; }); it('should add All wildcard option', function() { - expect(ctx.variable.options[0].value).to.be('*'); + expect(scenario.variable.options[0].value).to.be('*'); }); }); - describeUpdateVariable('with include all wildcard', function(ctx) { - ctx.setup(function() { - ctx.variable = { type: 'query', query: 'apps.*', name: 'test', includeAll: true, allFormat: 'regex wildcard' }; - ctx.queryResult = [{text: 'backend1'}, {text: 'backend2'}, { text: 'backend3'}]; + describeUpdateVariable('with include all wildcard', function(scenario) { + scenario.setup(function() { + scenario.variable = { type: 'query', query: 'apps.*', name: 'test', includeAll: true, allFormat: 'regex wildcard' }; + scenario.queryResult = [{text: 'backend1'}, {text: 'backend2'}, { text: 'backend3'}]; }); it('should add All wildcard option', function() { - expect(ctx.variable.options[0].value).to.be('.*'); + expect(scenario.variable.options[0].value).to.be('.*'); }); }); - describeUpdateVariable('with include all regex values', function(ctx) { - ctx.setup(function() { - ctx.variable = { type: 'query', query: 'apps.*', name: 'test', includeAll: true, allFormat: 'wildcard' }; - ctx.queryResult = [{text: 'backend1'}, {text: 'backend2'}, { text: 'backend3'}]; + describeUpdateVariable('with include all regex values', function(scenario) { + scenario.setup(function() { + scenario.variable = { type: 'query', query: 'apps.*', name: 'test', includeAll: true, allFormat: 'wildcard' }; + scenario.queryResult = [{text: 'backend1'}, {text: 'backend2'}, { text: 'backend3'}]; }); it('should add All wildcard option', function() { - expect(ctx.variable.options[0].value).to.be('*'); + expect(scenario.variable.options[0].value).to.be('*'); }); }); - describeUpdateVariable('with include all glob no values', function(ctx) { - ctx.setup(function() { - ctx.variable = { type: 'query', query: 'apps.*', name: 'test', includeAll: true, allFormat: 'glob' }; - ctx.queryResult = []; + describeUpdateVariable('with include all glob no values', function(scenario) { + scenario.setup(function() { + scenario.variable = { type: 'query', query: 'apps.*', name: 'test', includeAll: true, allFormat: 'glob' }; + scenario.queryResult = []; }); it('should add empty glob', function() { - expect(ctx.variable.options[0].value).to.be('{}'); + expect(scenario.variable.options[0].value).to.be('{}'); }); }); diff --git a/src/test/specs/timeSrv-specs.js b/src/test/specs/timeSrv-specs.js index 615656afcdf..a0bc053b1e1 100644 --- a/src/test/specs/timeSrv-specs.js +++ b/src/test/specs/timeSrv-specs.js @@ -1,35 +1,35 @@ define([ 'mocks/dashboard-mock', + './helpers', 'lodash', 'services/timeSrv' -], function(dashboardMock, _) { +], function(dashboardMock, helpers, _) { 'use strict'; describe('timeSrv', function() { - var _timeSrv; + var ctx = new helpers.ServiceTestContext(); var _dashboard; beforeEach(module('grafana.services')); - beforeEach(inject(function(timeSrv) { - _timeSrv = timeSrv; - _dashboard = dashboardMock.create(); - })); + beforeEach(ctx.providePhase()); + beforeEach(ctx.createService('timeSrv')); beforeEach(function() { - _timeSrv.init(_dashboard); + _dashboard = dashboardMock.create(); + ctx.service.init(_dashboard); }); describe('timeRange', function() { it('should return unparsed when parse is false', function() { - _timeSrv.setTime({from: 'now', to: 'now-1h' }); - var time = _timeSrv.timeRange(false); + ctx.service.setTime({from: 'now', to: 'now-1h' }); + var time = ctx.service.timeRange(false); expect(time.from).to.be('now'); expect(time.to).to.be('now-1h'); }); it('should return parsed when parse is true', function() { - _timeSrv.setTime({from: 'now', to: 'now-1h' }); - var time = _timeSrv.timeRange(true); + ctx.service.setTime({from: 'now', to: 'now-1h' }); + var time = ctx.service.timeRange(true); expect(_.isDate(time.from)).to.be(true); expect(_.isDate(time.to)).to.be(true); }); @@ -39,15 +39,15 @@ define([ it('should return disable refresh for absolute times', function() { _dashboard.refresh = false; - _timeSrv.setTime({from: '2011-01-01', to: '2015-01-01' }); + ctx.service.setTime({from: '2011-01-01', to: '2015-01-01' }); expect(_dashboard.refresh).to.be(false); }); it('should restore refresh after relative time range is set', function() { _dashboard.refresh = '10s'; - _timeSrv.setTime({from: '2011-01-01', to: '2015-01-01' }); + ctx.service.setTime({from: '2011-01-01', to: '2015-01-01' }); expect(_dashboard.refresh).to.be(false); - _timeSrv.setTime({from: '2011-01-01', to: 'now' }); + ctx.service.setTime({from: '2011-01-01', to: 'now' }); expect(_dashboard.refresh).to.be('10s'); }); });