mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Add 5s minRefreshInterval
to timefilter
with validation (#188881)
Adds enforcement for the global time filter to limit the min refresh interval.
This commit is contained in:
parent
af4f482f5f
commit
94b657e6c9
27 changed files with 239 additions and 48 deletions
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { schema, TypeOf } from '@kbn/config-schema';
|
||||
import { offeringBasedSchema, schema, TypeOf } from '@kbn/config-schema';
|
||||
|
||||
export const searchSessionsConfigSchema = schema.object({
|
||||
/**
|
||||
|
@ -84,7 +84,23 @@ export const searchConfigSchema = schema.object({
|
|||
sessions: searchSessionsConfigSchema,
|
||||
});
|
||||
|
||||
export const queryConfigSchema = schema.object({
|
||||
/**
|
||||
* Config for timefilter
|
||||
*/
|
||||
timefilter: schema.object({
|
||||
/**
|
||||
* Lower limit of refresh interval (in milliseconds)
|
||||
*/
|
||||
minRefreshInterval: offeringBasedSchema<number>({
|
||||
serverless: schema.number({ min: 1000, defaultValue: 5000 }),
|
||||
traditional: schema.number({ min: 1000, defaultValue: 1000 }),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const configSchema = schema.object({
|
||||
query: queryConfigSchema,
|
||||
search: searchConfigSchema,
|
||||
/**
|
||||
* Turns on/off limit validations for the registered uiSettings.
|
||||
|
@ -96,4 +112,6 @@ export type ConfigSchema = TypeOf<typeof configSchema>;
|
|||
|
||||
export type SearchConfigSchema = TypeOf<typeof searchConfigSchema>;
|
||||
|
||||
export type QueryConfigSchema = TypeOf<typeof queryConfigSchema>;
|
||||
|
||||
export type SearchSessionsConfigSchema = TypeOf<typeof searchSessionsConfigSchema>;
|
||||
|
|
|
@ -59,7 +59,9 @@ export class DataPublicPlugin
|
|||
|
||||
constructor(initializerContext: PluginInitializerContext<ConfigSchema>) {
|
||||
this.searchService = new SearchService(initializerContext);
|
||||
this.queryService = new QueryService();
|
||||
this.queryService = new QueryService(
|
||||
initializerContext.config.get().query.timefilter.minRefreshInterval
|
||||
);
|
||||
|
||||
this.storage = new Storage(window.localStorage);
|
||||
this.nowProvider = new NowProvider();
|
||||
|
|
|
@ -18,6 +18,8 @@ import { StubBrowserStorage } from '@kbn/test-jest-helpers';
|
|||
import { TimefilterContract } from './timefilter';
|
||||
import { createNowProviderMock } from '../now_provider/mocks';
|
||||
|
||||
const minRefreshIntervalDefault = 1000;
|
||||
|
||||
const setupMock = coreMock.createSetup();
|
||||
const startMock = coreMock.createStart();
|
||||
|
||||
|
@ -48,6 +50,7 @@ describe('query_service', () => {
|
|||
uiSettings: setupMock.uiSettings,
|
||||
storage: new Storage(new StubBrowserStorage()),
|
||||
nowProvider: createNowProviderMock(),
|
||||
minRefreshInterval: minRefreshIntervalDefault,
|
||||
});
|
||||
queryServiceStart = queryService.start({
|
||||
uiSettings: setupMock.uiSettings,
|
||||
|
@ -82,7 +85,7 @@ describe('query_service', () => {
|
|||
const filters = [getFilter(FilterStateStore.GLOBAL_STATE, true, true, 'key1', 'value1')];
|
||||
const query = { language: 'kql', query: 'query' };
|
||||
const time = { from: new Date().toISOString(), to: new Date().toISOString() };
|
||||
const refreshInterval = { pause: false, value: 10 };
|
||||
const refreshInterval = { pause: false, value: 2000 };
|
||||
|
||||
filterManager.setFilters(filters);
|
||||
queryStringManager.setQuery(query);
|
||||
|
|
|
@ -39,6 +39,7 @@ interface QueryServiceSetupDependencies {
|
|||
storage: IStorageWrapper;
|
||||
uiSettings: IUiSettingsClient;
|
||||
nowProvider: NowProviderInternalContract;
|
||||
minRefreshInterval?: number;
|
||||
}
|
||||
|
||||
interface QueryServiceStartDependencies {
|
||||
|
@ -81,13 +82,21 @@ export class QueryService implements PersistableStateService<QueryState> {
|
|||
|
||||
state$!: QueryState$;
|
||||
|
||||
public setup({ storage, uiSettings, nowProvider }: QueryServiceSetupDependencies): QuerySetup {
|
||||
constructor(private minRefreshInterval: number = 5000) {}
|
||||
|
||||
public setup({
|
||||
storage,
|
||||
uiSettings,
|
||||
nowProvider,
|
||||
minRefreshInterval = this.minRefreshInterval,
|
||||
}: QueryServiceSetupDependencies): QuerySetup {
|
||||
this.filterManager = new FilterManager(uiSettings);
|
||||
|
||||
const timefilterService = new TimefilterService(nowProvider);
|
||||
this.timefilter = timefilterService.setup({
|
||||
uiSettings,
|
||||
storage,
|
||||
minRefreshInterval,
|
||||
});
|
||||
|
||||
this.queryStringManager = new QueryStringManager(storage, uiSettings);
|
||||
|
|
|
@ -66,7 +66,7 @@ describe('connect_to_global_state', () => {
|
|||
let aF2: Filter;
|
||||
|
||||
beforeEach(() => {
|
||||
const queryService = new QueryService();
|
||||
const queryService = new QueryService(1000);
|
||||
queryService.setup({
|
||||
uiSettings: setupMock.uiSettings,
|
||||
storage: new Storage(new StubBrowserStorage()),
|
||||
|
@ -120,11 +120,21 @@ describe('connect_to_global_state', () => {
|
|||
});
|
||||
|
||||
test('when refresh interval changes, state container contains updated refresh interval', () => {
|
||||
const stop = connectToQueryGlobalState(queryServiceStart, globalState);
|
||||
timeFilter.setRefreshInterval({ pause: true, value: 5000 });
|
||||
expect(globalState.get().refreshInterval).toEqual({
|
||||
pause: true,
|
||||
value: 5000,
|
||||
});
|
||||
stop();
|
||||
});
|
||||
|
||||
test('when refresh interval is set below min, state container contains min refresh interval', () => {
|
||||
const stop = connectToQueryGlobalState(queryServiceStart, globalState);
|
||||
timeFilter.setRefreshInterval({ pause: true, value: 100 });
|
||||
expect(globalState.get().refreshInterval).toEqual({
|
||||
pause: true,
|
||||
value: 100,
|
||||
value: 1000,
|
||||
});
|
||||
stop();
|
||||
});
|
||||
|
@ -135,14 +145,14 @@ describe('connect_to_global_state', () => {
|
|||
globalState.set({
|
||||
...globalState.get(),
|
||||
filters: [gF1, gF2],
|
||||
refreshInterval: { pause: true, value: 100 },
|
||||
refreshInterval: { pause: true, value: 5000 },
|
||||
time: { from: 'now-30m', to: 'now' },
|
||||
});
|
||||
|
||||
expect(globalStateChangeTriggered).toBeCalledTimes(1);
|
||||
|
||||
expect(filterManager.getGlobalFilters()).toHaveLength(2);
|
||||
expect(timeFilter.getRefreshInterval()).toEqual({ pause: true, value: 100 });
|
||||
expect(timeFilter.getRefreshInterval()).toEqual({ pause: true, value: 5000 });
|
||||
expect(timeFilter.getTime()).toEqual({ from: 'now-30m', to: 'now' });
|
||||
stop();
|
||||
});
|
||||
|
|
|
@ -25,6 +25,8 @@ import { syncQueryStateWithUrl } from './sync_state_with_url';
|
|||
import { GlobalQueryStateFromUrl } from './types';
|
||||
import { createNowProviderMock } from '../../now_provider/mocks';
|
||||
|
||||
const minRefreshIntervalDefault = 1000;
|
||||
|
||||
const setupMock = coreMock.createSetup();
|
||||
const startMock = coreMock.createStart();
|
||||
|
||||
|
@ -65,6 +67,7 @@ describe('sync_query_state_with_url', () => {
|
|||
uiSettings: setupMock.uiSettings,
|
||||
storage: new Storage(new StubBrowserStorage()),
|
||||
nowProvider: createNowProviderMock(),
|
||||
minRefreshInterval: minRefreshIntervalDefault,
|
||||
});
|
||||
queryServiceStart = queryService.start({
|
||||
uiSettings: startMock.uiSettings,
|
||||
|
@ -93,7 +96,7 @@ describe('sync_query_state_with_url', () => {
|
|||
filterManager.setFilters([gF, aF]);
|
||||
kbnUrlStateStorage.kbnUrlControls.flush(); // sync force location change
|
||||
expect(history.location.hash).toMatchInlineSnapshot(
|
||||
`"#?_g=(filters:!(('$state':(store:globalState),meta:(alias:!n,disabled:!t,index:'logstash-*',key:query,negate:!t,type:custom,value:'%7B%22match%22:%7B%22key1%22:%22value1%22%7D%7D'),query:(match:(key1:value1)))),refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))"`
|
||||
`"#?_g=(filters:!(('$state':(store:globalState),meta:(alias:!n,disabled:!t,index:'logstash-*',key:query,negate:!t,type:custom,value:'%7B%22match%22:%7B%22key1%22:%22value1%22%7D%7D'),query:(match:(key1:value1)))),refreshInterval:(pause:!t,value:1000),time:(from:now-15m,to:now))"`
|
||||
);
|
||||
stop();
|
||||
});
|
||||
|
@ -116,11 +119,21 @@ describe('sync_query_state_with_url', () => {
|
|||
});
|
||||
|
||||
test('when refresh interval changes, refresh interval is synced to urlStorage', () => {
|
||||
const { stop } = syncQueryStateWithUrl(queryServiceStart, kbnUrlStateStorage);
|
||||
timefilter.setRefreshInterval({ pause: true, value: 5000 });
|
||||
expect(kbnUrlStateStorage.get<GlobalQueryStateFromUrl>('_g')?.refreshInterval).toEqual({
|
||||
pause: true,
|
||||
value: 5000,
|
||||
});
|
||||
stop();
|
||||
});
|
||||
|
||||
test('when refresh interval is set below min, refresh interval is not synced to urlStorage', () => {
|
||||
const { stop } = syncQueryStateWithUrl(queryServiceStart, kbnUrlStateStorage);
|
||||
timefilter.setRefreshInterval({ pause: true, value: 100 });
|
||||
expect(kbnUrlStateStorage.get<GlobalQueryStateFromUrl>('_g')?.refreshInterval).toEqual({
|
||||
pause: true,
|
||||
value: 100,
|
||||
value: minRefreshIntervalDefault,
|
||||
});
|
||||
stop();
|
||||
});
|
||||
|
|
|
@ -17,15 +17,23 @@ import { RefreshInterval } from '../../../common';
|
|||
import { createNowProviderMock } from '../../now_provider/mocks';
|
||||
|
||||
import { timefilterServiceMock } from './timefilter_service.mock';
|
||||
const timefilterSetupMock = timefilterServiceMock.createSetupContract();
|
||||
import { TimefilterConfig } from './types';
|
||||
|
||||
const timefilterConfig = {
|
||||
const timefilterSetupMock = timefilterServiceMock.createSetupContract();
|
||||
const minRefreshIntervalDefault = 1000;
|
||||
|
||||
const defaultTimefilterConfig: TimefilterConfig = {
|
||||
timeDefaults: { from: 'now-15m', to: 'now' },
|
||||
refreshIntervalDefaults: { pause: false, value: 0 },
|
||||
refreshIntervalDefaults: { pause: false, value: minRefreshIntervalDefault },
|
||||
minRefreshIntervalDefault,
|
||||
};
|
||||
|
||||
const nowProviderMock = createNowProviderMock();
|
||||
const timefilter = new Timefilter(timefilterConfig, timefilterSetupMock.history, nowProviderMock);
|
||||
let timefilter = new Timefilter(
|
||||
defaultTimefilterConfig,
|
||||
timefilterSetupMock.history,
|
||||
nowProviderMock
|
||||
);
|
||||
|
||||
function stubNowTime(nowTime: any) {
|
||||
nowProviderMock.get.mockImplementation(() => (nowTime ? new Date(nowTime) : new Date()));
|
||||
|
@ -118,21 +126,28 @@ describe('setRefreshInterval', () => {
|
|||
let fetchSub: Subscription;
|
||||
let refreshSub: Subscription;
|
||||
let autoRefreshSub: Subscription;
|
||||
let prevTimefilter = timefilter;
|
||||
|
||||
beforeEach(() => {
|
||||
function setup() {
|
||||
update = sinon.spy();
|
||||
fetch = sinon.spy();
|
||||
autoRefreshFetch = sinon.spy((done) => done());
|
||||
timefilter.setRefreshInterval({
|
||||
pause: false,
|
||||
value: 0,
|
||||
value: minRefreshIntervalDefault,
|
||||
});
|
||||
refreshSub = timefilter.getRefreshIntervalUpdate$().subscribe(update);
|
||||
fetchSub = timefilter.getFetch$().subscribe(fetch);
|
||||
autoRefreshSub = timefilter.getAutoRefreshFetch$().subscribe(autoRefreshFetch);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
prevTimefilter = timefilter;
|
||||
setup();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
timefilter = prevTimefilter;
|
||||
refreshSub.unsubscribe();
|
||||
fetchSub.unsubscribe();
|
||||
autoRefreshSub.unsubscribe();
|
||||
|
@ -142,16 +157,50 @@ describe('setRefreshInterval', () => {
|
|||
expect(timefilter.isRefreshIntervalTouched()).toBe(false);
|
||||
});
|
||||
|
||||
test('should register changes to the initial interval', () => {
|
||||
timefilter.setRefreshInterval(timefilterConfig.refreshIntervalDefaults);
|
||||
test('should limit initial default value to minRefreshIntervalDefault', () => {
|
||||
timefilter = new Timefilter(
|
||||
{
|
||||
...defaultTimefilterConfig,
|
||||
refreshIntervalDefaults: { pause: false, value: minRefreshIntervalDefault - 100 },
|
||||
},
|
||||
timefilterSetupMock.history,
|
||||
nowProviderMock
|
||||
);
|
||||
setup();
|
||||
|
||||
expect(timefilter.isRefreshIntervalTouched()).toBe(false);
|
||||
timefilter.setRefreshInterval({ pause: false, value: 1000 });
|
||||
expect(timefilter.getRefreshInterval()).toEqual({
|
||||
pause: false,
|
||||
value: minRefreshIntervalDefault,
|
||||
});
|
||||
});
|
||||
|
||||
test('should pause if initial value is set to 0 regardless of minRefreshInterval', () => {
|
||||
timefilter = new Timefilter(
|
||||
{
|
||||
...defaultTimefilterConfig,
|
||||
refreshIntervalDefaults: { pause: false, value: 0 },
|
||||
},
|
||||
timefilterSetupMock.history,
|
||||
nowProviderMock
|
||||
);
|
||||
|
||||
expect(timefilter.getRefreshInterval()).toEqual({
|
||||
pause: true,
|
||||
value: minRefreshIntervalDefault,
|
||||
});
|
||||
});
|
||||
|
||||
test('should register changes to the initial interval', () => {
|
||||
timefilter.setRefreshInterval(defaultTimefilterConfig.refreshIntervalDefaults);
|
||||
expect(timefilter.isRefreshIntervalTouched()).toBe(false);
|
||||
timefilter.setRefreshInterval({ pause: false, value: 5000 });
|
||||
expect(timefilter.isRefreshIntervalTouched()).toBe(true);
|
||||
});
|
||||
|
||||
test('should update refresh interval', () => {
|
||||
timefilter.setRefreshInterval({ pause: true, value: 10 });
|
||||
expect(timefilter.getRefreshInterval()).toEqual({ pause: true, value: 10 });
|
||||
timefilter.setRefreshInterval({ pause: true, value: 5000 });
|
||||
expect(timefilter.getRefreshInterval()).toEqual({ pause: true, value: 5000 });
|
||||
});
|
||||
|
||||
test('should not add unexpected object keys to refreshInterval state', () => {
|
||||
|
@ -165,23 +214,40 @@ describe('setRefreshInterval', () => {
|
|||
});
|
||||
|
||||
test('should allow partial updates to refresh interval', () => {
|
||||
timefilter.setRefreshInterval({ value: 10 });
|
||||
expect(timefilter.getRefreshInterval()).toEqual({ pause: true, value: 10 });
|
||||
const { pause } = timefilter.getRefreshInterval();
|
||||
timefilter.setRefreshInterval({ value: 5000 });
|
||||
expect(timefilter.getRefreshInterval()).toEqual({ pause, value: 5000 });
|
||||
});
|
||||
|
||||
test('should not allow negative intervals', () => {
|
||||
timefilter.setRefreshInterval({ value: -10 });
|
||||
expect(timefilter.getRefreshInterval()).toEqual({ pause: true, value: 0 });
|
||||
expect(timefilter.getRefreshInterval()).toEqual({ pause: false, value: 1000 });
|
||||
});
|
||||
|
||||
test('should set pause to true when interval is changed to zero from non-zero', () => {
|
||||
timefilter = new Timefilter(
|
||||
{
|
||||
...defaultTimefilterConfig,
|
||||
minRefreshIntervalDefault: 0,
|
||||
},
|
||||
timefilterSetupMock.history,
|
||||
nowProviderMock
|
||||
);
|
||||
setup();
|
||||
|
||||
timefilter.setRefreshInterval({ value: 1000, pause: false });
|
||||
timefilter.setRefreshInterval({ value: 0, pause: false });
|
||||
expect(timefilter.getRefreshInterval()).toEqual({ pause: true, value: 0 });
|
||||
});
|
||||
|
||||
test('should pause when interval is set to zero regardless of minRefreshInterval', () => {
|
||||
timefilter.setRefreshInterval({ value: 5000, pause: false });
|
||||
timefilter.setRefreshInterval({ value: 0 });
|
||||
expect(timefilter.getRefreshInterval()).toEqual({ pause: true, value: 1000 });
|
||||
});
|
||||
|
||||
test('not emit anything if nothing has changed', () => {
|
||||
timefilter.setRefreshInterval({ pause: false, value: 0 });
|
||||
timefilter.setRefreshInterval(timefilter.getRefreshInterval());
|
||||
expect(update.called).toBe(false);
|
||||
expect(fetch.called).toBe(false);
|
||||
});
|
||||
|
@ -192,7 +258,25 @@ describe('setRefreshInterval', () => {
|
|||
expect(fetch.called).toBe(false);
|
||||
});
|
||||
|
||||
test('should not emit update, nor fetch, when setting interval below min', () => {
|
||||
const prevInterval = timefilter.getRefreshInterval();
|
||||
timefilter.setRefreshInterval({ value: minRefreshIntervalDefault - 100 });
|
||||
expect(update.called).toBe(false);
|
||||
expect(fetch.called).toBe(false);
|
||||
expect(timefilter.getRefreshInterval()).toEqual(prevInterval);
|
||||
});
|
||||
|
||||
test('emit update, not fetch, when switching to value: 0', () => {
|
||||
timefilter = new Timefilter(
|
||||
{
|
||||
...defaultTimefilterConfig,
|
||||
minRefreshIntervalDefault: 0,
|
||||
},
|
||||
timefilterSetupMock.history,
|
||||
nowProviderMock
|
||||
);
|
||||
setup();
|
||||
|
||||
timefilter.setRefreshInterval({ pause: false, value: 5000 });
|
||||
expect(update.calledOnce).toBe(true);
|
||||
expect(fetch.calledOnce).toBe(true);
|
||||
|
|
|
@ -27,7 +27,6 @@ import { createAutoRefreshLoop, AutoRefreshDoneFn } from './lib/auto_refresh_loo
|
|||
|
||||
export type { AutoRefreshDoneFn };
|
||||
|
||||
// TODO: remove!
|
||||
export class Timefilter {
|
||||
// Fired when isTimeRangeSelectorEnabled \ isAutoRefreshSelectorEnabled are toggled
|
||||
private enabledUpdated$ = new BehaviorSubject(false);
|
||||
|
@ -41,6 +40,7 @@ export class Timefilter {
|
|||
// Denotes whether setTime has been called, can be used to determine if the constructor defaults are being used.
|
||||
private _isTimeTouched: boolean = false;
|
||||
private _refreshInterval!: RefreshInterval;
|
||||
private _minRefreshInterval: number;
|
||||
// Denotes whether the refresh interval defaults were overriden.
|
||||
private _isRefreshIntervalTouched: boolean = false;
|
||||
private _history: TimeHistoryContract;
|
||||
|
@ -61,7 +61,15 @@ export class Timefilter {
|
|||
) {
|
||||
this._history = timeHistory;
|
||||
this.timeDefaults = config.timeDefaults;
|
||||
this.refreshIntervalDefaults = config.refreshIntervalDefaults;
|
||||
|
||||
// Initialize 0ms intervals with pause set to true and min value
|
||||
this.refreshIntervalDefaults = {
|
||||
pause:
|
||||
config.refreshIntervalDefaults.value === 0 ? true : config.refreshIntervalDefaults.pause,
|
||||
value: Math.max(config.refreshIntervalDefaults.value, config.minRefreshIntervalDefault),
|
||||
};
|
||||
|
||||
this._minRefreshInterval = config.minRefreshIntervalDefault;
|
||||
this._time = config.timeDefaults;
|
||||
this.setRefreshInterval(config.refreshIntervalDefaults);
|
||||
}
|
||||
|
@ -148,6 +156,10 @@ export class Timefilter {
|
|||
return _.clone(this._refreshInterval);
|
||||
};
|
||||
|
||||
public getMinRefreshInterval = () => {
|
||||
return this._minRefreshInterval;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set timefilter refresh interval.
|
||||
* @param {Object} refreshInterval
|
||||
|
@ -157,6 +169,16 @@ export class Timefilter {
|
|||
public setRefreshInterval = (refreshInterval: Partial<RefreshInterval>) => {
|
||||
const prevRefreshInterval = this.getRefreshInterval();
|
||||
const newRefreshInterval = { ...prevRefreshInterval, ...refreshInterval };
|
||||
|
||||
if (newRefreshInterval.value === 0) {
|
||||
// override only when explicitly set to 0
|
||||
newRefreshInterval.pause = true;
|
||||
}
|
||||
|
||||
if (newRefreshInterval.value < this._minRefreshInterval) {
|
||||
newRefreshInterval.value = this._minRefreshInterval;
|
||||
}
|
||||
|
||||
let shouldUnpauseRefreshLoop =
|
||||
newRefreshInterval.pause === false && prevRefreshInterval != null;
|
||||
if (prevRefreshInterval?.value > 0 && newRefreshInterval.value <= 0) {
|
||||
|
|
|
@ -28,6 +28,7 @@ const createSetupContractMock = () => {
|
|||
setTime: jest.fn(),
|
||||
setRefreshInterval: jest.fn(),
|
||||
getRefreshInterval: jest.fn(),
|
||||
getMinRefreshInterval: jest.fn().mockReturnValue(1000),
|
||||
getActiveBounds: jest.fn(),
|
||||
disableAutoRefreshSelector: jest.fn(),
|
||||
disableTimeRangeSelector: jest.fn(),
|
||||
|
|
|
@ -8,27 +8,38 @@
|
|||
|
||||
import { IUiSettingsClient } from '@kbn/core/public';
|
||||
import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public';
|
||||
import { TimeHistory, Timefilter, TimeHistoryContract, TimefilterContract } from '.';
|
||||
import {
|
||||
TimeHistory,
|
||||
Timefilter,
|
||||
TimeHistoryContract,
|
||||
TimefilterContract,
|
||||
TimefilterConfig,
|
||||
} from '.';
|
||||
import { UI_SETTINGS } from '../../../common';
|
||||
import { NowProviderInternalContract } from '../../now_provider';
|
||||
|
||||
export interface TimeFilterServiceDependencies {
|
||||
uiSettings: IUiSettingsClient;
|
||||
storage: IStorageWrapper;
|
||||
minRefreshInterval: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter Service
|
||||
* @internal
|
||||
*/
|
||||
|
||||
export interface TimeFilterServiceDependencies {
|
||||
uiSettings: IUiSettingsClient;
|
||||
storage: IStorageWrapper;
|
||||
}
|
||||
|
||||
export class TimefilterService {
|
||||
constructor(private readonly nowProvider: NowProviderInternalContract) {}
|
||||
|
||||
public setup({ uiSettings, storage }: TimeFilterServiceDependencies): TimefilterSetup {
|
||||
const timefilterConfig = {
|
||||
public setup({
|
||||
uiSettings,
|
||||
storage,
|
||||
minRefreshInterval,
|
||||
}: TimeFilterServiceDependencies): TimefilterSetup {
|
||||
const timefilterConfig: TimefilterConfig = {
|
||||
timeDefaults: uiSettings.get(UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS),
|
||||
refreshIntervalDefaults: uiSettings.get(UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS),
|
||||
minRefreshIntervalDefault: minRefreshInterval,
|
||||
};
|
||||
const history = new TimeHistory(storage);
|
||||
const timefilter = new Timefilter(timefilterConfig, history, this.nowProvider);
|
||||
|
|
|
@ -14,6 +14,7 @@ import { RefreshInterval } from '../../../common';
|
|||
export interface TimefilterConfig {
|
||||
timeDefaults: TimeRange;
|
||||
refreshIntervalDefaults: RefreshInterval;
|
||||
minRefreshIntervalDefault: number;
|
||||
}
|
||||
|
||||
// Timefilter accepts moment input but always returns string output
|
||||
|
|
|
@ -107,6 +107,7 @@ export const config: PluginConfigDescriptor<ConfigSchema> = {
|
|||
deprecations: configDeprecationProvider,
|
||||
exposeToBrowser: {
|
||||
search: true,
|
||||
query: true,
|
||||
},
|
||||
schema: configSchema,
|
||||
};
|
||||
|
|
|
@ -185,7 +185,7 @@ export function getDataStateContainer({
|
|||
totalHits$: new BehaviorSubject<DataTotalHitsMsg>(initialState),
|
||||
};
|
||||
|
||||
let autoRefreshDone: AutoRefreshDoneFn | undefined;
|
||||
let autoRefreshDone: AutoRefreshDoneFn | undefined | null = null;
|
||||
/**
|
||||
* handler emitted by `timefilter.getAutoRefreshFetch$()`
|
||||
* to notify when data completed loading and to start a new autorefresh loop
|
||||
|
@ -304,8 +304,8 @@ export function getDataStateContainer({
|
|||
|
||||
// If the autoRefreshCallback is still the same as when we started i.e. there was no newer call
|
||||
// replacing this current one, call it to make sure we tell that the auto refresh is done
|
||||
// and a new one can be scheduled.
|
||||
if (autoRefreshDone === prevAutoRefreshDone) {
|
||||
// and a new one can be scheduled. null is checked to always start initial looping.
|
||||
if (autoRefreshDone === prevAutoRefreshDone || prevAutoRefreshDone === null) {
|
||||
// if this function was set and is executed, another refresh fetch can be triggered
|
||||
autoRefreshDone?.();
|
||||
autoRefreshDone = undefined;
|
||||
|
|
|
@ -141,7 +141,7 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
description: 'Analyze mock eCommerce orders and revenue',
|
||||
refreshInterval: {
|
||||
pause: true,
|
||||
value: 0,
|
||||
value: 60000,
|
||||
},
|
||||
timeRestore: true,
|
||||
optionsJSON:
|
||||
|
|
|
@ -141,7 +141,7 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
'Analyze mock flight data for ES-Air, Logstash Airways, Kibana Airlines and JetBeats',
|
||||
refreshInterval: {
|
||||
pause: true,
|
||||
value: 0,
|
||||
value: 60000,
|
||||
},
|
||||
timeRestore: true,
|
||||
optionsJSON:
|
||||
|
|
|
@ -305,8 +305,8 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
},
|
||||
description: "Analyze mock web traffic log data for Elastic's website",
|
||||
refreshInterval: {
|
||||
pause: false,
|
||||
value: 900000,
|
||||
pause: true,
|
||||
value: 60000,
|
||||
},
|
||||
timeRestore: true,
|
||||
optionsJSON:
|
||||
|
|
|
@ -79,7 +79,7 @@ export function TopNavMenu<QT extends AggregateQuery | Query = Query>(
|
|||
}
|
||||
|
||||
function renderSearchBar(): ReactElement | null {
|
||||
// Validate presense of all required fields
|
||||
// Validate presence of all required fields
|
||||
if (!showSearchBar || !props.unifiedSearch) return null;
|
||||
const { AggregateQuerySearchBar } = props.unifiedSearch.ui;
|
||||
return <AggregateQuerySearchBar<QT> {...searchBarProps} />;
|
||||
|
|
|
@ -152,6 +152,7 @@ export interface QueryBarTopRowProps<QT extends Query | AggregateQuery = Query>
|
|||
prepend?: React.ComponentProps<typeof EuiFieldText>['prepend'];
|
||||
query?: Query | QT;
|
||||
refreshInterval?: number;
|
||||
minRefreshInterval?: number;
|
||||
screenTitle?: string;
|
||||
showQueryInput?: boolean;
|
||||
showAddFilter?: boolean;
|
||||
|
@ -503,6 +504,7 @@ export const QueryBarTopRow = React.memo(
|
|||
end={props.dateRangeTo}
|
||||
isPaused={props.isRefreshPaused}
|
||||
refreshInterval={props.refreshInterval}
|
||||
refreshMinInterval={props.minRefreshInterval}
|
||||
onTimeChange={onTimeChange}
|
||||
onRefresh={onRefresh}
|
||||
onRefreshChange={props.onRefreshChange}
|
||||
|
|
|
@ -171,7 +171,7 @@ export function createSearchBar({
|
|||
query: props.query,
|
||||
queryStringManager: data.query.queryString,
|
||||
}) as { query: QT };
|
||||
const { timeRange, refreshInterval } = useTimefilter({
|
||||
const { timeRange, refreshInterval, minRefreshInterval } = useTimefilter({
|
||||
dateRangeFrom: props.dateRangeFrom,
|
||||
dateRangeTo: props.dateRangeTo,
|
||||
refreshInterval: props.refreshInterval,
|
||||
|
@ -232,6 +232,7 @@ export function createSearchBar({
|
|||
timeHistory={data.query.timefilter.history}
|
||||
dateRangeFrom={timeRange.from}
|
||||
dateRangeTo={timeRange.to}
|
||||
minRefreshInterval={minRefreshInterval}
|
||||
refreshInterval={refreshInterval.value}
|
||||
isRefreshPaused={refreshInterval.pause}
|
||||
isLoading={props.isLoading}
|
||||
|
|
|
@ -59,5 +59,6 @@ export const useTimefilter = (props: UseTimefilterProps) => {
|
|||
return {
|
||||
refreshInterval,
|
||||
timeRange,
|
||||
minRefreshInterval: props.timefilter.getMinRefreshInterval(),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -79,6 +79,7 @@ export interface SearchBarOwnProps<QT extends AggregateQuery | Query = Query> {
|
|||
// Date picker
|
||||
isRefreshPaused?: boolean;
|
||||
refreshInterval?: number;
|
||||
minRefreshInterval?: number;
|
||||
dateRangeFrom?: string;
|
||||
dateRangeTo?: string;
|
||||
// Query bar - should be in SearchBarInjectedDeps
|
||||
|
@ -619,6 +620,7 @@ class SearchBarUI<QT extends (Query | AggregateQuery) | Query = Query> extends C
|
|||
dateRangeTo={this.state.dateRangeTo}
|
||||
isRefreshPaused={this.props.isRefreshPaused}
|
||||
refreshInterval={this.props.refreshInterval}
|
||||
minRefreshInterval={this.props.minRefreshInterval}
|
||||
showAutoRefreshOnly={this.props.showAutoRefreshOnly}
|
||||
showQueryInput={this.props.showQueryInput}
|
||||
showAddFilter={this.props.showFilterBar}
|
||||
|
|
|
@ -42,6 +42,7 @@ export default function () {
|
|||
`--elasticsearch.password=${kibanaServerTestUser.password}`,
|
||||
// Needed for async search functional tests to introduce a delay
|
||||
`--data.search.aggs.shardDelay.enabled=true`,
|
||||
`--data.query.timefilter.minRefreshInterval=1000`,
|
||||
`--security.showInsecureClusterWarning=false`,
|
||||
'--telemetry.banner=false',
|
||||
'--telemetry.optIn=false',
|
||||
|
|
|
@ -123,6 +123,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
|
|||
'data.search.sessions.management.refreshTimeout (duration?)',
|
||||
'data.search.sessions.maxUpdateRetries (number?)',
|
||||
'data.search.sessions.notTouchedTimeout (duration?)',
|
||||
'data.query.timefilter.minRefreshInterval (number?)',
|
||||
'data_views.scriptedFieldsEnabled (boolean?|never)',
|
||||
'data_visualizer.resultLinks.fileBeat.enabled (boolean)',
|
||||
'dev_tools.deeplinks.navLinkStatus (string?)',
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { FilterQueryContextProvider, useFilterQueryUpdates } from './use_filters_query';
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import { dataPluginMock as mockDataPlugin } from '@kbn/data-plugin/public/mocks';
|
||||
import type { TimefilterConfig } from '@kbn/data-plugin/public/query';
|
||||
import { Timefilter } from '@kbn/data-plugin/public/query';
|
||||
import { useAiopsAppContext } from './use_aiops_app_context';
|
||||
import { useReload } from './use_reload';
|
||||
|
@ -43,9 +44,10 @@ describe('useFilterQueryUpdates', () => {
|
|||
test('provides correct search bounds for relative time range on each reload', async () => {
|
||||
const mockDataContract = mockDataPlugin.createStartContract();
|
||||
|
||||
const mockTimefilterConfig = {
|
||||
const mockTimefilterConfig: TimefilterConfig = {
|
||||
timeDefaults: { from: 'now-15m', to: 'now' },
|
||||
refreshIntervalDefaults: { pause: false, value: 0 },
|
||||
minRefreshIntervalDefault: 1000,
|
||||
};
|
||||
|
||||
useAiopsAppContext().data.query.timefilter.timefilter = new Timefilter(
|
||||
|
|
|
@ -61,7 +61,7 @@ import { alertingPluginMock } from '@kbn/alerting-plugin/public/mocks';
|
|||
|
||||
const mockUiSettings: Record<string, unknown> = {
|
||||
[DEFAULT_TIME_RANGE]: { from: 'now-15m', to: 'now', mode: 'quick' },
|
||||
[DEFAULT_REFRESH_RATE_INTERVAL]: { pause: false, value: 0 },
|
||||
[DEFAULT_REFRESH_RATE_INTERVAL]: { pause: true, value: 5000 },
|
||||
[DEFAULT_APP_TIME_RANGE]: {
|
||||
from: DEFAULT_FROM,
|
||||
to: DEFAULT_TO,
|
||||
|
|
|
@ -70,16 +70,21 @@ export class PluginServices {
|
|||
{ prebuiltRulesPackageVersion: this.prebuiltRulesPackageVersion }
|
||||
);
|
||||
|
||||
const minRefreshInterval =
|
||||
pluginsSetup.data.query.timefilter.timefilter.getMinRefreshInterval();
|
||||
|
||||
this.queryService.setup({
|
||||
uiSettings: coreSetup.uiSettings,
|
||||
storage: this.storage,
|
||||
nowProvider: new NowProvider(),
|
||||
minRefreshInterval,
|
||||
});
|
||||
|
||||
this.timelineQueryService.setup({
|
||||
uiSettings: coreSetup.uiSettings,
|
||||
storage: this.storage,
|
||||
nowProvider: new NowProvider(),
|
||||
minRefreshInterval,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import type { Observable } from 'rxjs';
|
|||
|
||||
import type { CoreStart, AppMountParameters, AppLeaveHandler } from '@kbn/core/public';
|
||||
import type { HomePublicPluginSetup } from '@kbn/home-plugin/public';
|
||||
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import type { DataPublicPluginStart, DataPublicPluginSetup } from '@kbn/data-plugin/public';
|
||||
import type { FieldFormatsStartCommon } from '@kbn/field-formats-plugin/common';
|
||||
import type { EmbeddableStart } from '@kbn/embeddable-plugin/public';
|
||||
import type { LensPublicStart } from '@kbn/lens-plugin/public';
|
||||
|
@ -103,6 +103,7 @@ export interface SetupPlugins {
|
|||
usageCollection?: UsageCollectionSetup;
|
||||
ml?: MlPluginSetup;
|
||||
cases?: CasesPublicSetup;
|
||||
data: DataPublicPluginSetup;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue