Selaa lähdekoodia

working on auto interval template variable support

Torkel Ödegaard 11 vuotta sitten
vanhempi
commit
4e5dcafa1b

+ 1 - 1
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';
       }
     };

+ 1 - 1
src/app/partials/panelgeneral.html

@@ -8,7 +8,7 @@
       <label class="small">Span</label> <select class="input-mini" ng-model="panel.span" ng-options="f for f in [0,1,2,3,4,5,6,7,8,9,10,11,12]"></select>
     </div>
 		<div class="editor-option">
-      <label class="small">Height</label><input type="text" class="input-medium" ng-model='panel.height'></select>
+      <label class="small">Height</label><input type="text" class="input-small" ng-model='panel.height'></select>
     </div>
   </div>
 </div>

+ 19 - 7
src/app/partials/templating_editor.html

@@ -58,7 +58,7 @@
 						</div>
 						<div class="editor-option">
 							<label class="small">Type</label>
-							<select class="input-medium" ng-model="current.type" ng-options="f for f in ['query', 'time period']" ng-change="typeChanged()"></select>
+							<select class="input-medium" ng-model="current.type" ng-options="f for f in ['query', 'interval', 'custom']" ng-change="typeChanged()"></select>
 						</div>
 						<div class="editor-option" ng-show="current.type === 'query'">
 							<label class="small">Datasource</label>
@@ -70,10 +70,22 @@
 						</div>
 					</div>
 
-					<div ng-show="current.type === 'time period'">
-						<div class="editor-option">
-							<label class="small">Values</label>
-							<input type="text" class="input-xxlarge" ng-model='current.query' placeholder="name"></input>
+					<div ng-show="current.type === 'interval'">
+						<div class="editor-row">
+							<div class="editor-option">
+								<label class="small">Values</label>
+								<input type="text" class="input-xxlarge" ng-model='current.query' ng-blur="runQuery()" placeholder="name"></input>
+							</div>
+						</div>
+						<div class="editor-row">
+							<div class="editor-option text-center">
+								<label class="small">Include auto interval</label>
+								<input type="checkbox" ng-model="current.auto" ng-checked="current.auto" ng-change="runQuery()">
+							</div>
+							<div class="editor-option" ng-show="current.auto">
+								<label class="small">Auto interval steps <tip>The number of times the time range should be divided to calculate the interval<tip></label>
+								<select class="input-mini" ng-model="current.auto_count" ng-options="f for f in [3,4,5,6,7,8,9,10,13,15,16,20,25,30,35,40,50,100,200]" ng-change="runQuery()"></select>
+							</div>
 						</div>
 					</div>
 
@@ -101,7 +113,7 @@
 							</div>
 							<div class="editor-option" ng-show="current.includeAll">
 								<label class="small">All format</label>
-								<select class="input-medium" ng-model="current.allFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'wildcard', 'regex wildcard', 'regex all values', 'comma list', 'custom']" ng-change="typeChanged()"></select>
+								<select class="input-medium" ng-model="current.allFormat" ng-change="runQuery()" ng-options="f for f in ['glob', 'wildcard', 'regex wildcard', 'regex all values', 'comma list', 'custom']"></select>
 							</div>
 							<div class="editor-option" ng-show="current.includeAll">
 								<label class="small">All value</label>
@@ -119,7 +131,7 @@
 									{{option.text}}
 								</li>
 							</ul>
-					 	</div>
+						</div>
 					</div>
 				</div>
 			</div>

+ 24 - 15
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 '<span class="template-variable">' + match + '</span>';
         }
@@ -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;
       });
     };
 

+ 26 - 3
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,7 +27,22 @@ 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) {
@@ -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([]);
       }

+ 1 - 0
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);
     };
 

+ 14 - 5
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) {

+ 92 - 107
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('{}');
       });
     });
 

+ 14 - 14
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');
       });
     });