瀏覽代碼

fix(browser history): fixes and enhancements to browser history, it now works properly again AND it can restore previous time ranges in dashboards, closes #7259

Torkel Ödegaard 9 年之前
父節點
當前提交
49fe74228b

+ 1 - 0
CHANGELOG.md

@@ -5,6 +5,7 @@
 * **Templating**: Make $__interval and $__interval_ms global built in variables that can be used in by any datasource (in panel queries), closes [#7190](https://github.com/grafana/grafana/issues/7190), closes [#6582](https://github.com/grafana/grafana/issues/6582)
 * **Templating**: Make $__interval and $__interval_ms global built in variables that can be used in by any datasource (in panel queries), closes [#7190](https://github.com/grafana/grafana/issues/7190), closes [#6582](https://github.com/grafana/grafana/issues/6582)
 * **S3 Image Store**: External s3 image store (used in alert notifications) now support AWS IAM Roles, closes [#6985](https://github.com/grafana/grafana/issues/6985), [#7058](https://github.com/grafana/grafana/issues/7058) thx [@mtanda](https://github.com/mtanda)
 * **S3 Image Store**: External s3 image store (used in alert notifications) now support AWS IAM Roles, closes [#6985](https://github.com/grafana/grafana/issues/6985), [#7058](https://github.com/grafana/grafana/issues/7058) thx [@mtanda](https://github.com/mtanda)
 * **Optimzation**: Never issue refresh event when Grafana tab is not visible [#7218](https://github.com/grafana/grafana/issues/7218), thx [@mtanda](https://github.com/mtanda)
 * **Optimzation**: Never issue refresh event when Grafana tab is not visible [#7218](https://github.com/grafana/grafana/issues/7218), thx [@mtanda](https://github.com/mtanda)
+* **Browser History**: Browser back/forward now works time ranges / zoom, [#7259](https://github.com/grafana/grafana/issues/7259)
 
 
 # 4.1.1 (2017-01-11)
 # 4.1.1 (2017-01-11)
 
 

+ 1 - 1
public/app/core/services/timer.js

@@ -22,7 +22,7 @@ function (angular, _, coreModule) {
       $timeout.cancel(promise);
       $timeout.cancel(promise);
     };
     };
 
 
-    this.cancel_all = function() {
+    this.cancelAll = function() {
       _.each(timers, function(t) {
       _.each(timers, function(t) {
         $timeout.cancel(t);
         $timeout.cancel(t);
       });
       });

+ 1 - 1
public/app/features/dashboard/all.js

@@ -9,7 +9,7 @@ define([
   './shareSnapshotCtrl',
   './shareSnapshotCtrl',
   './dashboard_srv',
   './dashboard_srv',
   './viewStateSrv',
   './viewStateSrv',
-  './timeSrv',
+  './time_srv',
   './unsavedChangesSrv',
   './unsavedChangesSrv',
   './timepicker/timepicker',
   './timepicker/timepicker',
   './graphiteImportCtrl',
   './graphiteImportCtrl',

+ 110 - 0
public/app/features/dashboard/specs/time_srv_specs.ts

@@ -0,0 +1,110 @@
+import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common';
+
+import helpers from 'test/specs/helpers';
+import _ from 'lodash';
+import TimeSrv from '../time_srv';
+import moment from 'moment';
+
+describe('timeSrv', function() {
+  var ctx = new helpers.ServiceTestContext();
+  var _dashboard: any = {
+    time: {from: 'now-6h', to: 'now'},
+  };
+
+  beforeEach(angularMocks.module('grafana.core'));
+  beforeEach(angularMocks.module('grafana.services'));
+  beforeEach(ctx.createService('timeSrv'));
+
+  beforeEach(function() {
+    ctx.service.init(_dashboard);
+  });
+
+  describe('timeRange', function() {
+    it('should return unparsed when parse is false', function() {
+      ctx.service.setTime({from: 'now', to: 'now-1h' });
+      var time = ctx.service.timeRange();
+      expect(time.raw.from).to.be('now');
+      expect(time.raw.to).to.be('now-1h');
+    });
+
+    it('should return parsed when parse is true', function() {
+      ctx.service.setTime({from: 'now', to: 'now-1h' });
+      var time = ctx.service.timeRange();
+      expect(moment.isMoment(time.from)).to.be(true);
+      expect(moment.isMoment(time.to)).to.be(true);
+    });
+  });
+
+  describe('init time from url', function() {
+    it('should handle relative times', function() {
+      ctx.$location.search({from: 'now-2d', to: 'now'});
+      ctx.service.init(_dashboard);
+      var time = ctx.service.timeRange();
+      expect(time.raw.from).to.be('now-2d');
+      expect(time.raw.to).to.be('now');
+    });
+
+    it('should handle formated dates', function() {
+      ctx.$location.search({from: '20140410T052010', to: '20140520T031022'});
+      ctx.service.init(_dashboard);
+      var time = ctx.service.timeRange(true);
+      expect(time.from.valueOf()).to.equal(new Date("2014-04-10T05:20:10Z").getTime());
+      expect(time.to.valueOf()).to.equal(new Date("2014-05-20T03:10:22Z").getTime());
+    });
+
+    it('should handle formated dates without time', function() {
+      ctx.$location.search({from: '20140410', to: '20140520'});
+      ctx.service.init(_dashboard);
+      var time = ctx.service.timeRange(true);
+      expect(time.from.valueOf()).to.equal(new Date("2014-04-10T00:00:00Z").getTime());
+      expect(time.to.valueOf()).to.equal(new Date("2014-05-20T00:00:00Z").getTime());
+    });
+
+    it('should handle epochs', function() {
+      ctx.$location.search({from: '1410337646373', to: '1410337665699'});
+      ctx.service.init(_dashboard);
+      var time = ctx.service.timeRange(true);
+      expect(time.from.valueOf()).to.equal(1410337646373);
+      expect(time.to.valueOf()).to.equal(1410337665699);
+    });
+
+    it('should handle bad dates', function() {
+      ctx.$location.search({from: '20151126T00010%3C%2Fp%3E%3Cspan%20class', to: 'now'});
+      _dashboard.time.from = 'now-6h';
+      ctx.service.init(_dashboard);
+      expect(ctx.service.time.from).to.equal('now-6h');
+      expect(ctx.service.time.to).to.equal('now');
+    });
+  });
+
+  describe('setTime', function() {
+    it('should return disable refresh if refresh is disabled for any range', function() {
+      _dashboard.refresh = false;
+
+      ctx.service.setTime({from: '2011-01-01', to: '2015-01-01' });
+      expect(_dashboard.refresh).to.be(false);
+    });
+
+    it('should restore refresh for absolute time range', function() {
+      _dashboard.refresh = '30s';
+
+      ctx.service.setTime({from: '2011-01-01', to: '2015-01-01' });
+      expect(_dashboard.refresh).to.be('30s');
+    });
+
+    it('should restore refresh after relative time range is set', function() {
+      _dashboard.refresh = '10s';
+      ctx.service.setTime({from: moment([2011,1,1]), to: moment([2015,1,1])});
+      expect(_dashboard.refresh).to.be(false);
+      ctx.service.setTime({from: '2011-01-01', to: 'now' });
+      expect(_dashboard.refresh).to.be('10s');
+    });
+
+    it('should keep refresh after relative time range is changed and now delay exists', function() {
+      _dashboard.refresh = '10s';
+      ctx.service.setTime({from: 'now-1h', to: 'now-10s' });
+      expect(_dashboard.refresh).to.be('10s');
+    });
+  });
+
+});

+ 0 - 170
public/app/features/dashboard/timeSrv.js

@@ -1,170 +0,0 @@
-define([
-  'angular',
-  'lodash',
-  'moment',
-  'app/core/config',
-  'app/core/utils/kbn',
-  'app/core/utils/datemath'
-], function (angular, _, moment, config, kbn, dateMath) {
-  'use strict';
-
-  var module = angular.module('grafana.services');
-
-  module.service('timeSrv', function($rootScope, $timeout, $routeParams, timer, contextSrv) {
-    var self = this;
-
-    // default time
-    this.time = {from: '6h', to: 'now'};
-
-    $rootScope.$on('zoom-out', function(e, factor) { self.zoomOut(factor); });
-
-    this.init = function(dashboard) {
-      timer.cancel_all();
-
-      this.dashboard = dashboard;
-      this.time = dashboard.time;
-      this.refresh = dashboard.refresh;
-
-      this._initTimeFromUrl();
-      this._parseTime();
-
-      if(this.refresh) {
-        this.setAutoRefresh(this.refresh);
-      }
-    };
-
-    this._parseTime = function() {
-      // when absolute time is saved in json it is turned to a string
-      if (_.isString(this.time.from) && this.time.from.indexOf('Z') >= 0) {
-        this.time.from = moment(this.time.from).utc();
-      }
-      if (_.isString(this.time.to) && this.time.to.indexOf('Z') >= 0) {
-        this.time.to = moment(this.time.to).utc();
-      }
-    };
-
-    this._parseUrlParam = function(value) {
-      if (value.indexOf('now') !== -1) {
-        return value;
-      }
-      if (value.length === 8) {
-        return moment.utc(value, 'YYYYMMDD');
-      }
-      if (value.length === 15) {
-        return moment.utc(value, 'YYYYMMDDTHHmmss');
-      }
-
-      if (!isNaN(value)) {
-        var epoch = parseInt(value);
-        return moment.utc(epoch);
-      }
-
-      return null;
-    };
-
-    this._initTimeFromUrl = function() {
-      if ($routeParams.from) {
-        this.time.from = this._parseUrlParam($routeParams.from) || this.time.from;
-      }
-      if ($routeParams.to) {
-        this.time.to = this._parseUrlParam($routeParams.to) || this.time.to;
-      }
-      if ($routeParams.refresh) {
-        this.refresh = $routeParams.refresh || this.refresh;
-      }
-    };
-
-    this.setAutoRefresh = function (interval) {
-      this.dashboard.refresh = interval;
-      if (interval) {
-        var interval_ms = kbn.interval_to_ms(interval);
-        $timeout(function () {
-          self.start_scheduled_refresh(interval_ms);
-          self.refreshDashboard();
-        }, interval_ms);
-      } else {
-        this.cancel_scheduled_refresh();
-      }
-    };
-
-    this.refreshDashboard = function() {
-      $rootScope.$broadcast('refresh');
-    };
-
-    this.start_scheduled_refresh = function (after_ms) {
-      self.cancel_scheduled_refresh();
-      self.refresh_timer = timer.register($timeout(function () {
-        self.start_scheduled_refresh(after_ms);
-        if (contextSrv.isGrafanaVisible()) {
-          self.refreshDashboard();
-        }
-      }, after_ms));
-    };
-
-    this.cancel_scheduled_refresh = function () {
-      timer.cancel(this.refresh_timer);
-    };
-
-    this.setTime = function(time, enableRefresh) {
-      _.extend(this.time, time);
-
-      // disable refresh if zoom in or zoom out
-      if (!enableRefresh && moment.isMoment(time.to)) {
-        this.old_refresh = this.dashboard.refresh || this.old_refresh;
-        this.setAutoRefresh(false);
-      }
-      else if (this.old_refresh && this.old_refresh !== this.dashboard.refresh) {
-        this.setAutoRefresh(this.old_refresh);
-        this.old_refresh = null;
-      }
-
-      $rootScope.appEvent('time-range-changed', this.time);
-      $timeout(this.refreshDashboard, 0);
-    };
-
-    this.timeRangeForUrl = function() {
-      var range = this.timeRange().raw;
-
-      if (moment.isMoment(range.from)) { range.from = range.from.valueOf(); }
-      if (moment.isMoment(range.to)) { range.to = range.to.valueOf(); }
-
-      return range;
-    };
-
-    this.timeRange = function() {
-      // make copies if they are moment  (do not want to return out internal moment, because they are mutable!)
-      var range = {
-        from: moment.isMoment(this.time.from) ? moment(this.time.from) : this.time.from,
-        to: moment.isMoment(this.time.to) ? moment(this.time.to) : this.time.to,
-      };
-
-      range = {
-        from: dateMath.parse(range.from, false),
-        to: dateMath.parse(range.to, true),
-        raw: range
-      };
-
-      return range;
-    };
-
-    this.zoomOut = function(factor) {
-      var range = this.timeRange();
-
-      var timespan = (range.to.valueOf() - range.from.valueOf());
-      var center = range.to.valueOf() - timespan/2;
-
-      var to = (center + (timespan*factor)/2);
-      var from = (center - (timespan*factor)/2);
-
-      if (to > Date.now() && range.to <= Date.now()) {
-        var offset = to - Date.now();
-        from = from - offset;
-        to = Date.now();
-      }
-
-      this.setTime({from: moment.utc(from), to: moment.utc(to) });
-    };
-
-  });
-
-});

+ 204 - 0
public/app/features/dashboard/time_srv.ts

@@ -0,0 +1,204 @@
+///<reference path="../../headers/common.d.ts" />
+
+import config from 'app/core/config';
+import angular from 'angular';
+import moment from 'moment';
+import _ from 'lodash';
+import coreModule from 'app/core/core_module';
+import kbn from 'app/core/utils/kbn';
+import * as dateMath from 'app/core/utils/datemath';
+
+class TimeSrv {
+  time: any;
+  refreshTimer: any;
+  refresh: boolean;
+  oldRefresh: boolean;
+  dashboard: any;
+  timeAtLoad: any;
+
+  /** @ngInject **/
+  constructor(private $rootScope, private $timeout, private $location, private timer, private contextSrv) {
+    // default time
+    this.time = {from: '6h', to: 'now'};
+
+    $rootScope.$on('zoom-out', this.zoomOut.bind(this));
+    $rootScope.$on('$routeUpdate', this.routeUpdated.bind(this));
+  }
+
+  init(dashboard) {
+    this.timer.cancelAll();
+
+    this.dashboard = dashboard;
+    this.time = dashboard.time;
+    this.refresh = dashboard.refresh;
+
+    this.initTimeFromUrl();
+    this.parseTime();
+
+    // remember time at load so we can go back to it
+    this.timeAtLoad = _.cloneDeep(this.time);
+
+    if (this.refresh) {
+      this.setAutoRefresh(this.refresh);
+    }
+  }
+
+  private parseTime() {
+    // when absolute time is saved in json it is turned to a string
+    if (_.isString(this.time.from) && this.time.from.indexOf('Z') >= 0) {
+      this.time.from = moment(this.time.from).utc();
+    }
+    if (_.isString(this.time.to) && this.time.to.indexOf('Z') >= 0) {
+      this.time.to = moment(this.time.to).utc();
+    }
+  };
+
+  private parseUrlParam(value) {
+    if (value.indexOf('now') !== -1) {
+      return value;
+    }
+    if (value.length === 8) {
+      return moment.utc(value, 'YYYYMMDD');
+    }
+    if (value.length === 15) {
+      return moment.utc(value, 'YYYYMMDDTHHmmss');
+    }
+
+    if (!isNaN(value)) {
+      var epoch = parseInt(value);
+      return moment.utc(epoch);
+    }
+
+    return null;
+  }
+
+  private initTimeFromUrl() {
+    var params = this.$location.search();
+    if (params.from) {
+      this.time.from = this.parseUrlParam(params.from) || this.time.from;
+    }
+    if (params.to) {
+      this.time.to = this.parseUrlParam(params.to) || this.time.to;
+    }
+    if (params.refresh) {
+      this.refresh = params.refresh || this.refresh;
+    }
+  };
+
+  private routeUpdated() {
+    var params = this.$location.search();
+    var urlRange = this.timeRangeForUrl();
+    // check if url has time range
+    if (params.from && params.to) {
+      // is it different from what our current time range?
+      if (params.from !== urlRange.from || params.to !== urlRange.to) {
+        // issue update
+        this.initTimeFromUrl();
+        this.setTime(this.time, true);
+      }
+    } else {
+      this.setTime(this.timeAtLoad, true);
+    }
+  }
+
+  setAutoRefresh(interval) {
+    this.dashboard.refresh = interval;
+    if (interval) {
+      var intervalMs = kbn.interval_to_ms(interval);
+
+      this.$timeout(() => {
+        this.startNextRefreshTimer(intervalMs);
+        this.refreshDashboard();
+      }, intervalMs);
+
+    } else {
+      this.cancelNextRefresh();
+    }
+  }
+
+  refreshDashboard() {
+    this.$rootScope.$broadcast('refresh');
+  }
+
+  private startNextRefreshTimer(afterMs) {
+    this.cancelNextRefresh();
+    this.refreshTimer = this.timer.register(this.$timeout(() => {
+      this.startNextRefreshTimer(afterMs);
+      if (this.contextSrv.isGrafanaVisible()) {
+        this.refreshDashboard();
+      }
+    }, afterMs));
+  }
+
+  private cancelNextRefresh() {
+    this.timer.cancel(this.refreshTimer);
+  };
+
+  setTime(time, fromRouteUpdate?) {
+    _.extend(this.time, time);
+
+    // disable refresh if zoom in or zoom out
+    if (moment.isMoment(time.to)) {
+      this.oldRefresh = this.dashboard.refresh || this.oldRefresh;
+      this.setAutoRefresh(false);
+    } else if (this.oldRefresh && this.oldRefresh !== this.dashboard.refresh) {
+      this.setAutoRefresh(this.oldRefresh);
+      this.oldRefresh = null;
+    }
+
+    // update url
+    if (fromRouteUpdate !== true) {
+      var urlRange = this.timeRangeForUrl();
+      var urlParams = this.$location.search();
+      urlParams.from = urlRange.from;
+      urlParams.to = urlRange.to;
+      this.$location.search(urlParams);
+    }
+
+    this.$rootScope.appEvent('time-range-changed', this.time);
+    this.$timeout(this.refreshDashboard.bind(this), 0);
+  }
+
+  timeRangeForUrl() {
+    var range = this.timeRange().raw;
+
+    if (moment.isMoment(range.from)) { range.from = range.from.valueOf(); }
+    if (moment.isMoment(range.to)) { range.to = range.to.valueOf(); }
+
+    return range;
+  }
+
+  timeRange() {
+    // make copies if they are moment  (do not want to return out internal moment, because they are mutable!)
+    var raw = {
+      from: moment.isMoment(this.time.from) ? moment(this.time.from) : this.time.from,
+      to: moment.isMoment(this.time.to) ? moment(this.time.to) : this.time.to,
+    };
+
+    return {
+      from: dateMath.parse(raw.from, false),
+      to: dateMath.parse(raw.to, true),
+      raw: raw
+    };
+  }
+
+  zoomOut(e, factor) {
+    var range = this.timeRange();
+
+    var timespan = (range.to.valueOf() - range.from.valueOf());
+    var center = range.to.valueOf() - timespan/2;
+
+    var to = (center + (timespan*factor)/2);
+    var from = (center - (timespan*factor)/2);
+
+    if (to > Date.now() && range.to <= Date.now()) {
+      var offset = to - Date.now();
+      from = from - offset;
+      to = Date.now();
+    }
+
+    this.setTime({from: moment.utc(from), to: moment.utc(to)});
+  }
+}
+
+coreModule.service('timeSrv', TimeSrv);

+ 2 - 3
public/app/features/dashboard/timepicker/timepicker.ts

@@ -97,8 +97,7 @@ export class TimePickerCtrl {
       from = range.from.valueOf();
       from = range.from.valueOf();
     }
     }
 
 
-    this.timeSrv.setTime({from: moment.utc(from), to: moment.utc(to) });
-
+    this.timeSrv.setTime({from: moment.utc(from), to: moment.utc(to)});
   }
   }
 
 
   openDropdown() {
   openDropdown() {
@@ -126,7 +125,7 @@ export class TimePickerCtrl {
       this.timeSrv.setAutoRefresh(this.refresh.value);
       this.timeSrv.setAutoRefresh(this.refresh.value);
     }
     }
 
 
-    this.timeSrv.setTime(this.timeRaw, true);
+    this.timeSrv.setTime(this.timeRaw);
     this.$rootScope.appEvent('hide-dash-editor');
     this.$rootScope.appEvent('hide-dash-editor');
   }
   }
 
 

+ 8 - 12
public/app/features/dashboard/viewStateSrv.js

@@ -8,7 +8,7 @@ function (angular, _, $) {
 
 
   var module = angular.module('grafana.services');
   var module = angular.module('grafana.services');
 
 
-  module.factory('dashboardViewStateSrv', function($location, $timeout, templateSrv, contextSrv, timeSrv) {
+  module.factory('dashboardViewStateSrv', function($location, $timeout) {
 
 
     // represents the transient view state
     // represents the transient view state
     // like fullscreen panel & edit
     // like fullscreen panel & edit
@@ -25,15 +25,6 @@ function (angular, _, $) {
         }
         }
       };
       };
 
 
-      // update url on time range change
-      $scope.onAppEvent('time-range-changed', function() {
-        var urlParams = $location.search();
-        var urlRange = timeSrv.timeRangeForUrl();
-        urlParams.from = urlRange.from;
-        urlParams.to = urlRange.to;
-        $location.search(urlParams);
-      });
-
       $scope.onAppEvent('$routeUpdate', function() {
       $scope.onAppEvent('$routeUpdate', function() {
         var urlState = self.getQueryStringState();
         var urlState = self.getQueryStringState();
         if (self.needsSync(urlState)) {
         if (self.needsSync(urlState)) {
@@ -82,7 +73,7 @@ function (angular, _, $) {
       return urlState;
       return urlState;
     };
     };
 
 
-    DashboardViewState.prototype.update = function(state) {
+    DashboardViewState.prototype.update = function(state, fromRouteUpdated) {
       // implement toggle logic
       // implement toggle logic
       if (state.toggle) {
       if (state.toggle) {
         delete state.toggle;
         delete state.toggle;
@@ -113,7 +104,12 @@ function (angular, _, $) {
         delete this.state.tab;
         delete this.state.tab;
       }
       }
 
 
-      $location.search(this.serializeToUrl());
+      // do not update url params if we are here
+      // from routeUpdated event
+      if (fromRouteUpdated !== true) {
+        $location.search(this.serializeToUrl());
+      }
+
       this.syncState();
       this.syncState();
     };
     };
 
 

+ 3 - 0
public/app/features/templating/specs/template_srv_specs.ts

@@ -8,6 +8,9 @@ describe('templateSrv', function() {
 
 
   beforeEach(angularMocks.module('grafana.core'));
   beforeEach(angularMocks.module('grafana.core'));
   beforeEach(angularMocks.module('grafana.services'));
   beforeEach(angularMocks.module('grafana.services'));
+  beforeEach(angularMocks.module($provide => {
+    $provide.value('timeSrv', {});
+  }));
 
 
   beforeEach(angularMocks.inject(function(variableSrv, templateSrv) {
   beforeEach(angularMocks.inject(function(variableSrv, templateSrv) {
     _templateSrv = templateSrv;
     _templateSrv = templateSrv;

+ 1 - 1
public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts

@@ -11,7 +11,7 @@ describe('ElasticDatasource', function() {
 
 
   beforeEach(angularMocks.module('grafana.core'));
   beforeEach(angularMocks.module('grafana.core'));
   beforeEach(angularMocks.module('grafana.services'));
   beforeEach(angularMocks.module('grafana.services'));
-  beforeEach(ctx.providePhase(['templateSrv', 'backendSrv']));
+  beforeEach(ctx.providePhase(['templateSrv', 'backendSrv', 'timeSrv']));
 
 
   beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) {
   beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) {
     ctx.$q = $q;
     ctx.$q = $q;

+ 2 - 0
public/app/plugins/datasource/prometheus/specs/datasource_specs.ts

@@ -9,6 +9,8 @@ describe('PrometheusDatasource', function() {
 
 
   beforeEach(angularMocks.module('grafana.core'));
   beforeEach(angularMocks.module('grafana.core'));
   beforeEach(angularMocks.module('grafana.services'));
   beforeEach(angularMocks.module('grafana.services'));
+  beforeEach(ctx.providePhase(['timeSrv']));
+
   beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) {
   beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) {
     ctx.$q = $q;
     ctx.$q = $q;
     ctx.$httpBackend =  $httpBackend;
     ctx.$httpBackend =  $httpBackend;

+ 2 - 2
public/test/specs/helpers.js

@@ -92,7 +92,6 @@ define([
     self.timeSrv = new TimeSrvStub();
     self.timeSrv = new TimeSrvStub();
     self.datasourceSrv = {};
     self.datasourceSrv = {};
     self.backendSrv = {};
     self.backendSrv = {};
-    self.$location = {};
     self.$routeParams = {};
     self.$routeParams = {};
 
 
     this.providePhase = function(mocks) {
     this.providePhase = function(mocks) {
@@ -104,10 +103,11 @@ define([
     };
     };
 
 
     this.createService = function(name) {
     this.createService = function(name) {
-      return inject(function($q, $rootScope, $httpBackend, $injector) {
+      return inject(function($q, $rootScope, $httpBackend, $injector, $location) {
         self.$q = $q;
         self.$q = $q;
         self.$rootScope = $rootScope;
         self.$rootScope = $rootScope;
         self.$httpBackend =  $httpBackend;
         self.$httpBackend =  $httpBackend;
+        self.$location = $location;
 
 
         self.$rootScope.onAppEvent = function() {};
         self.$rootScope.onAppEvent = function() {};
         self.$rootScope.appEvent = function() {};
         self.$rootScope.appEvent = function() {};

+ 1 - 0
public/test/specs/linkSrv-specs.js

@@ -1,5 +1,6 @@
 define([
 define([
   'lodash',
   'lodash',
+  'app/features/dashboard/all',
   'app/features/panellinks/linkSrv'
   'app/features/panellinks/linkSrv'
 ], function(_) {
 ], function(_) {
   'use strict';
   'use strict';

+ 0 - 120
public/test/specs/time_srv_specs.js

@@ -1,120 +0,0 @@
-define([
-  'test/mocks/dashboard-mock',
-  'test/specs/helpers',
-  'lodash',
-  'moment',
-  'app/core/services/timer',
-  'app/features/dashboard/timeSrv'
-], function(dashboardMock, helpers, _, moment) {
-  'use strict';
-
-  describe('timeSrv', function() {
-    var ctx = new helpers.ServiceTestContext();
-    var _dashboard;
-
-    beforeEach(module('grafana.core'));
-    beforeEach(module('grafana.services'));
-    beforeEach(ctx.providePhase(['$routeParams']));
-    beforeEach(ctx.createService('timeSrv'));
-
-    beforeEach(function() {
-      _dashboard = dashboardMock.create();
-      ctx.service.init(_dashboard);
-    });
-
-    describe('timeRange', function() {
-      it('should return unparsed when parse is false', function() {
-        ctx.service.setTime({from: 'now', to: 'now-1h' });
-        var time = ctx.service.timeRange();
-        expect(time.raw.from).to.be('now');
-        expect(time.raw.to).to.be('now-1h');
-      });
-
-      it('should return parsed when parse is true', function() {
-        ctx.service.setTime({from: 'now', to: 'now-1h' });
-        var time = ctx.service.timeRange();
-        expect(moment.isMoment(time.from)).to.be(true);
-        expect(moment.isMoment(time.to)).to.be(true);
-      });
-    });
-
-    describe('init time from url', function() {
-      it('should handle relative times', function() {
-        ctx.$routeParams.from = 'now-2d';
-        ctx.$routeParams.to = 'now';
-        ctx.service.init(_dashboard);
-        var time = ctx.service.timeRange();
-        expect(time.raw.from).to.be('now-2d');
-        expect(time.raw.to).to.be('now');
-      });
-
-      it('should handle formated dates', function() {
-        ctx.$routeParams.from = '20140410T052010';
-        ctx.$routeParams.to = '20140520T031022';
-        ctx.service.init(_dashboard);
-        var time = ctx.service.timeRange(true);
-        expect(time.from.valueOf()).to.equal(new Date("2014-04-10T05:20:10Z").getTime());
-        expect(time.to.valueOf()).to.equal(new Date("2014-05-20T03:10:22Z").getTime());
-      });
-
-      it('should handle formated dates without time', function() {
-        ctx.$routeParams.from = '20140410';
-        ctx.$routeParams.to = '20140520';
-        ctx.service.init(_dashboard);
-        var time = ctx.service.timeRange(true);
-        expect(time.from.valueOf()).to.equal(new Date("2014-04-10T00:00:00Z").getTime());
-        expect(time.to.valueOf()).to.equal(new Date("2014-05-20T00:00:00Z").getTime());
-      });
-
-      it('should handle epochs', function() {
-        ctx.$routeParams.from = '1410337646373';
-        ctx.$routeParams.to = '1410337665699';
-        ctx.service.init(_dashboard);
-        var time = ctx.service.timeRange(true);
-        expect(time.from.valueOf()).to.equal(1410337646373);
-        expect(time.to.valueOf()).to.equal(1410337665699);
-      });
-
-      it('should handle bad dates', function() {
-        ctx.$routeParams.from = '20151126T00010%3C%2Fp%3E%3Cspan%20class';
-        ctx.$routeParams.to = 'now';
-        _dashboard.time.from = 'now-6h';
-        ctx.service.init(_dashboard);
-        expect(ctx.service.time.from).to.equal('now-6h');
-        expect(ctx.service.time.to).to.equal('now');
-      });
-    });
-
-    describe('setTime', function() {
-      it('should return disable refresh if refresh is disabled for any range', function() {
-        _dashboard.refresh = false;
-
-        ctx.service.setTime({from: '2011-01-01', to: '2015-01-01' });
-        expect(_dashboard.refresh).to.be(false);
-      });
-
-      it('should restore refresh for absolute time range', function() {
-        _dashboard.refresh = '30s';
-
-        ctx.service.setTime({from: '2011-01-01', to: '2015-01-01' });
-        expect(_dashboard.refresh).to.be('30s');
-      });
-
-      it('should restore refresh after relative time range is set', function() {
-        _dashboard.refresh = '10s';
-        ctx.service.setTime({from: moment([2011,1,1]), to: moment([2015,1,1])});
-        expect(_dashboard.refresh).to.be(false);
-        ctx.service.setTime({from: '2011-01-01', to: 'now' });
-        expect(_dashboard.refresh).to.be('10s');
-      });
-
-      it('should keep refresh after relative time range is changed and now delay exists', function() {
-        _dashboard.refresh = '10s';
-        ctx.service.setTime({from: 'now-1h', to: 'now-10s' });
-        expect(_dashboard.refresh).to.be('10s');
-      });
-    });
-
-  });
-
-});