mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
* Timefilter - TypeScript (#43770) * Typescriptifying * More TS * define InputTimeRange type * fix import path * change import path to stabilize tests
This commit is contained in:
parent
909dd21e00
commit
bcf570dc22
11 changed files with 226 additions and 259 deletions
|
@ -23,26 +23,27 @@ import { DashboardStateManager } from './dashboard_state_manager';
|
|||
import { getAppStateMock, getSavedDashboardMock } from './__tests__';
|
||||
import { AppStateClass } from 'ui/state_management/app_state';
|
||||
import { DashboardAppState } from './types';
|
||||
import { Timefilter } from 'ui/timefilter';
|
||||
import { Timefilter, TimeRange } from 'ui/timefilter';
|
||||
import { ViewMode } from '../../../embeddable_api/public/np_ready/public';
|
||||
|
||||
describe('DashboardState', function() {
|
||||
let dashboardState: DashboardStateManager;
|
||||
const savedDashboard = getSavedDashboardMock();
|
||||
const mockTimefilter: Timefilter = {
|
||||
time: { to: 'now', from: 'now-15m' },
|
||||
|
||||
let mockTime: TimeRange = { to: 'now', from: 'now-15m' };
|
||||
const mockTimefilter: Partial<Timefilter> = {
|
||||
setTime(time) {
|
||||
this.time = time;
|
||||
mockTime = time as TimeRange;
|
||||
},
|
||||
getTime() {
|
||||
return this.time;
|
||||
return mockTime;
|
||||
},
|
||||
disableAutoRefreshSelector: jest.fn(),
|
||||
setRefreshInterval: jest.fn(),
|
||||
getRefreshInterval: jest.fn(),
|
||||
disableTimeRangeSelector: jest.fn(),
|
||||
enableAutoRefreshSelector: jest.fn(),
|
||||
getActiveBounds: () => {},
|
||||
getActiveBounds: jest.fn(),
|
||||
enableTimeRangeSelector: () => {},
|
||||
isAutoRefreshSelectorEnabled: true,
|
||||
isTimeRangeSelectorEnabled: true,
|
||||
|
@ -67,14 +68,14 @@ describe('DashboardState', function() {
|
|||
savedDashboard.timeFrom = 'now/w';
|
||||
savedDashboard.timeTo = 'now/w';
|
||||
|
||||
mockTimefilter.time.from = '2015-09-19 06:31:44.000';
|
||||
mockTimefilter.time.to = '2015-09-29 06:31:44.000';
|
||||
mockTime.from = '2015-09-19 06:31:44.000';
|
||||
mockTime.to = '2015-09-29 06:31:44.000';
|
||||
|
||||
initDashboardState();
|
||||
dashboardState.syncTimefilterWithDashboard(mockTimefilter);
|
||||
dashboardState.syncTimefilterWithDashboard(mockTimefilter as Timefilter);
|
||||
|
||||
expect(mockTimefilter.time.to).toBe('now/w');
|
||||
expect(mockTimefilter.time.from).toBe('now/w');
|
||||
expect(mockTime.to).toBe('now/w');
|
||||
expect(mockTime.from).toBe('now/w');
|
||||
});
|
||||
|
||||
test('syncs relative time', function() {
|
||||
|
@ -82,14 +83,14 @@ describe('DashboardState', function() {
|
|||
savedDashboard.timeFrom = 'now-13d';
|
||||
savedDashboard.timeTo = 'now';
|
||||
|
||||
mockTimefilter.time.from = '2015-09-19 06:31:44.000';
|
||||
mockTimefilter.time.to = '2015-09-29 06:31:44.000';
|
||||
mockTime.from = '2015-09-19 06:31:44.000';
|
||||
mockTime.to = '2015-09-29 06:31:44.000';
|
||||
|
||||
initDashboardState();
|
||||
dashboardState.syncTimefilterWithDashboard(mockTimefilter);
|
||||
dashboardState.syncTimefilterWithDashboard(mockTimefilter as Timefilter);
|
||||
|
||||
expect(mockTimefilter.time.to).toBe('now');
|
||||
expect(mockTimefilter.time.from).toBe('now-13d');
|
||||
expect(mockTime.to).toBe('now');
|
||||
expect(mockTime.from).toBe('now-13d');
|
||||
});
|
||||
|
||||
test('syncs absolute time', function() {
|
||||
|
@ -97,14 +98,14 @@ describe('DashboardState', function() {
|
|||
savedDashboard.timeFrom = '2015-09-19 06:31:44.000';
|
||||
savedDashboard.timeTo = '2015-09-29 06:31:44.000';
|
||||
|
||||
mockTimefilter.time.from = 'now/w';
|
||||
mockTimefilter.time.to = 'now/w';
|
||||
mockTime.from = 'now/w';
|
||||
mockTime.to = 'now/w';
|
||||
|
||||
initDashboardState();
|
||||
dashboardState.syncTimefilterWithDashboard(mockTimefilter);
|
||||
dashboardState.syncTimefilterWithDashboard(mockTimefilter as Timefilter);
|
||||
|
||||
expect(mockTimefilter.time.to).toBe(savedDashboard.timeTo);
|
||||
expect(mockTimefilter.time.from).toBe(savedDashboard.timeFrom);
|
||||
expect(mockTime.to).toBe(savedDashboard.timeTo);
|
||||
expect(mockTime.from).toBe(savedDashboard.timeFrom);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
import dateMath from '@elastic/datemath';
|
||||
import { Field, IndexPattern } from 'ui/index_patterns';
|
||||
import { TimeRange } from './time_history';
|
||||
import { TimeRange } from 'src/plugins/data/public';
|
||||
|
||||
interface CalculateBoundsOptions {
|
||||
forceNow?: Date;
|
||||
|
|
|
@ -17,9 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
// @ts-ignore
|
||||
export { registerTimefilterWithGlobalState } from './timefilter';
|
||||
export { timefilter, Timefilter, RefreshInterval } from './timefilter';
|
||||
export { timeHistory, TimeRange, TimeHistory } from './time_history';
|
||||
export { TimeRange, RefreshInterval } from '../../../../plugins/data/public';
|
||||
|
||||
export { timefilter, Timefilter, registerTimefilterWithGlobalState } from './timefilter';
|
||||
export { timeHistory, TimeHistory } from './time_history';
|
||||
export { getTime } from './get_time';
|
||||
|
|
|
@ -17,42 +17,35 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
|
||||
import moment from 'moment';
|
||||
import expect from '@kbn/expect';
|
||||
import { areTimePickerValsDifferent } from './diff_time_picker_vals';
|
||||
import { areTimeRangesDifferent } from './diff_time_picker_vals';
|
||||
|
||||
describe('Diff Time Picker Values', () => {
|
||||
|
||||
test('accepts two undefined values', () => {
|
||||
const diff = areTimePickerValsDifferent(undefined, undefined);
|
||||
expect(diff).to.be(false);
|
||||
});
|
||||
|
||||
describe('dateMath ranges', () => {
|
||||
test('knows a match', () => {
|
||||
const diff = areTimePickerValsDifferent(
|
||||
const diff = areTimeRangesDifferent(
|
||||
{
|
||||
to: 'now',
|
||||
from: 'now-7d'
|
||||
from: 'now-7d',
|
||||
},
|
||||
{
|
||||
to: 'now',
|
||||
from: 'now-7d'
|
||||
from: 'now-7d',
|
||||
}
|
||||
);
|
||||
|
||||
expect(diff).to.be(false);
|
||||
});
|
||||
test('knows a difference', () => {
|
||||
const diff = areTimePickerValsDifferent(
|
||||
const diff = areTimeRangesDifferent(
|
||||
{
|
||||
to: 'now',
|
||||
from: 'now-7d'
|
||||
from: 'now-7d',
|
||||
},
|
||||
{
|
||||
to: 'now',
|
||||
from: 'now-1h'
|
||||
from: 'now-1h',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -62,14 +55,14 @@ describe('Diff Time Picker Values', () => {
|
|||
|
||||
describe('a dateMath range, and a moment range', () => {
|
||||
test('is always different', () => {
|
||||
const diff = areTimePickerValsDifferent(
|
||||
const diff = areTimeRangesDifferent(
|
||||
{
|
||||
to: moment(),
|
||||
from: moment()
|
||||
from: moment(),
|
||||
},
|
||||
{
|
||||
to: 'now',
|
||||
from: 'now-1h'
|
||||
from: 'now-1h',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -82,14 +75,14 @@ describe('Diff Time Picker Values', () => {
|
|||
const to = moment();
|
||||
const from = moment().add(1, 'day');
|
||||
|
||||
const diff = areTimePickerValsDifferent(
|
||||
const diff = areTimeRangesDifferent(
|
||||
{
|
||||
to: to.clone(),
|
||||
from: from.clone()
|
||||
from: from.clone(),
|
||||
},
|
||||
{
|
||||
to: to.clone(),
|
||||
from: from.clone()
|
||||
from: from.clone(),
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -101,23 +94,18 @@ describe('Diff Time Picker Values', () => {
|
|||
const from = moment().add(1, 'day');
|
||||
const from2 = moment().add(2, 'day');
|
||||
|
||||
const diff = areTimePickerValsDifferent(
|
||||
const diff = areTimeRangesDifferent(
|
||||
{
|
||||
to: to.clone(),
|
||||
from: from.clone()
|
||||
from: from.clone(),
|
||||
},
|
||||
{
|
||||
to: to.clone(),
|
||||
from: from2.clone()
|
||||
from: from2.clone(),
|
||||
}
|
||||
);
|
||||
|
||||
expect(diff).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('does not fall apart with unusual values', () => {
|
||||
const diff = areTimePickerValsDifferent({}, {});
|
||||
expect(diff).to.be(false);
|
||||
});
|
||||
});
|
|
@ -19,17 +19,33 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
|
||||
const valueOf = function (o) {
|
||||
import { RefreshInterval } from 'src/plugins/data/public';
|
||||
import { InputTimeRange } from '../timefilter';
|
||||
|
||||
const valueOf = function(o: any) {
|
||||
if (o) return o.valueOf();
|
||||
};
|
||||
|
||||
export function areTimePickerValsDifferent(rangeA, rangeB) {
|
||||
export function areRefreshIntervalsDifferent(rangeA: RefreshInterval, rangeB: RefreshInterval) {
|
||||
if (_.isObject(rangeA) && _.isObject(rangeB)) {
|
||||
if (
|
||||
valueOf(rangeA.to) !== valueOf(rangeB.to)
|
||||
|| valueOf(rangeA.from) !== valueOf(rangeB.from)
|
||||
|| valueOf(rangeA.value) !== valueOf(rangeB.value)
|
||||
|| valueOf(rangeA.pause) !== valueOf(rangeB.pause)
|
||||
valueOf(rangeA.value) !== valueOf(rangeB.value) ||
|
||||
valueOf(rangeA.pause) !== valueOf(rangeB.pause)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return !_.isEqual(rangeA, rangeB);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function areTimeRangesDifferent(rangeA: InputTimeRange, rangeB: InputTimeRange) {
|
||||
if (rangeA && rangeB && _.isObject(rangeA) && _.isObject(rangeB)) {
|
||||
if (
|
||||
valueOf(rangeA.to) !== valueOf(rangeB.to) ||
|
||||
valueOf(rangeA.from) !== valueOf(rangeB.from)
|
||||
) {
|
||||
return true;
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { TimeRange } from '../../../../plugins/data/public';
|
||||
|
||||
export { TimeRange };
|
||||
|
||||
export interface TimeHistory {
|
||||
add: (options: TimeRange) => void;
|
||||
get: () => TimeRange[];
|
||||
}
|
||||
|
||||
export const timeHistory: TimeHistory;
|
|
@ -18,21 +18,24 @@
|
|||
*/
|
||||
|
||||
import moment from 'moment';
|
||||
import { TimeRange } from 'src/plugins/data/public';
|
||||
import { PersistedLog } from '../persisted_log';
|
||||
|
||||
class TimeHistory {
|
||||
export class TimeHistory {
|
||||
private history: PersistedLog;
|
||||
|
||||
constructor() {
|
||||
const historyOptions = {
|
||||
maxLength: 10,
|
||||
filterDuplicates: true,
|
||||
isDuplicate: (oldItem, newItem) => {
|
||||
isDuplicate: (oldItem: TimeRange, newItem: TimeRange) => {
|
||||
return oldItem.from === newItem.from && oldItem.to === newItem.to;
|
||||
}
|
||||
},
|
||||
};
|
||||
this.history = new PersistedLog('kibana.timepicker.timeHistory', historyOptions);
|
||||
}
|
||||
|
||||
add(time) {
|
||||
add(time: TimeRange) {
|
||||
if (!time) {
|
||||
return;
|
||||
}
|
||||
|
@ -40,7 +43,7 @@ class TimeHistory {
|
|||
// time from/to can be strings or moment objects - convert to strings so always dealing with same types
|
||||
const justStringsTime = {
|
||||
from: moment.isMoment(time.from) ? time.from.toISOString() : time.from,
|
||||
to: moment.isMoment(time.to) ? time.to.toISOString() : time.to
|
||||
to: moment.isMoment(time.to) ? time.to.toISOString() : time.to,
|
||||
};
|
||||
this.history.add(justStringsTime);
|
||||
}
|
49
src/legacy/ui/public/timefilter/timefilter.d.ts
vendored
49
src/legacy/ui/public/timefilter/timefilter.d.ts
vendored
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { Moment } from 'moment';
|
||||
import { Observable } from 'rxjs';
|
||||
import { TimeRange } from './time_history';
|
||||
import { RefreshInterval } from '../../../../plugins/data/public';
|
||||
|
||||
// NOTE: These types are somewhat guessed, they may be incorrect.
|
||||
|
||||
export { RefreshInterval, TimeRange };
|
||||
|
||||
export interface Timefilter {
|
||||
time: TimeRange;
|
||||
getEnabledUpdated$: () => Observable<any>;
|
||||
getTimeUpdate$: () => Observable<any>;
|
||||
getRefreshIntervalUpdate$: () => Observable<any>;
|
||||
getAutoRefreshFetch$: () => Observable<any>;
|
||||
getFetch$: () => Observable<any>;
|
||||
getTime: () => TimeRange;
|
||||
setTime: (timeRange: TimeRange) => void;
|
||||
setRefreshInterval: (refreshInterval: RefreshInterval) => void;
|
||||
getRefreshInterval: () => RefreshInterval;
|
||||
getActiveBounds: () => void;
|
||||
disableAutoRefreshSelector: () => void;
|
||||
disableTimeRangeSelector: () => void;
|
||||
enableAutoRefreshSelector: () => void;
|
||||
enableTimeRangeSelector: () => void;
|
||||
isAutoRefreshSelectorEnabled: boolean;
|
||||
isTimeRangeSelectorEnabled: boolean;
|
||||
}
|
||||
|
||||
export const timefilter: Timefilter;
|
|
@ -19,12 +19,13 @@
|
|||
|
||||
import './timefilter.test.mocks';
|
||||
|
||||
jest.mock('ui/chrome',
|
||||
jest.mock(
|
||||
'ui/chrome',
|
||||
() => ({
|
||||
getBasePath: () => `/some/base/path`,
|
||||
getUiSettingsClient: () => {
|
||||
return {
|
||||
get: (key) => {
|
||||
get: (key: string) => {
|
||||
switch (key) {
|
||||
case 'timepicker:timeDefaults':
|
||||
return { from: 'now-15m', to: 'now' };
|
||||
|
@ -33,46 +34,56 @@ jest.mock('ui/chrome',
|
|||
default:
|
||||
throw new Error(`Unexpected config key: ${key}`);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
}), { virtual: true });
|
||||
}),
|
||||
{ virtual: true }
|
||||
);
|
||||
|
||||
jest.mock('ui/timefilter/lib/parse_querystring',
|
||||
jest.mock(
|
||||
'ui/timefilter/lib/parse_querystring',
|
||||
() => ({
|
||||
parseQueryString: () => {
|
||||
return {
|
||||
// Can not access local variable from within a mock
|
||||
forceNow: global.nowTime
|
||||
// @ts-ignore
|
||||
forceNow: global.nowTime,
|
||||
};
|
||||
},
|
||||
}), { virtual: true });
|
||||
}),
|
||||
{ virtual: true }
|
||||
);
|
||||
|
||||
import sinon from 'sinon';
|
||||
import expect from '@kbn/expect';
|
||||
import moment from 'moment';
|
||||
import { timefilter } from './timefilter';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { TimeRange, RefreshInterval } from 'src/plugins/data/public';
|
||||
|
||||
function stubNowTime(nowTime) {
|
||||
function stubNowTime(nowTime: any) {
|
||||
// @ts-ignore
|
||||
global.nowTime = nowTime;
|
||||
}
|
||||
|
||||
function clearNowTimeStub() {
|
||||
// @ts-ignore
|
||||
delete global.nowTime;
|
||||
}
|
||||
|
||||
describe('setTime', () => {
|
||||
let update;
|
||||
let fetch;
|
||||
let updateSub;
|
||||
let fetchSub;
|
||||
let update: sinon.SinonSpy;
|
||||
let fetch: sinon.SinonSpy;
|
||||
let updateSub: Subscription;
|
||||
let fetchSub: Subscription;
|
||||
|
||||
beforeEach(() => {
|
||||
update = sinon.spy();
|
||||
fetch = sinon.spy();
|
||||
timefilter.setTime({
|
||||
from: 0,
|
||||
to: 1,
|
||||
from: '0',
|
||||
to: '1',
|
||||
});
|
||||
updateSub = timefilter.getTimeUpdate$().subscribe(update);
|
||||
fetchSub = timefilter.getFetch$().subscribe(fetch);
|
||||
|
@ -84,29 +95,36 @@ describe('setTime', () => {
|
|||
});
|
||||
|
||||
test('should update time', () => {
|
||||
timefilter.setTime({ from: 5, to: 10 });
|
||||
expect(timefilter.getTime()).to.eql({ from: 5, to: 10 });
|
||||
timefilter.setTime({ from: '5', to: '10' });
|
||||
expect(timefilter.getTime()).to.eql({
|
||||
from: '5',
|
||||
to: '10',
|
||||
});
|
||||
});
|
||||
|
||||
test('should not add unexpected object keys to time state', () => {
|
||||
const unexpectedKey = 'unexpectedKey';
|
||||
timefilter.setTime({ from: 5, to: 10, [unexpectedKey]: 'I should not be added to time state' });
|
||||
timefilter.setTime({
|
||||
from: '5',
|
||||
to: '10',
|
||||
[unexpectedKey]: 'I should not be added to time state',
|
||||
} as TimeRange);
|
||||
expect(timefilter.getTime()).not.to.have.property(unexpectedKey);
|
||||
});
|
||||
|
||||
test('should allow partial updates to time', () => {
|
||||
timefilter.setTime({ from: 5, to: 10 });
|
||||
expect(timefilter.getTime()).to.eql({ from: 5, to: 10 });
|
||||
timefilter.setTime({ from: '5', to: '10' });
|
||||
expect(timefilter.getTime()).to.eql({ from: '5', to: '10' });
|
||||
});
|
||||
|
||||
test('not emit anything if the time has not changed', () => {
|
||||
timefilter.setTime({ from: 0, to: 1 });
|
||||
timefilter.setTime({ from: '0', to: '1' });
|
||||
expect(update.called).to.be(false);
|
||||
expect(fetch.called).to.be(false);
|
||||
});
|
||||
|
||||
test('emit update and fetch if the time has changed', () => {
|
||||
timefilter.setTime({ from: 5, to: 10 });
|
||||
timefilter.setTime({ from: '5', to: '10' });
|
||||
expect(update.called).to.be(true);
|
||||
expect(fetch.called).to.be(true);
|
||||
});
|
||||
|
@ -123,17 +141,17 @@ describe('setTime', () => {
|
|||
});
|
||||
|
||||
describe('setRefreshInterval', () => {
|
||||
let update;
|
||||
let fetch;
|
||||
let fetchSub;
|
||||
let refreshSub;
|
||||
let update: sinon.SinonSpy;
|
||||
let fetch: sinon.SinonSpy;
|
||||
let fetchSub: Subscription;
|
||||
let refreshSub: Subscription;
|
||||
|
||||
beforeEach(() => {
|
||||
update = sinon.spy();
|
||||
fetch = sinon.spy();
|
||||
timefilter.setRefreshInterval({
|
||||
pause: false,
|
||||
value: 0
|
||||
value: 0,
|
||||
});
|
||||
refreshSub = timefilter.getRefreshIntervalUpdate$().subscribe(update);
|
||||
fetchSub = timefilter.getFetch$().subscribe(fetch);
|
||||
|
@ -151,7 +169,11 @@ describe('setRefreshInterval', () => {
|
|||
|
||||
test('should not add unexpected object keys to refreshInterval state', () => {
|
||||
const unexpectedKey = 'unexpectedKey';
|
||||
timefilter.setRefreshInterval({ pause: true, value: 10, [unexpectedKey]: 'I should not be added to refreshInterval state' });
|
||||
timefilter.setRefreshInterval({
|
||||
pause: true,
|
||||
value: 10,
|
||||
[unexpectedKey]: 'I should not be added to refreshInterval state',
|
||||
} as RefreshInterval);
|
||||
expect(timefilter.getRefreshInterval()).not.to.have.property(unexpectedKey);
|
||||
});
|
||||
|
||||
|
@ -211,12 +233,11 @@ describe('setRefreshInterval', () => {
|
|||
expect(update.calledTwice).to.be(true);
|
||||
expect(fetch.calledOnce).to.be(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('isTimeRangeSelectorEnabled', () => {
|
||||
let update;
|
||||
let updateSub;
|
||||
let update: sinon.SinonSpy;
|
||||
let updateSub: Subscription;
|
||||
|
||||
beforeEach(() => {
|
||||
update = sinon.spy();
|
||||
|
@ -241,8 +262,8 @@ describe('isTimeRangeSelectorEnabled', () => {
|
|||
});
|
||||
|
||||
describe('isAutoRefreshSelectorEnabled', () => {
|
||||
let update;
|
||||
let updateSub;
|
||||
let update: sinon.SinonSpy;
|
||||
let updateSub: Subscription;
|
||||
|
||||
beforeEach(() => {
|
||||
update = sinon.spy();
|
||||
|
@ -267,11 +288,10 @@ describe('isAutoRefreshSelectorEnabled', () => {
|
|||
});
|
||||
|
||||
describe('calculateBounds', () => {
|
||||
|
||||
const fifteenMinutesInMilliseconds = 15 * 60 * 1000;
|
||||
const clockNowTicks = new Date(2000, 1, 1, 0, 0, 0, 0).valueOf();
|
||||
|
||||
let clock;
|
||||
let clock: sinon.SinonFakeTimers;
|
||||
|
||||
beforeEach(() => {
|
||||
clock = sinon.useFakeTimers(clockNowTicks);
|
||||
|
@ -285,19 +305,19 @@ describe('calculateBounds', () => {
|
|||
test('uses clock time by default', () => {
|
||||
const timeRange = {
|
||||
from: 'now-15m',
|
||||
to: 'now'
|
||||
to: 'now',
|
||||
};
|
||||
|
||||
stubNowTime(undefined);
|
||||
const result = timefilter.calculateBounds(timeRange);
|
||||
expect(result.min.valueOf()).to.eql(clockNowTicks - fifteenMinutesInMilliseconds);
|
||||
expect(result.max.valueOf()).to.eql(clockNowTicks);
|
||||
expect(result.min && result.min.valueOf()).to.eql(clockNowTicks - fifteenMinutesInMilliseconds);
|
||||
expect(result.max && result.max.valueOf()).to.eql(clockNowTicks);
|
||||
});
|
||||
|
||||
test('uses forceNow string', () => {
|
||||
const timeRange = {
|
||||
from: 'now-15m',
|
||||
to: 'now'
|
||||
to: 'now',
|
||||
};
|
||||
|
||||
const forceNowString = '1999-01-01T00:00:00.000Z';
|
||||
|
@ -305,18 +325,17 @@ describe('calculateBounds', () => {
|
|||
const result = timefilter.calculateBounds(timeRange);
|
||||
|
||||
const forceNowTicks = Date.parse(forceNowString);
|
||||
expect(result.min.valueOf()).to.eql(forceNowTicks - fifteenMinutesInMilliseconds);
|
||||
expect(result.max.valueOf()).to.eql(forceNowTicks);
|
||||
expect(result.min && result.min.valueOf()).to.eql(forceNowTicks - fifteenMinutesInMilliseconds);
|
||||
expect(result.max && result.max.valueOf()).to.eql(forceNowTicks);
|
||||
});
|
||||
|
||||
test(`throws Error if forceNow can't be parsed`, () => {
|
||||
const timeRange = {
|
||||
from: 'now-15m',
|
||||
to: 'now'
|
||||
to: 'now',
|
||||
};
|
||||
|
||||
stubNowTime('not_a_parsable_date');
|
||||
expect(() => timefilter.calculateBounds(timeRange)).to.throwError();
|
||||
});
|
||||
});
|
||||
|
|
@ -19,67 +19,77 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
import { Subject, BehaviorSubject } from 'rxjs';
|
||||
import moment from 'moment';
|
||||
import { calculateBounds, getTime } from './get_time';
|
||||
import { parseQueryString } from './lib/parse_querystring';
|
||||
import moment, { Moment } from 'moment';
|
||||
import { subscribeWithScope } from 'ui/utils/subscribe_with_scope';
|
||||
import uiRoutes from '../routes';
|
||||
import chrome from 'ui/chrome';
|
||||
import { areTimePickerValsDifferent } from './lib/diff_time_picker_vals';
|
||||
import { UiSettingsClientContract } from 'src/core/public';
|
||||
import { RefreshInterval, TimeRange } from 'src/plugins/data/public';
|
||||
import { IndexPattern } from 'src/legacy/core_plugins/data/public';
|
||||
import { IScope } from 'angular';
|
||||
import { timeHistory } from './time_history';
|
||||
import { areRefreshIntervalsDifferent, areTimeRangesDifferent } from './lib/diff_time_picker_vals';
|
||||
import uiRoutes from '../routes';
|
||||
import { parseQueryString } from './lib/parse_querystring';
|
||||
import { calculateBounds, getTime } from './get_time';
|
||||
|
||||
class Timefilter {
|
||||
constructor() {
|
||||
// Timefilter accepts moment input but always returns string output
|
||||
export type InputTimeRange =
|
||||
| TimeRange
|
||||
| {
|
||||
from: Moment;
|
||||
to: Moment;
|
||||
};
|
||||
|
||||
// Fired when isTimeRangeSelectorEnabled \ isAutoRefreshSelectorEnabled are toggled
|
||||
this.enabledUpdated$ = new BehaviorSubject();
|
||||
export class Timefilter {
|
||||
// Fired when isTimeRangeSelectorEnabled \ isAutoRefreshSelectorEnabled are toggled
|
||||
private enabledUpdated$ = new BehaviorSubject(false);
|
||||
// Fired when a user changes the timerange
|
||||
private timeUpdate$ = new Subject();
|
||||
// Fired when a user changes the the autorefresh settings
|
||||
private refreshIntervalUpdate$ = new Subject();
|
||||
// Used when search poll triggers an auto refresh
|
||||
private autoRefreshFetch$ = new Subject();
|
||||
private fetch$ = new Subject();
|
||||
|
||||
// Fired when a user changes the timerange
|
||||
this.timeUpdate$ = new Subject();
|
||||
private _time: TimeRange;
|
||||
private _refreshInterval!: RefreshInterval;
|
||||
|
||||
// Fired when a user changes the the autorefresh settings
|
||||
this.refreshIntervalUpdate$ = new Subject();
|
||||
public isTimeRangeSelectorEnabled: boolean = false;
|
||||
public isAutoRefreshSelectorEnabled: boolean = false;
|
||||
|
||||
// Used when search poll triggers an auto refresh
|
||||
this.autoRefreshFetch$ = new Subject();
|
||||
|
||||
this.fetch$ = new Subject();
|
||||
|
||||
this.isTimeRangeSelectorEnabled = false;
|
||||
this.isAutoRefreshSelectorEnabled = false;
|
||||
this._time = chrome.getUiSettingsClient().get('timepicker:timeDefaults');
|
||||
this.setRefreshInterval(chrome.getUiSettingsClient().get('timepicker:refreshIntervalDefaults'));
|
||||
constructor(uiSettings: UiSettingsClientContract) {
|
||||
this._time = uiSettings.get('timepicker:timeDefaults');
|
||||
this.setRefreshInterval(uiSettings.get('timepicker:refreshIntervalDefaults'));
|
||||
}
|
||||
|
||||
getEnabledUpdated$ = () => {
|
||||
return this.enabledUpdated$.asObservable();
|
||||
}
|
||||
};
|
||||
|
||||
getTimeUpdate$ = () => {
|
||||
return this.timeUpdate$.asObservable();
|
||||
}
|
||||
};
|
||||
|
||||
getRefreshIntervalUpdate$ = () => {
|
||||
return this.refreshIntervalUpdate$.asObservable();
|
||||
}
|
||||
};
|
||||
|
||||
getAutoRefreshFetch$ = () => {
|
||||
return this.autoRefreshFetch$.asObservable();
|
||||
}
|
||||
};
|
||||
|
||||
getFetch$ = () => {
|
||||
return this.fetch$.asObservable();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
getTime = () => {
|
||||
getTime = (): TimeRange => {
|
||||
const { from, to } = this._time;
|
||||
return {
|
||||
...this._time,
|
||||
from: moment.isMoment(from) ? from.toISOString() : from,
|
||||
to: moment.isMoment(to) ? to.toISOString() : to
|
||||
to: moment.isMoment(to) ? to.toISOString() : to,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates timefilter time.
|
||||
|
@ -88,10 +98,10 @@ class Timefilter {
|
|||
* @property {string|moment} time.from
|
||||
* @property {string|moment} time.to
|
||||
*/
|
||||
setTime = (time) => {
|
||||
setTime = (time: InputTimeRange) => {
|
||||
// Object.assign used for partially composed updates
|
||||
const newTime = Object.assign(this.getTime(), time);
|
||||
if (areTimePickerValsDifferent(this.getTime(), newTime)) {
|
||||
if (areTimeRangesDifferent(this.getTime(), newTime)) {
|
||||
this._time = {
|
||||
from: newTime.from,
|
||||
to: newTime.to,
|
||||
|
@ -100,11 +110,11 @@ class Timefilter {
|
|||
this.timeUpdate$.next();
|
||||
this.fetch$.next();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
getRefreshInterval = () => {
|
||||
return _.clone(this._refreshInterval);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set timefilter refresh interval.
|
||||
|
@ -112,7 +122,7 @@ class Timefilter {
|
|||
* @property {number} time.value Refresh interval in milliseconds. Positive integer
|
||||
* @property {boolean} time.pause
|
||||
*/
|
||||
setRefreshInterval = (refreshInterval) => {
|
||||
setRefreshInterval = (refreshInterval: Partial<RefreshInterval>) => {
|
||||
const prevRefreshInterval = this.getRefreshInterval();
|
||||
const newRefreshInterval = { ...prevRefreshInterval, ...refreshInterval };
|
||||
// If the refresh interval is <= 0 handle that as a paused refresh
|
||||
|
@ -122,32 +132,38 @@ class Timefilter {
|
|||
}
|
||||
this._refreshInterval = {
|
||||
value: newRefreshInterval.value,
|
||||
pause: newRefreshInterval.pause
|
||||
pause: newRefreshInterval.pause,
|
||||
};
|
||||
// Only send out an event if we already had a previous refresh interval (not for the initial set)
|
||||
// and the old and new refresh interval are actually different.
|
||||
if (prevRefreshInterval && areTimePickerValsDifferent(prevRefreshInterval, newRefreshInterval)) {
|
||||
if (
|
||||
prevRefreshInterval &&
|
||||
areRefreshIntervalsDifferent(prevRefreshInterval, newRefreshInterval)
|
||||
) {
|
||||
this.refreshIntervalUpdate$.next();
|
||||
if (!newRefreshInterval.pause && newRefreshInterval.value !== 0) {
|
||||
this.fetch$.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
toggleRefresh = () => {
|
||||
this.setRefreshInterval({ pause: !this._refreshInterval.pause });
|
||||
}
|
||||
this.setRefreshInterval({
|
||||
pause: !this._refreshInterval.pause,
|
||||
value: this._refreshInterval.value,
|
||||
});
|
||||
};
|
||||
|
||||
createFilter = (indexPattern, timeRange) => {
|
||||
createFilter = (indexPattern: IndexPattern, timeRange: TimeRange) => {
|
||||
return getTime(indexPattern, timeRange ? timeRange : this._time, this.getForceNow());
|
||||
}
|
||||
};
|
||||
|
||||
getBounds = () => {
|
||||
return this.calculateBounds(this._time);
|
||||
}
|
||||
};
|
||||
|
||||
getForceNow = () => {
|
||||
const forceNow = parseQueryString().forceNow;
|
||||
const forceNow = parseQueryString().forceNow as string;
|
||||
if (!forceNow) {
|
||||
return;
|
||||
}
|
||||
|
@ -157,82 +173,87 @@ class Timefilter {
|
|||
throw new Error(`forceNow query parameter, ${forceNow}, can't be parsed by Date.parse`);
|
||||
}
|
||||
return new Date(ticks);
|
||||
}
|
||||
};
|
||||
|
||||
calculateBounds = (timeRange) => {
|
||||
calculateBounds = (timeRange: TimeRange) => {
|
||||
return calculateBounds(timeRange, { forceNow: this.getForceNow() });
|
||||
}
|
||||
};
|
||||
|
||||
getActiveBounds = () => {
|
||||
if (this.isTimeRangeSelectorEnabled) {
|
||||
return this.getBounds();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Show the time bounds selector part of the time filter
|
||||
*/
|
||||
enableTimeRangeSelector = () => {
|
||||
this.isTimeRangeSelectorEnabled = true;
|
||||
this.enabledUpdated$.next();
|
||||
}
|
||||
this.enabledUpdated$.next(true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide the time bounds selector part of the time filter
|
||||
*/
|
||||
disableTimeRangeSelector = () => {
|
||||
this.isTimeRangeSelectorEnabled = false;
|
||||
this.enabledUpdated$.next();
|
||||
}
|
||||
this.enabledUpdated$.next(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Show the auto refresh part of the time filter
|
||||
*/
|
||||
enableAutoRefreshSelector = () => {
|
||||
this.isAutoRefreshSelectorEnabled = true;
|
||||
this.enabledUpdated$.next();
|
||||
}
|
||||
this.enabledUpdated$.next(true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide the auto refresh part of the time filter
|
||||
*/
|
||||
disableAutoRefreshSelector = () => {
|
||||
this.isAutoRefreshSelectorEnabled = false;
|
||||
this.enabledUpdated$.next();
|
||||
}
|
||||
this.enabledUpdated$.next(false);
|
||||
};
|
||||
|
||||
notifyShouldFetch = () => {
|
||||
this.autoRefreshFetch$.next();
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
export const timefilter = new Timefilter();
|
||||
export const timefilter = new Timefilter(chrome.getUiSettingsClient());
|
||||
|
||||
// TODO
|
||||
// remove everything underneath once globalState is no longer an angular service
|
||||
// and listener can be registered without angular.
|
||||
function convertISO8601(stringTime) {
|
||||
function convertISO8601(stringTime: string): string {
|
||||
const obj = moment(stringTime, 'YYYY-MM-DDTHH:mm:ss.SSSZ', true);
|
||||
return obj.isValid() ? obj : stringTime;
|
||||
return obj.isValid() ? obj.toString() : stringTime;
|
||||
}
|
||||
|
||||
// Currently some parts of Kibana (index patterns, timefilter) rely on addSetupWork in the uiRouter
|
||||
// and require it to be executed to properly function.
|
||||
// This function is exposed for applications that do not use uiRoutes like APM
|
||||
// Kibana issue https://github.com/elastic/kibana/issues/19110 tracks the removal of this dependency on uiRouter
|
||||
export const registerTimefilterWithGlobalState = _.once((globalState, $rootScope) => {
|
||||
export const registerTimefilterWithGlobalState = _.once((globalState: any, $rootScope: IScope) => {
|
||||
const uiSettings = chrome.getUiSettingsClient();
|
||||
const timeDefaults = uiSettings.get('timepicker:timeDefaults');
|
||||
const refreshIntervalDefaults = uiSettings.get('timepicker:refreshIntervalDefaults');
|
||||
|
||||
timefilter.setTime(_.defaults(globalState.time || {}, timeDefaults));
|
||||
timefilter.setRefreshInterval(_.defaults(globalState.refreshInterval || {}, refreshIntervalDefaults));
|
||||
timefilter.setRefreshInterval(
|
||||
_.defaults(globalState.refreshInterval || {}, refreshIntervalDefaults)
|
||||
);
|
||||
|
||||
globalState.on('fetch_with_changes', () => {
|
||||
// clone and default to {} in one
|
||||
const newTime = _.defaults({}, globalState.time, timeDefaults);
|
||||
const newRefreshInterval = _.defaults({}, globalState.refreshInterval, refreshIntervalDefaults);
|
||||
// clone and default to {} in one
|
||||
const newTime: TimeRange = _.defaults({}, globalState.time, timeDefaults);
|
||||
const newRefreshInterval: RefreshInterval = _.defaults(
|
||||
{},
|
||||
globalState.refreshInterval,
|
||||
refreshIntervalDefaults
|
||||
);
|
||||
|
||||
if (newTime) {
|
||||
if (newTime.to) newTime.to = convertISO8601(newTime.to);
|
||||
|
@ -250,16 +271,14 @@ export const registerTimefilterWithGlobalState = _.once((globalState, $rootScope
|
|||
};
|
||||
|
||||
subscribeWithScope($rootScope, timefilter.getRefreshIntervalUpdate$(), {
|
||||
next: updateGlobalStateWithTime
|
||||
next: updateGlobalStateWithTime,
|
||||
});
|
||||
|
||||
subscribeWithScope($rootScope, timefilter.getTimeUpdate$(), {
|
||||
next: updateGlobalStateWithTime
|
||||
next: updateGlobalStateWithTime,
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
uiRoutes
|
||||
.addSetupWork((globalState, $rootScope) => {
|
||||
return registerTimefilterWithGlobalState(globalState, $rootScope);
|
||||
});
|
||||
uiRoutes.addSetupWork((globalState, $rootScope) => {
|
||||
return registerTimefilterWithGlobalState(globalState, $rootScope);
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue