1import { TimeSrv } from './TimeSrv';
2import { ContextSrvStub } from 'test/specs/helpers';
3import { dateTime, isDateTime } from '@grafana/data';
4import * as H from 'history';
5import { HistoryWrapper, locationService, setLocationService } from '@grafana/runtime';
6import { beforeEach } from '../../../../test/lib/common';
7
8jest.mock('app/core/core', () => ({
9  appEvents: {
10    subscribe: () => {},
11  },
12}));
13
14describe('timeSrv', () => {
15  let timeSrv: TimeSrv;
16  let _dashboard: any;
17  let locationUpdates: H.Location[] = [];
18
19  beforeEach(() => {
20    _dashboard = {
21      time: { from: 'now-6h', to: 'now' },
22      getTimezone: jest.fn(() => 'browser'),
23      refresh: false,
24      timeRangeUpdated: jest.fn(() => {}),
25    };
26
27    timeSrv = new TimeSrv(new ContextSrvStub() as any);
28    timeSrv.init(_dashboard);
29
30    beforeEach(() => {
31      locationUpdates = [];
32      const history = new HistoryWrapper();
33      history.getHistory().listen((x) => locationUpdates.push(x));
34      setLocationService(history);
35    });
36  });
37
38  describe('timeRange', () => {
39    it('should return unparsed when parse is false', () => {
40      timeSrv.setTime({ from: 'now', to: 'now-1h' });
41      const time = timeSrv.timeRange();
42      expect(time.raw.from).toBe('now');
43      expect(time.raw.to).toBe('now-1h');
44    });
45
46    it('should return parsed when parse is true', () => {
47      timeSrv.setTime({ from: 'now', to: 'now-1h' });
48      const time = timeSrv.timeRange();
49      expect(isDateTime(time.from)).toBe(true);
50      expect(isDateTime(time.to)).toBe(true);
51    });
52  });
53
54  describe('init time from url', () => {
55    it('should handle relative times', () => {
56      locationService.push('/d/id?from=now-2d&to=now');
57
58      timeSrv = new TimeSrv(new ContextSrvStub() as any);
59
60      timeSrv.init(_dashboard);
61      const time = timeSrv.timeRange();
62      expect(time.raw.from).toBe('now-2d');
63      expect(time.raw.to).toBe('now');
64    });
65
66    it('should handle formatted dates', () => {
67      locationService.push('/d/id?from=20140410T052010&to=20140520T031022');
68
69      timeSrv = new TimeSrv(new ContextSrvStub() as any);
70
71      timeSrv.init(_dashboard);
72      const time = timeSrv.timeRange();
73      expect(time.from.valueOf()).toEqual(new Date('2014-04-10T05:20:10Z').getTime());
74      expect(time.to.valueOf()).toEqual(new Date('2014-05-20T03:10:22Z').getTime());
75    });
76
77    it('should ignore refresh if time absolute', () => {
78      locationService.push('/d/id?from=20140410T052010&to=20140520T031022');
79
80      timeSrv = new TimeSrv(new ContextSrvStub() as any);
81
82      // dashboard saved with refresh on
83      _dashboard.refresh = true;
84      timeSrv.init(_dashboard);
85
86      expect(timeSrv.refresh).toBe(false);
87    });
88
89    it('should handle formatted dates without time', () => {
90      locationService.push('/d/id?from=20140410&to=20140520');
91
92      timeSrv = new TimeSrv(new ContextSrvStub() as any);
93
94      timeSrv.init(_dashboard);
95      const time = timeSrv.timeRange();
96      expect(time.from.valueOf()).toEqual(new Date('2014-04-10T00:00:00Z').getTime());
97      expect(time.to.valueOf()).toEqual(new Date('2014-05-20T00:00:00Z').getTime());
98    });
99
100    it('should handle epochs', () => {
101      locationService.push('/d/id?from=1410337646373&to=1410337665699');
102
103      timeSrv = new TimeSrv(new ContextSrvStub() as any);
104
105      timeSrv.init(_dashboard);
106      const time = timeSrv.timeRange();
107      expect(time.from.valueOf()).toEqual(1410337646373);
108      expect(time.to.valueOf()).toEqual(1410337665699);
109    });
110
111    it('should handle epochs that look like formatted date without time', () => {
112      locationService.push('/d/id?from=20149999&to=20159999');
113
114      timeSrv = new TimeSrv(new ContextSrvStub() as any);
115
116      timeSrv.init(_dashboard);
117      const time = timeSrv.timeRange();
118      expect(time.from.valueOf()).toEqual(20149999);
119      expect(time.to.valueOf()).toEqual(20159999);
120    });
121
122    it('should handle epochs that look like formatted date', () => {
123      locationService.push('/d/id?from=201499991234567&to=201599991234567');
124
125      timeSrv = new TimeSrv(new ContextSrvStub() as any);
126
127      timeSrv.init(_dashboard);
128      const time = timeSrv.timeRange();
129      expect(time.from.valueOf()).toEqual(201499991234567);
130      expect(time.to.valueOf()).toEqual(201599991234567);
131    });
132
133    it('should handle bad dates', () => {
134      locationService.push('/d/id?from=20151126T00010%3C%2Fp%3E%3Cspan%20class&to=now');
135
136      timeSrv = new TimeSrv(new ContextSrvStub() as any);
137
138      _dashboard.time.from = 'now-6h';
139      timeSrv.init(_dashboard);
140      expect(timeSrv.time.from).toEqual('now-6h');
141      expect(timeSrv.time.to).toEqual('now');
142    });
143
144    it('should handle refresh_intervals=null when refresh is enabled', () => {
145      locationService.push('/d/id?refresh=30s');
146
147      timeSrv = new TimeSrv(new ContextSrvStub() as any);
148
149      _dashboard.timepicker = {
150        refresh_intervals: null,
151      };
152      expect(() => timeSrv.init(_dashboard)).not.toThrow();
153    });
154
155    describe('data point windowing', () => {
156      it('handles time window specfied as interval string', () => {
157        locationService.push('/d/id?time=1410337645000&time.window=10s');
158
159        timeSrv = new TimeSrv(new ContextSrvStub() as any);
160
161        timeSrv.init(_dashboard);
162        const time = timeSrv.timeRange();
163        expect(time.from.valueOf()).toEqual(1410337640000);
164        expect(time.to.valueOf()).toEqual(1410337650000);
165      });
166
167      it('handles time window specified in ms', () => {
168        locationService.push('/d/id?time=1410337645000&time.window=10000');
169
170        timeSrv = new TimeSrv(new ContextSrvStub() as any);
171
172        timeSrv.init(_dashboard);
173        const time = timeSrv.timeRange();
174        expect(time.from.valueOf()).toEqual(1410337640000);
175        expect(time.to.valueOf()).toEqual(1410337650000);
176      });
177
178      it('corrects inverted from/to dates in ms', () => {
179        locationService.push('/d/id?from=1621436828909&to=1621436818909');
180
181        timeSrv = new TimeSrv(new ContextSrvStub() as any);
182
183        timeSrv.init(_dashboard);
184        const time = timeSrv.timeRange();
185        expect(time.from.valueOf()).toEqual(1621436818909);
186        expect(time.to.valueOf()).toEqual(1621436828909);
187      });
188
189      it('corrects inverted from/to dates as relative times', () => {
190        locationService.push('/d/id?from=now&to=now-1h');
191
192        timeSrv = new TimeSrv(new ContextSrvStub() as any);
193
194        timeSrv.init(_dashboard);
195        const time = timeSrv.timeRange();
196        expect(time.raw.from).toBe('now-1h');
197        expect(time.raw.to).toBe('now');
198      });
199    });
200  });
201
202  describe('setTime', () => {
203    it('should return disable refresh if refresh is disabled for any range', () => {
204      _dashboard.refresh = false;
205
206      timeSrv.setTime({ from: '2011-01-01', to: '2015-01-01' });
207      expect(_dashboard.refresh).toBe(false);
208    });
209
210    it('should restore refresh for absolute time range', () => {
211      _dashboard.refresh = '30s';
212
213      timeSrv.setTime({ from: '2011-01-01', to: '2015-01-01' });
214      expect(_dashboard.refresh).toBe('30s');
215    });
216
217    it('should restore refresh after relative time range is set', () => {
218      _dashboard.refresh = '10s';
219      timeSrv.setTime({
220        from: dateTime([2011, 1, 1]),
221        to: dateTime([2015, 1, 1]),
222      });
223      expect(_dashboard.refresh).toBe(false);
224      timeSrv.setTime({ from: '2011-01-01', to: 'now' });
225      expect(_dashboard.refresh).toBe('10s');
226    });
227
228    it('should keep refresh after relative time range is changed and now delay exists', () => {
229      _dashboard.refresh = '10s';
230      timeSrv.setTime({ from: 'now-1h', to: 'now-10s' });
231      expect(_dashboard.refresh).toBe('10s');
232    });
233
234    it('should update location only once for consecutive calls with the same range', () => {
235      timeSrv.setTime({ from: 'now-1h', to: 'now-10s' });
236      timeSrv.setTime({ from: 'now-1h', to: 'now-10s' });
237
238      expect(locationUpdates.length).toBe(1);
239    });
240
241    it('should update location so that bool params are preserved', () => {
242      locationService.partial({ kiosk: true });
243
244      timeSrv.setTime({ from: 'now-1h', to: 'now-10s' });
245      timeSrv.setTime({ from: 'now-1h', to: 'now-10s' });
246
247      expect(locationUpdates[1].search).toEqual('?kiosk&from=now-1h&to=now-10s');
248    });
249  });
250
251  describe('pauseAutoRefresh', () => {
252    it('should set refresh to empty value', () => {
253      _dashboard.refresh = '10s';
254      timeSrv.pauseAutoRefresh();
255      expect(_dashboard.refresh).toBe('');
256    });
257
258    it('should set previousAutoRefresh value', () => {
259      _dashboard.refresh = '10s';
260      timeSrv.pauseAutoRefresh();
261      expect(timeSrv.previousAutoRefresh).toBe('10s');
262    });
263  });
264
265  describe('resumeAutoRefresh', () => {
266    it('should set refresh to empty value', () => {
267      timeSrv.previousAutoRefresh = '10s';
268      timeSrv.resumeAutoRefresh();
269      expect(_dashboard.refresh).toBe('10s');
270    });
271  });
272
273  describe('isRefreshOutsideThreshold', () => {
274    const originalNow = Date.now;
275
276    beforeEach(() => {
277      Date.now = jest.fn(() => 60000);
278    });
279
280    afterEach(() => {
281      Date.now = originalNow;
282    });
283
284    describe('when called and current time range is absolute', () => {
285      it('then it should return false', () => {
286        timeSrv.setTime({ from: dateTime(), to: dateTime() });
287
288        expect(timeSrv.isRefreshOutsideThreshold(0, 0.05)).toBe(false);
289      });
290    });
291
292    describe('when called and current time range is relative', () => {
293      describe('and last refresh is within threshold', () => {
294        it('then it should return false', () => {
295          timeSrv.setTime({ from: 'now-1m', to: 'now' });
296
297          expect(timeSrv.isRefreshOutsideThreshold(57001, 0.05)).toBe(false);
298        });
299      });
300
301      describe('and last refresh is outside the threshold', () => {
302        it('then it should return true', () => {
303          timeSrv.setTime({ from: 'now-1m', to: 'now' });
304
305          expect(timeSrv.isRefreshOutsideThreshold(57000, 0.05)).toBe(true);
306        });
307      });
308    });
309  });
310});
311