diff --git a/src/plugins/data/config.ts b/src/plugins/data/config.ts index b6adfdede2469..5b9f15dbce3e5 100644 --- a/src/plugins/data/config.ts +++ b/src/plugins/data/config.ts @@ -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({ + 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; export type SearchConfigSchema = TypeOf; +export type QueryConfigSchema = TypeOf; + export type SearchSessionsConfigSchema = TypeOf; diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index 32e321bd98a43..530cbe978c3d1 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -59,7 +59,9 @@ export class DataPublicPlugin constructor(initializerContext: PluginInitializerContext) { 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(); diff --git a/src/plugins/data/public/query/query_service.test.ts b/src/plugins/data/public/query/query_service.test.ts index 6ddde2d18f74f..b95e02dadf356 100644 --- a/src/plugins/data/public/query/query_service.test.ts +++ b/src/plugins/data/public/query/query_service.test.ts @@ -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); diff --git a/src/plugins/data/public/query/query_service.ts b/src/plugins/data/public/query/query_service.ts index 72943d6b0f59b..776f7e93273a2 100644 --- a/src/plugins/data/public/query/query_service.ts +++ b/src/plugins/data/public/query/query_service.ts @@ -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 { 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); diff --git a/src/plugins/data/public/query/state_sync/connect_to_query_state.test.ts b/src/plugins/data/public/query/state_sync/connect_to_query_state.test.ts index 515cc38783cbd..21a8c6a0661ed 100644 --- a/src/plugins/data/public/query/state_sync/connect_to_query_state.test.ts +++ b/src/plugins/data/public/query/state_sync/connect_to_query_state.test.ts @@ -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(); }); diff --git a/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts b/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts index 10cbe1bd25c5b..e6ed0119d80b8 100644 --- a/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts +++ b/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts @@ -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('_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('_g')?.refreshInterval).toEqual({ pause: true, - value: 100, + value: minRefreshIntervalDefault, }); stop(); }); diff --git a/src/plugins/data/public/query/timefilter/timefilter.test.ts b/src/plugins/data/public/query/timefilter/timefilter.test.ts index 1a4023644ed43..9aef9870541ce 100644 --- a/src/plugins/data/public/query/timefilter/timefilter.test.ts +++ b/src/plugins/data/public/query/timefilter/timefilter.test.ts @@ -17,15 +17,23 @@ import { RefreshInterval } from '../../../common'; import { createNowProviderMock } from '../../now_provider/mocks'; import { timefilterServiceMock } from './timefilter_service.mock'; +import { TimefilterConfig } from './types'; + const timefilterSetupMock = timefilterServiceMock.createSetupContract(); +const minRefreshIntervalDefault = 1000; -const timefilterConfig = { +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 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); + 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(timefilterConfig.refreshIntervalDefaults); + timefilter.setRefreshInterval(defaultTimefilterConfig.refreshIntervalDefaults); expect(timefilter.isRefreshIntervalTouched()).toBe(false); - timefilter.setRefreshInterval({ pause: false, value: 1000 }); + 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); diff --git a/src/plugins/data/public/query/timefilter/timefilter.ts b/src/plugins/data/public/query/timefilter/timefilter.ts index 11c44bd296640..19ed99173411a 100644 --- a/src/plugins/data/public/query/timefilter/timefilter.ts +++ b/src/plugins/data/public/query/timefilter/timefilter.ts @@ -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) => { 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) { diff --git a/src/plugins/data/public/query/timefilter/timefilter_service.mock.ts b/src/plugins/data/public/query/timefilter/timefilter_service.mock.ts index 03e7bed0545d1..27b25ddee9635 100644 --- a/src/plugins/data/public/query/timefilter/timefilter_service.mock.ts +++ b/src/plugins/data/public/query/timefilter/timefilter_service.mock.ts @@ -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(), diff --git a/src/plugins/data/public/query/timefilter/timefilter_service.ts b/src/plugins/data/public/query/timefilter/timefilter_service.ts index a8c9b7a759e9e..d9c026c956165 100644 --- a/src/plugins/data/public/query/timefilter/timefilter_service.ts +++ b/src/plugins/data/public/query/timefilter/timefilter_service.ts @@ -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'; -/** - * Filter Service - * @internal - */ - export interface TimeFilterServiceDependencies { uiSettings: IUiSettingsClient; storage: IStorageWrapper; + minRefreshInterval: number; } +/** + * Filter Service + * @internal + */ 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); diff --git a/src/plugins/data/public/query/timefilter/types.ts b/src/plugins/data/public/query/timefilter/types.ts index 1392022768fa7..f3cc8f5869a19 100644 --- a/src/plugins/data/public/query/timefilter/types.ts +++ b/src/plugins/data/public/query/timefilter/types.ts @@ -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 diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index e1d2080b6c59f..7a41c20094d6e 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -107,6 +107,7 @@ export const config: PluginConfigDescriptor = { deprecations: configDeprecationProvider, exposeToBrowser: { search: true, + query: true, }, schema: configSchema, }; diff --git a/src/plugins/discover/public/application/main/state_management/discover_data_state_container.ts b/src/plugins/discover/public/application/main/state_management/discover_data_state_container.ts index 6e34982dc91ae..d943dba36f9e2 100644 --- a/src/plugins/discover/public/application/main/state_management/discover_data_state_container.ts +++ b/src/plugins/discover/public/application/main/state_management/discover_data_state_container.ts @@ -185,7 +185,7 @@ export function getDataStateContainer({ totalHits$: new BehaviorSubject(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; diff --git a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts index 812052480e8b2..d3cca336413b8 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts @@ -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: diff --git a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts index 3c3bf07f28da5..fcc780f8b7b64 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts @@ -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: diff --git a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts index c0140bd89283d..d4a3fd466ba0d 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts @@ -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: diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx index 56daf31fb0703..f4c23d41e01ee 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx @@ -79,7 +79,7 @@ export function TopNavMenu( } 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 {...searchBarProps} />; diff --git a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx index 60d160aa943db..c685af3336551 100644 --- a/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx +++ b/src/plugins/unified_search/public/query_string_input/query_bar_top_row.tsx @@ -152,6 +152,7 @@ export interface QueryBarTopRowProps prepend?: React.ComponentProps['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} diff --git a/src/plugins/unified_search/public/search_bar/create_search_bar.tsx b/src/plugins/unified_search/public/search_bar/create_search_bar.tsx index 38e3b11ae15e7..4f2a609c93cbc 100644 --- a/src/plugins/unified_search/public/search_bar/create_search_bar.tsx +++ b/src/plugins/unified_search/public/search_bar/create_search_bar.tsx @@ -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} diff --git a/src/plugins/unified_search/public/search_bar/lib/use_timefilter.ts b/src/plugins/unified_search/public/search_bar/lib/use_timefilter.ts index ddfff690fce81..de13936bed8d7 100644 --- a/src/plugins/unified_search/public/search_bar/lib/use_timefilter.ts +++ b/src/plugins/unified_search/public/search_bar/lib/use_timefilter.ts @@ -59,5 +59,6 @@ export const useTimefilter = (props: UseTimefilterProps) => { return { refreshInterval, timeRange, + minRefreshInterval: props.timefilter.getMinRefreshInterval(), }; }; diff --git a/src/plugins/unified_search/public/search_bar/search_bar.tsx b/src/plugins/unified_search/public/search_bar/search_bar.tsx index 2d7ce30423fc3..992f6290b53e8 100644 --- a/src/plugins/unified_search/public/search_bar/search_bar.tsx +++ b/src/plugins/unified_search/public/search_bar/search_bar.tsx @@ -79,6 +79,7 @@ export interface SearchBarOwnProps { // Date picker isRefreshPaused?: boolean; refreshInterval?: number; + minRefreshInterval?: number; dateRangeFrom?: string; dateRangeTo?: string; // Query bar - should be in SearchBarInjectedDeps @@ -619,6 +620,7 @@ class SearchBarUI 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} diff --git a/test/common/config.js b/test/common/config.js index 2c707d3b6856a..163703a693356 100644 --- a/test/common/config.js +++ b/test/common/config.js @@ -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', diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index a7afb228f5c1f..f4e43bf5fc06d 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -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?)', diff --git a/x-pack/plugins/aiops/public/hooks/use_filters_query.test.tsx b/x-pack/plugins/aiops/public/hooks/use_filters_query.test.tsx index 2f45f627495b8..bfea21f9e8bbc 100644 --- a/x-pack/plugins/aiops/public/hooks/use_filters_query.test.tsx +++ b/x-pack/plugins/aiops/public/hooks/use_filters_query.test.tsx @@ -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( diff --git a/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts b/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts index f20679d68884c..14800d0a2b78f 100644 --- a/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts +++ b/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts @@ -61,7 +61,7 @@ import { alertingPluginMock } from '@kbn/alerting-plugin/public/mocks'; const mockUiSettings: Record = { [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, diff --git a/x-pack/plugins/security_solution/public/plugin_services.ts b/x-pack/plugins/security_solution/public/plugin_services.ts index 38ac65c08c8be..97b9a163b9f80 100644 --- a/x-pack/plugins/security_solution/public/plugin_services.ts +++ b/x-pack/plugins/security_solution/public/plugin_services.ts @@ -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, }); } diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts index 129f3e7728f9d..42c332d5f8449 100644 --- a/x-pack/plugins/security_solution/public/types.ts +++ b/x-pack/plugins/security_solution/public/types.ts @@ -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; } /**