From 1c820503c24b4f4fbdd26b2a1a20f36212d58871 Mon Sep 17 00:00:00 2001 From: Liza K Date: Tue, 24 Nov 2020 16:51:57 +0200 Subject: [PATCH 01/58] Monitor ids --- .../data/server/search/search_service.ts | 6 +- .../search/session/session_service.test.ts | 245 +++++++++++++++++- .../server/search/session/session_service.ts | 133 +++++++++- 3 files changed, 372 insertions(+), 12 deletions(-) diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index b44980164d0976..42f829cd64ec67 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -106,12 +106,14 @@ export class SearchService implements Plugin { private readonly searchSourceService = new SearchSourceService(); private defaultSearchStrategyName: string = ES_SEARCH_STRATEGY; private searchStrategies: StrategyMap = {}; - private sessionService: BackgroundSessionService = new BackgroundSessionService(); + private sessionService: BackgroundSessionService; constructor( private initializerContext: PluginInitializerContext, private readonly logger: Logger - ) {} + ) { + this.sessionService = new BackgroundSessionService(logger); + } public setup( core: CoreSetup<{}, DataPluginStart>, diff --git a/src/plugins/data/server/search/session/session_service.test.ts b/src/plugins/data/server/search/session/session_service.test.ts index 1ceebae967d4c6..3a204e2a77fdf2 100644 --- a/src/plugins/data/server/search/session/session_service.test.ts +++ b/src/plugins/data/server/search/session/session_service.test.ts @@ -17,16 +17,82 @@ * under the License. */ -import type { SavedObject, SavedObjectsClientContract } from 'kibana/server'; +import { coreMock } from 'src/core/server/mocks'; +import type { SavedObject, SavedObjectsClientContract } from 'src/core/server'; import { savedObjectsClientMock } from '../../../../../core/server/mocks'; import { BackgroundSessionStatus } from '../../../common'; import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; -import { BackgroundSessionService } from './session_service'; +import { + BackgroundSessionService, + INMEM_TRACKING_INTERVAL, + MAX_UPDATE_RETRIES, + SessionInfo, +} from './session_service'; import { createRequestHash } from './utils'; +import moment from 'moment'; describe('BackgroundSessionService', () => { let savedObjectsClient: jest.Mocked; let service: BackgroundSessionService; + const mockCoreStart = coreMock.createStart(); + + const MOCK_SESSION_ID = 'session-id-mock'; + const MOCK_ASYNC_ID = '123456'; + const MOCK_KEY_HASH = '608de49a4600dbb5b173492759792e4a'; + + const createMockInternalSavedObjectClient = ( + bulkGetSpy?: jest.SpyInstance, + bulkUpdateSpy?: jest.SpyInstance + ) => { + Object.defineProperty(service, 'internalSavedObjectsClient', { + get: () => { + const bulkGet = + bulkGetSpy || + (() => { + return { + saved_objects: [ + { + attributes: { + sessionId: MOCK_SESSION_ID, + idMapping: { + 'another-key': 'another-async-id', + }, + }, + id: MOCK_SESSION_ID, + version: '1', + }, + ], + }; + }); + + const bulkUpdate = + bulkUpdateSpy || + (() => { + return { + saved_objects: [], + }; + }); + return { + bulkGet, + bulkUpdate, + }; + }, + }); + }; + + const createMockIdMapping = ( + mapValues: any[], + insertTime?: moment.Moment, + retryCount?: number + ): Map => { + const fakeMap = new Map(); + fakeMap.set(MOCK_SESSION_ID, { + ids: new Map(mapValues), + insertTime: insertTime || moment(), + retryCount: retryCount || 0, + }); + return fakeMap; + }; const mockSavedObject: SavedObject = { id: 'd7170a35-7e2c-48d6-8dec-9a056721b489', @@ -40,7 +106,11 @@ describe('BackgroundSessionService', () => { beforeEach(() => { savedObjectsClient = savedObjectsClientMock.create(); - service = new BackgroundSessionService(); + const mockLogger: any = { + debug: jest.fn(), + error: jest.fn(), + }; + service = new BackgroundSessionService(mockLogger); }); it('save throws if `name` is not provided', () => { @@ -230,4 +300,173 @@ describe('BackgroundSessionService', () => { expect(id).toBe(searchId); }); }); + + describe('Monitor', () => { + beforeEach(() => { + jest.useFakeTimers(); + service.start(mockCoreStart); + }); + + afterEach(() => { + jest.useRealTimers(); + service.stop(); + }); + + it('should delete expired IDs', async () => { + const bulkGetSpy = jest.fn().mockResolvedValueOnce({ saved_objects: [] }); + createMockInternalSavedObjectClient(bulkGetSpy); + + const mockIdMapping = createMockIdMapping( + [[MOCK_KEY_HASH, MOCK_ASYNC_ID]], + moment().subtract(1, 'm') + ); + + const deleteSpy = jest.spyOn(mockIdMapping, 'delete'); + Object.defineProperty(service, 'sessionSearchMap', { + get: () => mockIdMapping, + }); + + // Get setInterval to fire + jest.advanceTimersByTime(INMEM_TRACKING_INTERVAL); + + expect(bulkGetSpy).not.toHaveBeenCalled(); + expect(deleteSpy).toHaveBeenCalledTimes(1); + }); + + it('should delete IDs that passed max retries', async () => { + const bulkGetSpy = jest.fn().mockResolvedValueOnce({ saved_objects: [] }); + createMockInternalSavedObjectClient(bulkGetSpy); + + const mockIdMapping = createMockIdMapping( + [[MOCK_KEY_HASH, MOCK_ASYNC_ID]], + moment().subtract(1, 'm'), + MAX_UPDATE_RETRIES + ); + + const deleteSpy = jest.spyOn(mockIdMapping, 'delete'); + Object.defineProperty(service, 'sessionSearchMap', { + get: () => mockIdMapping, + }); + + // Get setInterval to fire + jest.advanceTimersByTime(INMEM_TRACKING_INTERVAL); + + expect(bulkGetSpy).not.toHaveBeenCalled(); + expect(deleteSpy).toHaveBeenCalledTimes(1); + }); + + it('should bot fetch when no IDs are mapped', async () => { + const bulkGetSpy = jest.fn().mockResolvedValueOnce({ saved_objects: [] }); + createMockInternalSavedObjectClient(bulkGetSpy); + + jest.advanceTimersByTime(INMEM_TRACKING_INTERVAL); + expect(bulkGetSpy).not.toHaveBeenCalled(); + }); + + it('should try to fetch saved objects if some ids are mapped', async () => { + const mockIdMapping = createMockIdMapping([[MOCK_KEY_HASH, MOCK_ASYNC_ID]]); + Object.defineProperty(service, 'sessionSearchMap', { + get: () => mockIdMapping, + }); + + const bulkGetSpy = jest.fn().mockResolvedValueOnce({ saved_objects: [] }); + const bulkUpdateSpy = jest.fn().mockResolvedValueOnce({ saved_objects: [] }); + createMockInternalSavedObjectClient(bulkGetSpy, bulkUpdateSpy); + + jest.advanceTimersByTime(INMEM_TRACKING_INTERVAL); + expect(bulkGetSpy).toHaveBeenCalledTimes(1); + expect(bulkUpdateSpy).not.toHaveBeenCalled(); + }); + + it('should update saved objects if they are found, and delete ids on success', async () => { + const mockIdMapping = createMockIdMapping([[MOCK_KEY_HASH, MOCK_ASYNC_ID]], undefined, 1); + const mockMapDeleteSpy = jest.fn(); + mockIdMapping.get(MOCK_SESSION_ID)!.ids.delete = mockMapDeleteSpy; + Object.defineProperty(service, 'sessionSearchMap', { + get: () => mockIdMapping, + }); + + const bulkGetSpy = jest.fn().mockResolvedValueOnce({ + saved_objects: [ + { + id: MOCK_SESSION_ID, + attributes: { + idMapping: { + b: 'c', + }, + }, + }, + ], + }); + const bulkUpdateSpy = jest.fn().mockResolvedValueOnce({ + saved_objects: [ + { + id: MOCK_SESSION_ID, + attributes: { + idMapping: { + b: 'c', + [MOCK_KEY_HASH]: MOCK_ASYNC_ID, + }, + }, + }, + ], + }); + createMockInternalSavedObjectClient(bulkGetSpy, bulkUpdateSpy); + + jest.advanceTimersByTime(INMEM_TRACKING_INTERVAL); + + // Release timers to call check after test actions are done. + jest.useRealTimers(); + await new Promise((r) => setTimeout(r, 15)); + + expect(bulkGetSpy).toHaveBeenCalledTimes(1); + expect(bulkUpdateSpy).toHaveBeenCalledTimes(1); + expect(mockMapDeleteSpy).toHaveBeenCalledTimes(2); + expect(mockMapDeleteSpy).toBeCalledWith('b'); + expect(mockMapDeleteSpy).toBeCalledWith(MOCK_KEY_HASH); + expect(mockIdMapping.get(MOCK_SESSION_ID)?.retryCount).toBe(0); + }); + + it('should update saved objects if they are found, and increase retryCount on error', async () => { + const mockIdMapping = createMockIdMapping([[MOCK_KEY_HASH, MOCK_ASYNC_ID]]); + const mockMapDeleteSpy = jest.fn(); + mockIdMapping.get(MOCK_SESSION_ID)!.ids.delete = mockMapDeleteSpy; + Object.defineProperty(service, 'sessionSearchMap', { + get: () => mockIdMapping, + }); + + const bulkGetSpy = jest.fn().mockResolvedValueOnce({ + saved_objects: [ + { + id: MOCK_SESSION_ID, + attributes: { + idMapping: { + b: 'c', + }, + }, + }, + ], + }); + const bulkUpdateSpy = jest.fn().mockResolvedValueOnce({ + saved_objects: [ + { + id: MOCK_SESSION_ID, + error: 'not ok', + }, + ], + }); + createMockInternalSavedObjectClient(bulkGetSpy, bulkUpdateSpy); + + jest.advanceTimersByTime(INMEM_TRACKING_INTERVAL); + + // Release timers to call check after test actions are done. + jest.useRealTimers(); + await new Promise((r) => setTimeout(r, 15)); + + expect(bulkGetSpy).toHaveBeenCalledTimes(1); + expect(bulkUpdateSpy).toHaveBeenCalledTimes(1); + expect(mockMapDeleteSpy).not.toHaveBeenCalled(); + expect(mockIdMapping.get(MOCK_SESSION_ID)?.retryCount).toBe(1); + }); + }); }); diff --git a/src/plugins/data/server/search/session/session_service.ts b/src/plugins/data/server/search/session/session_service.ts index eca5f428b8555f..b3c1aec6d380e7 100644 --- a/src/plugins/data/server/search/session/session_service.ts +++ b/src/plugins/data/server/search/session/session_service.ts @@ -17,7 +17,16 @@ * under the License. */ -import { CoreStart, KibanaRequest, SavedObjectsClientContract } from 'kibana/server'; +import { + CoreStart, + KibanaRequest, + Logger, + SavedObject, + SavedObjectsClient, + SavedObjectsClientContract, + SavedObjectsServiceStart, +} from 'src/core/server'; +import moment, { Moment } from 'moment'; import { BackgroundSessionSavedObjectAttributes, IKibanaSearchRequest, @@ -29,6 +38,9 @@ import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; import { createRequestHash } from './utils'; const DEFAULT_EXPIRATION = 7 * 24 * 60 * 60 * 1000; +export const INMEM_TRACKING_INTERVAL = 2000; +export const INMEM_TRACKING_TIMEOUT_SEC = 60; +export const MAX_UPDATE_RETRIES = 3; export interface BackgroundSessionDependencies { savedObjectsClient: SavedObjectsClientContract; @@ -38,24 +50,127 @@ export type ISearchSessionClient = ReturnType< ReturnType >; +export interface SessionInfo { + insertTime: Moment; + retryCount: number; + ids: Map; +} + export class BackgroundSessionService { /** * Map of sessionId to { [requestHash]: searchId } * @private */ - private sessionSearchMap = new Map>(); + private sessionSearchMap = new Map(); + private internalSavedObjectsClient!: SavedObjectsClientContract; + private monitorInterval!: NodeJS.Timeout; + + constructor(private readonly logger: Logger) {} + + private setupMonitoring = (savedObjects: SavedObjectsServiceStart) => { + const internalRepo = savedObjects.createInternalRepository(); + this.internalSavedObjectsClient = new SavedObjectsClient(internalRepo); + this.monitorInterval = setInterval(this.monitorMappedIds.bind(this), INMEM_TRACKING_INTERVAL); + }; + + /** + * Gets all {@link SessionSavedObjectAttributes | Background Searches} that + * currently being tracked by the service. + * * + * @remarks + * Uses `internalSavedObjectsClient` as this is called asynchronously, not within the + * context of a user's session. + */ + private async getAllMappedSavedObjects() { + const activeMappingIds = Array.from(this.sessionSearchMap.keys()).map((sessionId) => { + return { + id: sessionId, + type: BACKGROUND_SESSION_TYPE, + }; + }); + const res = await this.internalSavedObjectsClient.bulkGet( + activeMappingIds + ); + return res.saved_objects; + } + + private clearSessions = () => { + this.logger.debug(`clearSessions`); + const curTime = moment(); + this.sessionSearchMap.forEach((sessionInfo, sessionId) => { + if ( + moment.duration(curTime.diff(sessionInfo.insertTime)).asSeconds() > + INMEM_TRACKING_TIMEOUT_SEC + ) { + this.logger.debug(`Deleting expired session ${sessionId}`); + this.sessionSearchMap.delete(sessionId); + } else if (sessionInfo.retryCount >= MAX_UPDATE_RETRIES) { + this.logger.warn(`Deleting failed session ${sessionId}`); + this.sessionSearchMap.delete(sessionId); + } + }); + }; + + private async monitorMappedIds() { + try { + this.logger.debug(`monitorMappedIds. Map contains ${this.sessionSearchMap.size} items`); + this.clearSessions(); + + if (!this.sessionSearchMap.size) return; + + const savedSessions = await this.getAllMappedSavedObjects(); + const updatedSessions = await this.updateAllSavedObjects(savedSessions); + + updatedSessions.forEach((updatedSavedObject) => { + const sessionInfo = this.sessionSearchMap.get(updatedSavedObject.id)!; + if (updatedSavedObject.error) { + // Retry next time + sessionInfo.retryCount++; + } else if (updatedSavedObject.attributes.idMapping) { + // Delete the ids that we just saved, avoiding a potential new ids being lost (?) + Object.keys(updatedSavedObject.attributes.idMapping).forEach((key) => { + sessionInfo.ids.delete(key); + }); + sessionInfo.retryCount = 0; + } + }); + } catch (e) { + this.logger.error(`Error fetching sessions. ${e}`); + } + } + + private async updateAllSavedObjects( + activeMappingObjects: Array> + ) { + if (!activeMappingObjects.length) return []; - constructor() {} + const updatedSessions = activeMappingObjects?.map((sessionSavedObject) => { + const sessionInfo = this.sessionSearchMap.get(sessionSavedObject.id); + const idMapping = sessionInfo ? Object.fromEntries(sessionInfo.ids.entries()) : {}; + sessionSavedObject.attributes.idMapping = { + ...sessionSavedObject.attributes.idMapping, + ...idMapping, + }; + return sessionSavedObject; + }); + + const updateResults = await this.internalSavedObjectsClient.bulkUpdate( + updatedSessions + ); + return updateResults.saved_objects; + } public setup = () => {}; public start = (core: CoreStart) => { + this.setupMonitoring(core.savedObjects); return { asScoped: this.asScopedProvider(core), }; }; public stop = () => { + clearInterval(this.monitorInterval); this.sessionSearchMap.clear(); }; @@ -75,8 +190,8 @@ export class BackgroundSessionService { if (!name) throw new Error('Name is required'); // Get the mapping of request hash/search ID for this session - const searchMap = this.sessionSearchMap.get(sessionId) ?? new Map(); - const idMapping = Object.fromEntries(searchMap.entries()); + const searchMap = this.sessionSearchMap.get(sessionId); + const idMapping = searchMap ? Object.fromEntries(searchMap.ids.entries()) : {}; const attributes = { name, created, expires, status, initialState, restoreState, idMapping }; const session = await savedObjectsClient.create( BACKGROUND_SESSION_TYPE, @@ -147,8 +262,12 @@ export class BackgroundSessionService { const attributes = { idMapping: { [requestHash]: searchId } }; await this.update(sessionId, attributes, deps); } else { - const map = this.sessionSearchMap.get(sessionId) ?? new Map(); - map.set(requestHash, searchId); + const map = this.sessionSearchMap.get(sessionId) ?? { + insertTime: moment(), + retryCount: 0, + ids: new Map(), + }; + map.ids.set(requestHash, searchId); this.sessionSearchMap.set(sessionId, map); } }; From 52032fc2108bc60bb776b92e1e9c07447d896a4e Mon Sep 17 00:00:00 2001 From: Liza K Date: Tue, 24 Nov 2020 18:24:19 +0200 Subject: [PATCH 02/58] import fix --- .../data/server/search/search_service.ts | 1 + .../server/search/session/session_service.ts | 39 ++++++++++--------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 42f829cd64ec67..7fc53a67f350da 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -188,6 +188,7 @@ export class SearchService implements Plugin { ): ISearchStart { const { elasticsearch, savedObjects, uiSettings } = core; const asScoped = this.asScopedProvider(core); + this.sessionService.start(core); return { aggs: this.aggsService.start({ fieldFormats, diff --git a/src/plugins/data/server/search/session/session_service.ts b/src/plugins/data/server/search/session/session_service.ts index b3c1aec6d380e7..288071652e0b71 100644 --- a/src/plugins/data/server/search/session/session_service.ts +++ b/src/plugins/data/server/search/session/session_service.ts @@ -17,6 +17,7 @@ * under the License. */ +import moment, { Moment } from 'moment'; import { CoreStart, KibanaRequest, @@ -25,8 +26,7 @@ import { SavedObjectsClient, SavedObjectsClientContract, SavedObjectsServiceStart, -} from 'src/core/server'; -import moment, { Moment } from 'moment'; +} from '../../../../../core/server'; import { BackgroundSessionSavedObjectAttributes, IKibanaSearchRequest, @@ -38,7 +38,7 @@ import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; import { createRequestHash } from './utils'; const DEFAULT_EXPIRATION = 7 * 24 * 60 * 60 * 1000; -export const INMEM_TRACKING_INTERVAL = 2000; +export const INMEM_TRACKING_INTERVAL = 10 * 1000; export const INMEM_TRACKING_TIMEOUT_SEC = 60; export const MAX_UPDATE_RETRIES = 3; @@ -88,14 +88,13 @@ export class BackgroundSessionService { type: BACKGROUND_SESSION_TYPE, }; }); - const res = await this.internalSavedObjectsClient.bulkGet( - activeMappingIds - ); + const res = await this.internalSavedObjectsClient.bulkGet< + BackgroundSessionSavedObjectAttributes + >(activeMappingIds); return res.saved_objects; } private clearSessions = () => { - this.logger.debug(`clearSessions`); const curTime = moment(); this.sessionSearchMap.forEach((sessionInfo, sessionId) => { if ( @@ -144,19 +143,21 @@ export class BackgroundSessionService { ) { if (!activeMappingObjects.length) return []; - const updatedSessions = activeMappingObjects?.map((sessionSavedObject) => { - const sessionInfo = this.sessionSearchMap.get(sessionSavedObject.id); - const idMapping = sessionInfo ? Object.fromEntries(sessionInfo.ids.entries()) : {}; - sessionSavedObject.attributes.idMapping = { - ...sessionSavedObject.attributes.idMapping, - ...idMapping, - }; - return sessionSavedObject; - }); + const updatedSessions = activeMappingObjects + ?.filter((so) => !so.error) + .map((sessionSavedObject) => { + const sessionInfo = this.sessionSearchMap.get(sessionSavedObject.id); + const idMapping = sessionInfo ? Object.fromEntries(sessionInfo.ids.entries()) : {}; + sessionSavedObject.attributes.idMapping = { + ...sessionSavedObject.attributes.idMapping, + ...idMapping, + }; + return sessionSavedObject; + }); - const updateResults = await this.internalSavedObjectsClient.bulkUpdate( - updatedSessions - ); + const updateResults = await this.internalSavedObjectsClient.bulkUpdate< + BackgroundSessionSavedObjectAttributes + >(updatedSessions); return updateResults.saved_objects; } From 8a1299197d4cad8c9f185506b155deeb224bba00 Mon Sep 17 00:00:00 2001 From: Liza K Date: Wed, 25 Nov 2020 16:47:28 +0200 Subject: [PATCH 03/58] solve circular dep --- .../create_or_upgrade_saved_config.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts index 10a30db038174d..3f61db42926040 100644 --- a/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts +++ b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts @@ -19,10 +19,10 @@ import Chance from 'chance'; +import { getUpgradeableConfigMock } from './get_upgradeable_config.test.mock'; import { SavedObjectsErrorHelpers } from '../../saved_objects'; import { savedObjectsClientMock } from '../../saved_objects/service/saved_objects_client.mock'; import { loggingSystemMock } from '../../logging/logging_system.mock'; -import { getUpgradeableConfigMock } from './get_upgradeable_config.test.mock'; import { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config'; From d692c1e4e2748ff9d9bb6ed29b1f72f1e2ef0ea1 Mon Sep 17 00:00:00 2001 From: Liza K Date: Mon, 30 Nov 2020 19:49:09 +0200 Subject: [PATCH 04/58] eslint --- .../data/server/search/session/session_service.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/plugins/data/server/search/session/session_service.ts b/src/plugins/data/server/search/session/session_service.ts index 288071652e0b71..c984e6a7679bed 100644 --- a/src/plugins/data/server/search/session/session_service.ts +++ b/src/plugins/data/server/search/session/session_service.ts @@ -88,9 +88,9 @@ export class BackgroundSessionService { type: BACKGROUND_SESSION_TYPE, }; }); - const res = await this.internalSavedObjectsClient.bulkGet< - BackgroundSessionSavedObjectAttributes - >(activeMappingIds); + const res = await this.internalSavedObjectsClient.bulkGet( + activeMappingIds + ); return res.saved_objects; } @@ -155,9 +155,9 @@ export class BackgroundSessionService { return sessionSavedObject; }); - const updateResults = await this.internalSavedObjectsClient.bulkUpdate< - BackgroundSessionSavedObjectAttributes - >(updatedSessions); + const updateResults = await this.internalSavedObjectsClient.bulkUpdate( + updatedSessions + ); return updateResults.saved_objects; } From 1092c7ab8f4cb2951fd26656f9b71ecc8074a1da Mon Sep 17 00:00:00 2001 From: Liza K Date: Mon, 30 Nov 2020 19:58:36 +0200 Subject: [PATCH 05/58] mock circular dep --- .../spaces/server/routes/api/external/copy_to_space.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts b/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts index a6e1c11d011a0c..aecdd785fa9d18 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts @@ -25,6 +25,10 @@ import { SpacesService } from '../../../spaces_service'; import { initCopyToSpacesApi } from './copy_to_space'; import { spacesConfig } from '../../../lib/__fixtures__'; import { ObjectType } from '@kbn/config-schema'; + +// Mock out circular dependency +jest.mock('../../../../../../../src/core/server/saved_objects/es_query', () => {}); + jest.mock('../../../../../../../src/core/server', () => { return { ...(jest.requireActual('../../../../../../../src/core/server') as Record), From 500f15d1f3fdc6e4bfb69760bfbfa13fa254bc2b Mon Sep 17 00:00:00 2001 From: Liza K Date: Mon, 30 Nov 2020 19:59:44 +0200 Subject: [PATCH 06/58] max retries test --- src/plugins/data/server/search/session/session_service.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/data/server/search/session/session_service.test.ts b/src/plugins/data/server/search/session/session_service.test.ts index 3a204e2a77fdf2..117e2188a93322 100644 --- a/src/plugins/data/server/search/session/session_service.test.ts +++ b/src/plugins/data/server/search/session/session_service.test.ts @@ -340,7 +340,7 @@ describe('BackgroundSessionService', () => { const mockIdMapping = createMockIdMapping( [[MOCK_KEY_HASH, MOCK_ASYNC_ID]], moment().subtract(1, 'm'), - MAX_UPDATE_RETRIES + MAX_UPDATE_RETRIES + 1 ); const deleteSpy = jest.spyOn(mockIdMapping, 'delete'); From c6fea683eef1ca3431547ef2491457fc705a4186 Mon Sep 17 00:00:00 2001 From: Liza K Date: Mon, 30 Nov 2020 20:40:12 +0200 Subject: [PATCH 07/58] mock circular dep --- src/plugins/data/server/search/session/session_service.ts | 1 + .../spaces/server/lib/copy_to_spaces/copy_to_spaces.test.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/plugins/data/server/search/session/session_service.ts b/src/plugins/data/server/search/session/session_service.ts index c984e6a7679bed..caa37f80a89bf8 100644 --- a/src/plugins/data/server/search/session/session_service.ts +++ b/src/plugins/data/server/search/session/session_service.ts @@ -68,6 +68,7 @@ export class BackgroundSessionService { constructor(private readonly logger: Logger) {} private setupMonitoring = (savedObjects: SavedObjectsServiceStart) => { + // TODO: setup monitoring only if BGS is enabled const internalRepo = savedObjects.createInternalRepository(); this.internalSavedObjectsClient = new SavedObjectsClient(internalRepo); this.monitorInterval = setInterval(this.monitorMappedIds.bind(this), INMEM_TRACKING_INTERVAL); diff --git a/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.test.ts b/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.test.ts index d1a8e93bff9292..945a2bdbf6daf0 100644 --- a/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.test.ts +++ b/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.test.ts @@ -18,6 +18,9 @@ import { } from 'src/core/server/mocks'; import { copySavedObjectsToSpacesFactory } from './copy_to_spaces'; +// Mock out circular dependency +jest.mock('../../../../../../src/core/server/saved_objects/es_query', () => {}); + jest.mock('../../../../../../src/core/server', () => { return { ...(jest.requireActual('../../../../../../src/core/server') as Record), From e5325e9cfc418890c0f14ec5c3b9906e368fdcb5 Mon Sep 17 00:00:00 2001 From: Liza K Date: Mon, 30 Nov 2020 20:45:43 +0200 Subject: [PATCH 08/58] test --- .../data/server/search/session/session_service.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/plugins/data/server/search/session/session_service.test.ts b/src/plugins/data/server/search/session/session_service.test.ts index 117e2188a93322..be8ffda0779d64 100644 --- a/src/plugins/data/server/search/session/session_service.test.ts +++ b/src/plugins/data/server/search/session/session_service.test.ts @@ -108,6 +108,7 @@ describe('BackgroundSessionService', () => { savedObjectsClient = savedObjectsClientMock.create(); const mockLogger: any = { debug: jest.fn(), + warn: jest.fn(), error: jest.fn(), }; service = new BackgroundSessionService(mockLogger); @@ -339,8 +340,8 @@ describe('BackgroundSessionService', () => { const mockIdMapping = createMockIdMapping( [[MOCK_KEY_HASH, MOCK_ASYNC_ID]], - moment().subtract(1, 'm'), - MAX_UPDATE_RETRIES + 1 + moment(), + MAX_UPDATE_RETRIES ); const deleteSpy = jest.spyOn(mockIdMapping, 'delete'); From c540fdea0581983e26e8094be9602cab0bad25e6 Mon Sep 17 00:00:00 2001 From: Liza K Date: Tue, 1 Dec 2020 10:41:29 +0200 Subject: [PATCH 09/58] jest <(-:C --- .../server/lib/copy_to_spaces/resolve_copy_conflicts.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.test.ts b/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.test.ts index f1b475ee9d1b3d..0e54412ee61ae4 100644 --- a/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.test.ts +++ b/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.test.ts @@ -18,6 +18,9 @@ import { } from 'src/core/server/mocks'; import { resolveCopySavedObjectsToSpacesConflictsFactory } from './resolve_copy_conflicts'; +// Mock out circular dependency +jest.mock('../../../../../../src/core/server/saved_objects/es_query', () => {}); + jest.mock('../../../../../../src/core/server', () => { return { ...(jest.requireActual('../../../../../../src/core/server') as Record), From 5f7db2da24c838aae322ce5e90342db38b5f220e Mon Sep 17 00:00:00 2001 From: Liza K Date: Tue, 1 Dec 2020 10:59:07 +0200 Subject: [PATCH 10/58] jestttttt --- src/plugins/data/server/search/session/session_service.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/data/server/search/session/session_service.test.ts b/src/plugins/data/server/search/session/session_service.test.ts index be8ffda0779d64..2c905ca205c30a 100644 --- a/src/plugins/data/server/search/session/session_service.test.ts +++ b/src/plugins/data/server/search/session/session_service.test.ts @@ -319,7 +319,7 @@ describe('BackgroundSessionService', () => { const mockIdMapping = createMockIdMapping( [[MOCK_KEY_HASH, MOCK_ASYNC_ID]], - moment().subtract(1, 'm') + moment().subtract(2, 'm') ); const deleteSpy = jest.spyOn(mockIdMapping, 'delete'); From 4886819db0e4b7084ce10b91e4f12de762ca8018 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Tue, 1 Dec 2020 16:07:19 -0700 Subject: [PATCH 11/58] [data.search] Move search method inside session service and add tests --- .../data/server/search/search_service.ts | 35 +++----- .../search/session/session_service.test.ts | 89 ++++++++++++++++--- .../server/search/session/session_service.ts | 37 ++++++-- 3 files changed, 122 insertions(+), 39 deletions(-) diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index a9539a8fd3c154..03cbcd534b15ff 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -17,7 +17,7 @@ * under the License. */ -import { BehaviorSubject, from, Observable } from 'rxjs'; +import { BehaviorSubject, Observable } from 'rxjs'; import { pick } from 'lodash'; import { CoreSetup, @@ -29,7 +29,7 @@ import { SharedGlobalConfig, StartServicesAccessor, } from 'src/core/server'; -import { catchError, first, map, switchMap } from 'rxjs/operators'; +import { catchError, first, map } from 'rxjs/operators'; import { BfetchServerSetup } from 'src/plugins/bfetch/server'; import { ExpressionsServerSetup } from 'src/plugins/expressions/server'; import { @@ -50,7 +50,11 @@ import { DataPluginStart } from '../plugin'; import { UsageCollectionSetup } from '../../../usage_collection/server'; import { registerUsageCollector } from './collectors/register'; import { usageProvider } from './collectors/usage'; -import { BACKGROUND_SESSION_TYPE, searchTelemetry } from '../saved_objects'; +import { + BACKGROUND_SESSION_TYPE, + backgroundSessionMapping, + searchTelemetry, +} from '../saved_objects'; import { IEsSearchRequest, IEsSearchResponse, @@ -73,8 +77,6 @@ import { aggShardDelay } from '../../common/search/aggs/buckets/shard_delay_fn'; import { ConfigSchema } from '../../config'; import { BackgroundSessionService, ISearchSessionClient } from './session'; import { registerSessionRoutes } from './routes/session'; -import { backgroundSessionMapping } from '../saved_objects'; -import { tapFirst } from '../../common/utils'; declare module 'src/core/server' { interface RequestHandlerContext { @@ -295,7 +297,7 @@ export class SearchService implements Plugin { SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest, SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse >( - searchRequest: SearchStrategyRequest, + request: SearchStrategyRequest, options: ISearchOptions, deps: SearchStrategyDependencies ) => { @@ -303,24 +305,9 @@ export class SearchService implements Plugin { options.strategy ); - // If this is a restored background search session, look up the ID using the provided sessionId - const getSearchRequest = async () => - !options.isRestore || searchRequest.id - ? searchRequest - : { - ...searchRequest, - id: await this.sessionService.getId(searchRequest, options, deps), - }; - - return from(getSearchRequest()).pipe( - switchMap((request) => strategy.search(request, options, deps)), - tapFirst((response) => { - if (searchRequest.id || !options.sessionId || !response.id || options.isRestore) return; - this.sessionService.trackId(searchRequest, response.id, options, { - savedObjectsClient: deps.savedObjectsClient, - }); - }) - ); + return options.sessionId + ? this.sessionService.search(strategy, request, options, deps) + : strategy.search(request, options, deps); }; private cancel = (id: string, options: ISearchOptions, deps: SearchStrategyDependencies) => { diff --git a/src/plugins/data/server/search/session/session_service.test.ts b/src/plugins/data/server/search/session/session_service.test.ts index 5ff6d4b932487c..167aa8c4099e04 100644 --- a/src/plugins/data/server/search/session/session_service.test.ts +++ b/src/plugins/data/server/search/session/session_service.test.ts @@ -17,7 +17,9 @@ * under the License. */ +import { of } from 'rxjs'; import type { SavedObject, SavedObjectsClientContract } from 'kibana/server'; +import type { SearchStrategyDependencies } from '../types'; import { savedObjectsClientMock } from '../../../../../core/server/mocks'; import { BackgroundSessionStatus } from '../../../common'; import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; @@ -28,6 +30,7 @@ describe('BackgroundSessionService', () => { let savedObjectsClient: jest.Mocked; let service: BackgroundSessionService; + const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; const mockSavedObject: SavedObject = { id: 'd7170a35-7e2c-48d6-8dec-9a056721b489', type: BACKGROUND_SESSION_TYPE, @@ -45,9 +48,13 @@ describe('BackgroundSessionService', () => { service = new BackgroundSessionService(); }); - it('save throws if `name` is not provided', () => { - const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + it('search throws if `name` is not provided', () => { + expect(() => service.save(sessionId, {}, { savedObjectsClient })).rejects.toMatchInlineSnapshot( + `[Error: Name is required]` + ); + }); + it('save throws if `name` is not provided', () => { expect(() => service.save(sessionId, {}, { savedObjectsClient })).rejects.toMatchInlineSnapshot( `[Error: Name is required]` ); @@ -56,7 +63,6 @@ describe('BackgroundSessionService', () => { it('get calls saved objects client', async () => { savedObjectsClient.get.mockResolvedValue(mockSavedObject); - const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; const response = await service.get(sessionId, { savedObjectsClient }); expect(response).toBe(mockSavedObject); @@ -93,7 +99,6 @@ describe('BackgroundSessionService', () => { }; savedObjectsClient.update.mockResolvedValue(mockUpdateSavedObject); - const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; const attributes = { name: 'new_name' }; const response = await service.update(sessionId, attributes, { savedObjectsClient }); @@ -108,19 +113,87 @@ describe('BackgroundSessionService', () => { it('delete calls saved objects client', async () => { savedObjectsClient.delete.mockResolvedValue({}); - const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; const response = await service.delete(sessionId, { savedObjectsClient }); expect(response).toEqual({}); expect(savedObjectsClient.delete).toHaveBeenCalledWith(BACKGROUND_SESSION_TYPE, sessionId); }); + describe('search', () => { + const mockSearch = jest.fn().mockReturnValue(of({})); + const mockStrategy = { search: mockSearch }; + const mockDeps = {} as SearchStrategyDependencies; + + beforeEach(() => { + mockSearch.mockClear(); + }); + + it('searches using the original request if not restoring', async () => { + const searchRequest = { params: {} }; + const options = { sessionId, isStored: false, isRestore: false }; + + await service.search(mockStrategy, searchRequest, options, mockDeps).toPromise(); + + expect(mockSearch).toBeCalledWith(searchRequest, options, mockDeps); + }); + + it('searches using the original request if `id` is provided', async () => { + const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; + const searchRequest = { id: searchId, params: {} }; + const options = { sessionId, isStored: true, isRestore: true }; + + await service.search(mockStrategy, searchRequest, options, mockDeps).toPromise(); + + expect(mockSearch).toBeCalledWith(searchRequest, options, mockDeps); + }); + + it('searches by looking up an `id` if restoring and `id` is not provided', async () => { + const searchRequest = { params: {} }; + const options = { sessionId, isStored: true, isRestore: true }; + const spyGetId = jest.spyOn(service, 'getId').mockResolvedValueOnce('my_id'); + + await service.search(mockStrategy, searchRequest, options, mockDeps).toPromise(); + + expect(mockSearch).toBeCalledWith({ ...searchRequest, id: 'my_id' }, options, mockDeps); + + spyGetId.mockRestore(); + }); + + it('calls `trackId` once if the response contains an `id` and not restoring', async () => { + const searchRequest = { params: {} }; + const options = { sessionId, isStored: false, isRestore: false }; + const spyTrackId = jest.spyOn(service, 'trackId').mockResolvedValue(); + mockSearch.mockReturnValueOnce(of({ id: 'my_id' }, { id: 'my_id' })); + + await service.search(mockStrategy, searchRequest, options, mockDeps).toPromise(); + + expect(spyTrackId).toBeCalledTimes(1); + expect(spyTrackId).toBeCalledWith(searchRequest, 'my_id', options, mockDeps); + + spyTrackId.mockRestore(); + }); + + it('does not call `trackId` if restoring', async () => { + const searchRequest = { params: {} }; + const options = { sessionId, isStored: true, isRestore: true }; + const spyGetId = jest.spyOn(service, 'getId').mockResolvedValueOnce('my_id'); + const spyTrackId = jest.spyOn(service, 'trackId').mockResolvedValue(); + mockSearch.mockReturnValueOnce(of({ id: 'my_id' })); + + await service.search(mockStrategy, searchRequest, options, mockDeps).toPromise(); + + expect(spyTrackId).not.toBeCalled(); + + spyGetId.mockRestore(); + spyTrackId.mockRestore(); + }); + }); + describe('trackId', () => { it('stores hash in memory when `isStored` is `false` for when `save` is called', async () => { const searchRequest = { params: {} }; const requestHash = createRequestHash(searchRequest.params); const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; - const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; const isStored = false; const name = 'my saved background search session'; const appId = 'my_app_id'; @@ -164,7 +237,6 @@ describe('BackgroundSessionService', () => { const searchRequest = { params: {} }; const requestHash = createRequestHash(searchRequest.params); const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; - const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; const isStored = true; await service.trackId( @@ -191,7 +263,6 @@ describe('BackgroundSessionService', () => { it('throws if there is not a saved object', () => { const searchRequest = { params: {} }; - const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; expect(() => service.getId(searchRequest, { sessionId, isStored: false }, { savedObjectsClient }) @@ -202,7 +273,6 @@ describe('BackgroundSessionService', () => { it('throws if not restoring a saved session', () => { const searchRequest = { params: {} }; - const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; expect(() => service.getId( @@ -219,7 +289,6 @@ describe('BackgroundSessionService', () => { const searchRequest = { params: {} }; const requestHash = createRequestHash(searchRequest.params); const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; - const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; const mockSession = { id: 'd7170a35-7e2c-48d6-8dec-9a056721b489', type: BACKGROUND_SESSION_TYPE, diff --git a/src/plugins/data/server/search/session/session_service.ts b/src/plugins/data/server/search/session/session_service.ts index b9a738413ede4f..d997af728b60cb 100644 --- a/src/plugins/data/server/search/session/session_service.ts +++ b/src/plugins/data/server/search/session/session_service.ts @@ -18,14 +18,19 @@ */ import { CoreStart, KibanaRequest, SavedObjectsClientContract } from 'kibana/server'; +import { from } from 'rxjs'; +import { switchMap } from 'rxjs/operators'; import { BackgroundSessionSavedObjectAttributes, + BackgroundSessionStatus, IKibanaSearchRequest, + IKibanaSearchResponse, ISearchOptions, SearchSessionFindOptions, - BackgroundSessionStatus, + tapFirst, } from '../../../common'; import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; +import { ISearchStrategy, SearchStrategyDependencies } from '../types'; import { createRequestHash } from './utils'; const DEFAULT_EXPIRATION = 7 * 24 * 60 * 60 * 1000; @@ -59,6 +64,32 @@ export class BackgroundSessionService { this.sessionSearchMap.clear(); }; + public search = ( + strategy: ISearchStrategy, + searchRequest: Request, + options: ISearchOptions, + deps: SearchStrategyDependencies + ) => { + // If this is a restored background search session, look up the ID using the provided sessionId + const getSearchRequest = async () => + !options.isRestore || searchRequest.id + ? searchRequest + : { + ...searchRequest, + id: await this.getId(searchRequest, options, deps), + }; + + return from(getSearchRequest()).pipe( + switchMap((request) => strategy.search(request, options, deps)), + tapFirst((response) => { + if (searchRequest.id || !options.sessionId || !response.id || options.isRestore) return; + this.trackId(searchRequest, response.id, options, { + savedObjectsClient: deps.savedObjectsClient, + }); + }) + ); + }; + // TODO: Generate the `userId` from the realm type/realm name/username public save = async ( sessionId: string, @@ -208,10 +239,6 @@ export class BackgroundSessionService { update: (sessionId: string, attributes: Partial) => this.update(sessionId, attributes, deps), delete: (sessionId: string) => this.delete(sessionId, deps), - trackId: (searchRequest: IKibanaSearchRequest, searchId: string, options: ISearchOptions) => - this.trackId(searchRequest, searchId, options, deps), - getId: (searchRequest: IKibanaSearchRequest, options: ISearchOptions) => - this.getId(searchRequest, options, deps), }; }; }; From a91ea100387420f77aac01f239dd7cee102350d7 Mon Sep 17 00:00:00 2001 From: Liza K Date: Wed, 2 Dec 2020 16:56:47 +0200 Subject: [PATCH 12/58] merge --- src/plugins/data/server/search/session/session_service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/data/server/search/session/session_service.ts b/src/plugins/data/server/search/session/session_service.ts index b57ceb1b924a3b..a70a6dc0780aa6 100644 --- a/src/plugins/data/server/search/session/session_service.ts +++ b/src/plugins/data/server/search/session/session_service.ts @@ -196,8 +196,8 @@ export class BackgroundSessionService { if (!urlGeneratorId) throw new Error('UrlGeneratorId is required'); // Get the mapping of request hash/search ID for this session - const searchMap = this.sessionSearchMap.get(sessionId) ?? new Map(); - const idMapping = Object.fromEntries(searchMap.entries()); + const searchMap = this.sessionSearchMap.get(sessionId); + const idMapping = searchMap ? Object.fromEntries(searchMap.ids.entries()) : {}; const attributes = { name, created, From 0de773085dc0ff83508ac33244c967a41052c250 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Wed, 2 Dec 2020 14:31:22 -0700 Subject: [PATCH 13/58] Move background session service to data_enhanced plugin --- .../kibana-plugin-plugins-data-public.md | 2 +- ...blic.searchsessioninfoprovider.getname.md} | 2 +- ...essioninfoprovider.geturlgeneratordata.md} | 2 +- ...-data-public.searchsessioninfoprovider.md} | 6 +- ...server.isessionservice.asscopedprovider.md | 11 + ...gin-plugins-data-server.isessionservice.md | 18 + .../kibana-plugin-plugins-data-server.md | 2 + ...ata-server.sessionservice._constructor_.md | 13 + ...-server.sessionservice.asscopedprovider.md | 26 ++ ...ugin-plugins-data-server.sessionservice.md | 27 ++ ...ugins-data-server.sessionservice.search.md | 23 ++ src/plugins/data/common/search/index.ts | 1 - .../data/common/search/session/index.ts | 21 -- .../data/common/search/session/status.ts | 26 -- src/plugins/data/public/public.api.md | 1 + .../public/search/session/sessions_client.ts | 16 +- src/plugins/data/server/index.ts | 2 + .../data/server/saved_objects/index.ts | 1 - src/plugins/data/server/search/index.ts | 1 + src/plugins/data/server/search/mocks.ts | 8 +- .../data/server/search/search_service.ts | 37 +-- .../data/server/search/session/index.ts | 3 +- .../search/session/session_service.test.ts | 298 +---------------- .../server/search/session/session_service.ts | 238 +------------ .../search/session/types.ts | 35 +- .../data/server/search/session/utils.test.ts | 37 --- .../data/server/search/session/utils.ts | 30 -- src/plugins/data/server/search/types.ts | 2 + src/plugins/data/server/server.api.md | 68 ++-- src/plugins/embeddable/public/public.api.md | 1 + x-pack/plugins/data_enhanced/common/index.ts | 3 + .../data_enhanced/common/search/index.ts | 1 + .../common/search/session/index.ts | 8 + .../common/search/session/status.ts | 13 + .../common/search/session/types.ts | 31 ++ x-pack/plugins/data_enhanced/server/plugin.ts | 23 +- .../data_enhanced/server/routes/index.ts | 7 + .../server}/routes/session.test.ts | 25 +- .../data_enhanced/server}/routes/session.ts | 19 +- .../saved_objects/background_session.ts | 19 +- .../server/saved_objects/index.ts | 7 + .../data_enhanced/server/search/index.ts | 1 + .../server/search/session/index.ts | 7 + .../search/session/session_service.test.ts | 312 ++++++++++++++++++ .../server/search/session/session_service.ts | 225 +++++++++++++ .../server/search/session/utils.test.ts | 24 ++ .../server/search/session/utils.ts | 17 + 47 files changed, 925 insertions(+), 775 deletions(-) rename docs/development/plugins/data/public/{kibana-plugin-plugins-data-public.searchsessionrestorationinfoprovider.getname.md => kibana-plugin-plugins-data-public.searchsessioninfoprovider.getname.md} (83%) rename docs/development/plugins/data/public/{kibana-plugin-plugins-data-public.searchsessionrestorationinfoprovider.geturlgeneratordata.md => kibana-plugin-plugins-data-public.searchsessioninfoprovider.geturlgeneratordata.md} (82%) rename docs/development/plugins/data/public/{kibana-plugin-plugins-data-public.searchsessionrestorationinfoprovider.md => kibana-plugin-plugins-data-public.searchsessioninfoprovider.md} (84%) create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isessionservice.asscopedprovider.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isessionservice.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice._constructor_.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.asscopedprovider.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.search.md delete mode 100644 src/plugins/data/common/search/session/index.ts delete mode 100644 src/plugins/data/common/search/session/status.ts rename src/plugins/data/{common => server}/search/session/types.ts (55%) delete mode 100644 src/plugins/data/server/search/session/utils.test.ts delete mode 100644 src/plugins/data/server/search/session/utils.ts create mode 100644 x-pack/plugins/data_enhanced/common/search/session/index.ts create mode 100644 x-pack/plugins/data_enhanced/common/search/session/status.ts create mode 100644 x-pack/plugins/data_enhanced/common/search/session/types.ts create mode 100644 x-pack/plugins/data_enhanced/server/routes/index.ts rename {src/plugins/data/server/search => x-pack/plugins/data_enhanced/server}/routes/session.test.ts (78%) rename {src/plugins/data/server/search => x-pack/plugins/data_enhanced/server}/routes/session.ts (85%) rename {src/plugins/data => x-pack/plugins/data_enhanced}/server/saved_objects/background_session.ts (51%) create mode 100644 x-pack/plugins/data_enhanced/server/saved_objects/index.ts create mode 100644 x-pack/plugins/data_enhanced/server/search/session/index.ts create mode 100644 x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts create mode 100644 x-pack/plugins/data_enhanced/server/search/session/session_service.ts create mode 100644 x-pack/plugins/data_enhanced/server/search/session/utils.test.ts create mode 100644 x-pack/plugins/data_enhanced/server/search/session/utils.ts diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md index 9121b0aade4706..08ed14b92d24c7 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md @@ -89,7 +89,7 @@ | [SavedQueryService](./kibana-plugin-plugins-data-public.savedqueryservice.md) | | | [SearchError](./kibana-plugin-plugins-data-public.searcherror.md) | | | [SearchInterceptorDeps](./kibana-plugin-plugins-data-public.searchinterceptordeps.md) | | -| [SearchSessionInfoProvider](./kibana-plugin-plugins-data-public.searchSessionInfoprovider.md) | Provide info about current search session to be stored in backgroundSearch saved object | +| [SearchSessionInfoProvider](./kibana-plugin-plugins-data-public.searchsessioninfoprovider.md) | Provide info about current search session to be stored in backgroundSearch saved object | | [SearchSourceFields](./kibana-plugin-plugins-data-public.searchsourcefields.md) | search source fields | | [TabbedAggColumn](./kibana-plugin-plugins-data-public.tabbedaggcolumn.md) | \* | | [TabbedTable](./kibana-plugin-plugins-data-public.tabbedtable.md) | \* | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessionrestorationinfoprovider.getname.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessioninfoprovider.getname.md similarity index 83% rename from docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessionrestorationinfoprovider.getname.md rename to docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessioninfoprovider.getname.md index 0f0b616066dd66..2a5e1d2a3135fe 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessionrestorationinfoprovider.getname.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessioninfoprovider.getname.md @@ -1,6 +1,6 @@ -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSessionInfoProvider](./kibana-plugin-plugins-data-public.searchSessionInfoprovider.md) > [getName](./kibana-plugin-plugins-data-public.searchSessionInfoprovider.getname.md) +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSessionInfoProvider](./kibana-plugin-plugins-data-public.searchsessioninfoprovider.md) > [getName](./kibana-plugin-plugins-data-public.searchsessioninfoprovider.getname.md) ## SearchSessionInfoProvider.getName property diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessionrestorationinfoprovider.geturlgeneratordata.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessioninfoprovider.geturlgeneratordata.md similarity index 82% rename from docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessionrestorationinfoprovider.geturlgeneratordata.md rename to docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessioninfoprovider.geturlgeneratordata.md index 207adaf2bd50b6..01558ed3dddad1 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessionrestorationinfoprovider.geturlgeneratordata.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessioninfoprovider.geturlgeneratordata.md @@ -1,6 +1,6 @@ -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSessionInfoProvider](./kibana-plugin-plugins-data-public.searchSessionInfoprovider.md) > [getUrlGeneratorData](./kibana-plugin-plugins-data-public.searchSessionInfoprovider.geturlgeneratordata.md) +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSessionInfoProvider](./kibana-plugin-plugins-data-public.searchsessioninfoprovider.md) > [getUrlGeneratorData](./kibana-plugin-plugins-data-public.searchsessioninfoprovider.geturlgeneratordata.md) ## SearchSessionInfoProvider.getUrlGeneratorData property diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessionrestorationinfoprovider.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessioninfoprovider.md similarity index 84% rename from docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessionrestorationinfoprovider.md rename to docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessioninfoprovider.md index a3d294f5e33038..bcc4a5508eb59c 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessionrestorationinfoprovider.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessioninfoprovider.md @@ -1,6 +1,6 @@ -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSessionInfoProvider](./kibana-plugin-plugins-data-public.searchSessionInfoprovider.md) +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSessionInfoProvider](./kibana-plugin-plugins-data-public.searchsessioninfoprovider.md) ## SearchSessionInfoProvider interface @@ -16,6 +16,6 @@ export interface SearchSessionInfoProvider() => Promise<string> | User-facing name of the session. e.g. will be displayed in background sessions management list | -| [getUrlGeneratorData](./kibana-plugin-plugins-data-public.searchSessionInfoprovider.geturlgeneratordata.md) | () => Promise<{
urlGeneratorId: ID;
initialState: UrlGeneratorStateMapping[ID]['State'];
restoreState: UrlGeneratorStateMapping[ID]['State'];
}> | | +| [getName](./kibana-plugin-plugins-data-public.searchsessioninfoprovider.getname.md) | () => Promise<string> | User-facing name of the session. e.g. will be displayed in background sessions management list | +| [getUrlGeneratorData](./kibana-plugin-plugins-data-public.searchsessioninfoprovider.geturlgeneratordata.md) | () => Promise<{
urlGeneratorId: ID;
initialState: UrlGeneratorStateMapping[ID]['State'];
restoreState: UrlGeneratorStateMapping[ID]['State'];
}> | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isessionservice.asscopedprovider.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isessionservice.asscopedprovider.md new file mode 100644 index 00000000000000..d52b9b783919b4 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isessionservice.asscopedprovider.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISessionService](./kibana-plugin-plugins-data-server.isessionservice.md) > [asScopedProvider](./kibana-plugin-plugins-data-server.isessionservice.asscopedprovider.md) + +## ISessionService.asScopedProvider property + +Signature: + +```typescript +asScopedProvider: (core: CoreStart) => (request: KibanaRequest) => IScopedSessionService; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isessionservice.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isessionservice.md new file mode 100644 index 00000000000000..dcc7dfc8bb9465 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isessionservice.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISessionService](./kibana-plugin-plugins-data-server.isessionservice.md) + +## ISessionService interface + +Signature: + +```typescript +export interface ISessionService +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [asScopedProvider](./kibana-plugin-plugins-data-server.isessionservice.asscopedprovider.md) | (core: CoreStart) => (request: KibanaRequest) => IScopedSessionService | | + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md index c85f294d162bcc..55504e01b6c9a3 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md @@ -14,6 +14,7 @@ | [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) | | | [OptionedParamType](./kibana-plugin-plugins-data-server.optionedparamtype.md) | | | [Plugin](./kibana-plugin-plugins-data-server.plugin.md) | | +| [SessionService](./kibana-plugin-plugins-data-server.sessionservice.md) | The OSS session service. See data\_enhanced in X-Pack for the background session service. | ## Enumerations @@ -54,6 +55,7 @@ | [ISearchSetup](./kibana-plugin-plugins-data-server.isearchsetup.md) | | | [ISearchStart](./kibana-plugin-plugins-data-server.isearchstart.md) | | | [ISearchStrategy](./kibana-plugin-plugins-data-server.isearchstrategy.md) | Search strategy interface contains a search method that takes in a request and returns a promise that resolves to a response. | +| [ISessionService](./kibana-plugin-plugins-data-server.isessionservice.md) | | | [KueryNode](./kibana-plugin-plugins-data-server.kuerynode.md) | | | [OptionedValueProp](./kibana-plugin-plugins-data-server.optionedvalueprop.md) | | | [PluginSetup](./kibana-plugin-plugins-data-server.pluginsetup.md) | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice._constructor_.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice._constructor_.md new file mode 100644 index 00000000000000..73d658455a66f6 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice._constructor_.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [SessionService](./kibana-plugin-plugins-data-server.sessionservice.md) > [(constructor)](./kibana-plugin-plugins-data-server.sessionservice._constructor_.md) + +## SessionService.(constructor) + +Constructs a new instance of the `SessionService` class + +Signature: + +```typescript +constructor(); +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.asscopedprovider.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.asscopedprovider.md new file mode 100644 index 00000000000000..f3af7fb0f61d99 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.asscopedprovider.md @@ -0,0 +1,26 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [SessionService](./kibana-plugin-plugins-data-server.sessionservice.md) > [asScopedProvider](./kibana-plugin-plugins-data-server.sessionservice.asscopedprovider.md) + +## SessionService.asScopedProvider() method + +Signature: + +```typescript +asScopedProvider(core: CoreStart): (request: KibanaRequest) => { + search: , Response_1 extends IKibanaSearchResponse>(strategy: ISearchStrategy, request: Request_1, options: import("../../../common").ISearchOptions, deps: import("../types").SearchStrategyDependencies) => import("rxjs").Observable; + }; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| core | CoreStart | | + +Returns: + +`(request: KibanaRequest) => { + search: , Response_1 extends IKibanaSearchResponse>(strategy: ISearchStrategy, request: Request_1, options: import("../../../common").ISearchOptions, deps: import("../types").SearchStrategyDependencies) => import("rxjs").Observable; + }` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.md new file mode 100644 index 00000000000000..2369b006445485 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [SessionService](./kibana-plugin-plugins-data-server.sessionservice.md) + +## SessionService class + +The OSS session service. See data\_enhanced in X-Pack for the background session service. + +Signature: + +```typescript +export declare class SessionService implements ISessionService +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)()](./kibana-plugin-plugins-data-server.sessionservice._constructor_.md) | | Constructs a new instance of the SessionService class | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [asScopedProvider(core)](./kibana-plugin-plugins-data-server.sessionservice.asscopedprovider.md) | | | +| [search(strategy, args)](./kibana-plugin-plugins-data-server.sessionservice.search.md) | | | + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.search.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.search.md new file mode 100644 index 00000000000000..8f8620a6ed5ee9 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.search.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [SessionService](./kibana-plugin-plugins-data-server.sessionservice.md) > [search](./kibana-plugin-plugins-data-server.sessionservice.search.md) + +## SessionService.search() method + +Signature: + +```typescript +search(strategy: ISearchStrategy, ...args: Parameters['search']>): import("rxjs").Observable; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| strategy | ISearchStrategy<Request, Response> | | +| args | Parameters<ISearchStrategy<Request, Response>['search']> | | + +Returns: + +`import("rxjs").Observable` + diff --git a/src/plugins/data/common/search/index.ts b/src/plugins/data/common/search/index.ts index 01944d6e37aaf2..d5939b4ec9cbf6 100644 --- a/src/plugins/data/common/search/index.ts +++ b/src/plugins/data/common/search/index.ts @@ -23,5 +23,4 @@ export * from './expressions'; export * from './search_source'; export * from './tabify'; export * from './types'; -export * from './session'; export * from './utils'; diff --git a/src/plugins/data/common/search/session/index.ts b/src/plugins/data/common/search/session/index.ts deleted file mode 100644 index 0feb43f8f1d4b9..00000000000000 --- a/src/plugins/data/common/search/session/index.ts +++ /dev/null @@ -1,21 +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. - */ - -export * from './status'; -export * from './types'; diff --git a/src/plugins/data/common/search/session/status.ts b/src/plugins/data/common/search/session/status.ts deleted file mode 100644 index 1f6b6eb3084bb5..00000000000000 --- a/src/plugins/data/common/search/session/status.ts +++ /dev/null @@ -1,26 +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. - */ - -export enum BackgroundSessionStatus { - IN_PROGRESS = 'in_progress', - ERROR = 'error', - COMPLETE = 'complete', - CANCELLED = 'cancelled', - EXPIRED = 'expired', -} diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index ad1861cecea0be..cc4e32cbce1578 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -81,6 +81,7 @@ import { SavedObject as SavedObject_2 } from 'src/core/server'; import { SavedObject as SavedObject_3 } from 'src/core/public'; import { SavedObjectReference } from 'src/core/types'; import { SavedObjectsClientContract } from 'src/core/public'; +import { SavedObjectsFindOptions } from 'kibana/public'; import { SavedObjectsFindResponse } from 'kibana/server'; import { Search } from '@elastic/elasticsearch/api/requestParams'; import { SearchResponse } from 'elasticsearch'; diff --git a/src/plugins/data/public/search/session/sessions_client.ts b/src/plugins/data/public/search/session/sessions_client.ts index c19c5db0640946..38be647a37c7ac 100644 --- a/src/plugins/data/public/search/session/sessions_client.ts +++ b/src/plugins/data/public/search/session/sessions_client.ts @@ -18,9 +18,8 @@ */ import { PublicContract } from '@kbn/utility-types'; -import { HttpSetup } from 'kibana/public'; +import { HttpSetup, SavedObjectsFindOptions } from 'kibana/public'; import type { SavedObject, SavedObjectsFindResponse } from 'kibana/server'; -import { BackgroundSessionSavedObjectAttributes, SearchSessionFindOptions } from '../../../common'; export type ISessionsClient = PublicContract; export interface SessionsClientDeps { @@ -37,7 +36,7 @@ export class SessionsClient { this.http = deps.http; } - public get(sessionId: string): Promise> { + public get(sessionId: string): Promise { return this.http.get(`/internal/session/${encodeURIComponent(sessionId)}`); } @@ -55,7 +54,7 @@ export class SessionsClient { restoreState: Record; urlGeneratorId: string; sessionId: string; - }): Promise> { + }): Promise { return this.http.post(`/internal/session`, { body: JSON.stringify({ name, @@ -68,18 +67,13 @@ export class SessionsClient { }); } - public find( - options: SearchSessionFindOptions - ): Promise> { + public find(options: SavedObjectsFindOptions): Promise { return this.http!.post(`/internal/session`, { body: JSON.stringify(options), }); } - public update( - sessionId: string, - attributes: Partial - ): Promise> { + public update(sessionId: string, attributes: unknown): Promise { return this.http!.put(`/internal/session/${encodeURIComponent(sessionId)}`, { body: JSON.stringify(attributes), }); diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index a233447cdf438b..4a4e1df2cf45f4 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -242,6 +242,8 @@ export { searchUsageObserver, shimAbortSignal, SearchUsage, + SessionService, + ISessionService, } from './search'; // Search namespace diff --git a/src/plugins/data/server/saved_objects/index.ts b/src/plugins/data/server/saved_objects/index.ts index 7cd4d319e64171..077f9380823d08 100644 --- a/src/plugins/data/server/saved_objects/index.ts +++ b/src/plugins/data/server/saved_objects/index.ts @@ -20,4 +20,3 @@ export { querySavedObjectType } from './query'; export { indexPatternSavedObjectType } from './index_patterns'; export { kqlTelemetry } from './kql_telemetry'; export { searchTelemetry } from './search_telemetry'; -export { BACKGROUND_SESSION_TYPE, backgroundSessionMapping } from './background_session'; diff --git a/src/plugins/data/server/search/index.ts b/src/plugins/data/server/search/index.ts index 3001bbe3c2f381..f051e4c48223ce 100644 --- a/src/plugins/data/server/search/index.ts +++ b/src/plugins/data/server/search/index.ts @@ -22,3 +22,4 @@ export * from './es_search'; export { usageProvider, SearchUsage, searchUsageObserver } from './collectors'; export * from './aggs'; export { shimHitsTotal } from './routes'; +export * from './session'; diff --git a/src/plugins/data/server/search/mocks.ts b/src/plugins/data/server/search/mocks.ts index 290e94ee7cf991..6d214008e8b173 100644 --- a/src/plugins/data/server/search/mocks.ts +++ b/src/plugins/data/server/search/mocks.ts @@ -50,13 +50,7 @@ export function createSearchRequestHandlerContext(): jest.Mocked { private defaultSearchStrategyName: string = ES_SEARCH_STRATEGY; private searchStrategies: StrategyMap = {}; private coreStart?: CoreStart; - private sessionService: BackgroundSessionService = new BackgroundSessionService(); + private sessionService: ISessionService = new SessionService(); constructor( private initializerContext: PluginInitializerContext, @@ -131,7 +126,6 @@ export class SearchService implements Plugin { }; registerSearchRoute(router); registerMsearchRoute(router, routeDependencies); - registerSessionRoutes(router); core.getStartServices().then(([coreStart]) => { this.coreStart = coreStart; @@ -143,8 +137,6 @@ export class SearchService implements Plugin { return { ...search, session }; }); - core.savedObjects.registerType(backgroundSessionMapping); - this.registerSearchStrategy( ES_SEARCH_STRATEGY, esSearchStrategyProvider( @@ -219,6 +211,7 @@ export class SearchService implements Plugin { if (this.searchStrategies.hasOwnProperty(enhancements.defaultStrategy)) { this.defaultSearchStrategyName = enhancements.defaultStrategy; } + this.sessionService = enhancements.sessionService; }, aggs, registerSearchStrategy: this.registerSearchStrategy, @@ -279,7 +272,6 @@ export class SearchService implements Plugin { public stop() { this.aggsService.stop(); - this.sessionService.stop(); } private registerSearchStrategy = < @@ -297,6 +289,7 @@ export class SearchService implements Plugin { SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest, SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse >( + session: IScopedSessionService, request: SearchStrategyRequest, options: ISearchOptions, deps: SearchStrategyDependencies @@ -304,15 +297,11 @@ export class SearchService implements Plugin { const strategy = this.getSearchStrategy( options.strategy ); - - return options.sessionId - ? this.sessionService.search(strategy, request, options, deps) - : strategy.search(request, options, deps); + return session.search(strategy, request, options, deps); }; private cancel = (id: string, options: ISearchOptions, deps: SearchStrategyDependencies) => { const strategy = this.getSearchStrategy(options.strategy); - return strategy.cancel ? strategy.cancel(id, options, deps) : Promise.resolve(); }; @@ -330,18 +319,20 @@ export class SearchService implements Plugin { return strategy; }; - private asScopedProvider = ({ elasticsearch, savedObjects, uiSettings }: CoreStart) => { + private asScopedProvider = (core: CoreStart) => { + const { elasticsearch, savedObjects, uiSettings } = core; + const getSessionAsScoped = this.sessionService.asScopedProvider(core); return (request: KibanaRequest): ISearchClient => { - const savedObjectsClient = savedObjects.getScopedClient(request, { - includedHiddenTypes: [BACKGROUND_SESSION_TYPE], - }); + const scopedSession = getSessionAsScoped(request); + const savedObjectsClient = savedObjects.getScopedClient(request); const deps = { savedObjectsClient, esClient: elasticsearch.client.asScoped(request), uiSettingsClient: uiSettings.asScopedToClient(savedObjectsClient), }; return { - search: (searchRequest, options = {}) => this.search(searchRequest, options, deps), + search: (searchRequest, options = {}) => + this.search(scopedSession, searchRequest, options, deps), cancel: (id, options = {}) => this.cancel(id, options, deps), }; }; diff --git a/src/plugins/data/server/search/session/index.ts b/src/plugins/data/server/search/session/index.ts index 11b5b16a02b561..0966a1e4a18ec5 100644 --- a/src/plugins/data/server/search/session/index.ts +++ b/src/plugins/data/server/search/session/index.ts @@ -17,4 +17,5 @@ * under the License. */ -export { BackgroundSessionService, ISearchSessionClient } from './session_service'; +export * from './session_service'; +export * from './types'; diff --git a/src/plugins/data/server/search/session/session_service.test.ts b/src/plugins/data/server/search/session/session_service.test.ts index 167aa8c4099e04..3dbe01105aca4e 100644 --- a/src/plugins/data/server/search/session/session_service.test.ts +++ b/src/plugins/data/server/search/session/session_service.test.ts @@ -18,297 +18,21 @@ */ import { of } from 'rxjs'; -import type { SavedObject, SavedObjectsClientContract } from 'kibana/server'; -import type { SearchStrategyDependencies } from '../types'; -import { savedObjectsClientMock } from '../../../../../core/server/mocks'; -import { BackgroundSessionStatus } from '../../../common'; -import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; -import { BackgroundSessionService } from './session_service'; -import { createRequestHash } from './utils'; +import { SearchStrategyDependencies } from '../types'; +import { SessionService } from './session_service'; -describe('BackgroundSessionService', () => { - let savedObjectsClient: jest.Mocked; - let service: BackgroundSessionService; - - const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; - const mockSavedObject: SavedObject = { - id: 'd7170a35-7e2c-48d6-8dec-9a056721b489', - type: BACKGROUND_SESSION_TYPE, - attributes: { - name: 'my_name', - appId: 'my_app_id', - urlGeneratorId: 'my_url_generator_id', - idMapping: {}, - }, - references: [], - }; - - beforeEach(() => { - savedObjectsClient = savedObjectsClientMock.create(); - service = new BackgroundSessionService(); - }); - - it('search throws if `name` is not provided', () => { - expect(() => service.save(sessionId, {}, { savedObjectsClient })).rejects.toMatchInlineSnapshot( - `[Error: Name is required]` - ); - }); - - it('save throws if `name` is not provided', () => { - expect(() => service.save(sessionId, {}, { savedObjectsClient })).rejects.toMatchInlineSnapshot( - `[Error: Name is required]` - ); - }); - - it('get calls saved objects client', async () => { - savedObjectsClient.get.mockResolvedValue(mockSavedObject); - - const response = await service.get(sessionId, { savedObjectsClient }); - - expect(response).toBe(mockSavedObject); - expect(savedObjectsClient.get).toHaveBeenCalledWith(BACKGROUND_SESSION_TYPE, sessionId); - }); - - it('find calls saved objects client', async () => { - const mockFindSavedObject = { - ...mockSavedObject, - score: 1, - }; - const mockResponse = { - saved_objects: [mockFindSavedObject], - total: 1, - per_page: 1, - page: 0, - }; - savedObjectsClient.find.mockResolvedValue(mockResponse); - - const options = { page: 0, perPage: 5 }; - const response = await service.find(options, { savedObjectsClient }); - - expect(response).toBe(mockResponse); - expect(savedObjectsClient.find).toHaveBeenCalledWith({ - ...options, - type: BACKGROUND_SESSION_TYPE, - }); - }); - - it('update calls saved objects client', async () => { - const mockUpdateSavedObject = { - ...mockSavedObject, - attributes: {}, - }; - savedObjectsClient.update.mockResolvedValue(mockUpdateSavedObject); - - const attributes = { name: 'new_name' }; - const response = await service.update(sessionId, attributes, { savedObjectsClient }); - - expect(response).toBe(mockUpdateSavedObject); - expect(savedObjectsClient.update).toHaveBeenCalledWith( - BACKGROUND_SESSION_TYPE, - sessionId, - attributes - ); - }); - - it('delete calls saved objects client', async () => { - savedObjectsClient.delete.mockResolvedValue({}); - - const response = await service.delete(sessionId, { savedObjectsClient }); - - expect(response).toEqual({}); - expect(savedObjectsClient.delete).toHaveBeenCalledWith(BACKGROUND_SESSION_TYPE, sessionId); - }); - - describe('search', () => { +describe('SessionService', () => { + it('search invokes `strategy.search`', async () => { + const service = new SessionService(); const mockSearch = jest.fn().mockReturnValue(of({})); const mockStrategy = { search: mockSearch }; - const mockDeps = {} as SearchStrategyDependencies; - - beforeEach(() => { - mockSearch.mockClear(); - }); - - it('searches using the original request if not restoring', async () => { - const searchRequest = { params: {} }; - const options = { sessionId, isStored: false, isRestore: false }; - - await service.search(mockStrategy, searchRequest, options, mockDeps).toPromise(); - - expect(mockSearch).toBeCalledWith(searchRequest, options, mockDeps); - }); - - it('searches using the original request if `id` is provided', async () => { - const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; - const searchRequest = { id: searchId, params: {} }; - const options = { sessionId, isStored: true, isRestore: true }; - - await service.search(mockStrategy, searchRequest, options, mockDeps).toPromise(); - - expect(mockSearch).toBeCalledWith(searchRequest, options, mockDeps); - }); - - it('searches by looking up an `id` if restoring and `id` is not provided', async () => { - const searchRequest = { params: {} }; - const options = { sessionId, isStored: true, isRestore: true }; - const spyGetId = jest.spyOn(service, 'getId').mockResolvedValueOnce('my_id'); - - await service.search(mockStrategy, searchRequest, options, mockDeps).toPromise(); - - expect(mockSearch).toBeCalledWith({ ...searchRequest, id: 'my_id' }, options, mockDeps); - - spyGetId.mockRestore(); - }); - - it('calls `trackId` once if the response contains an `id` and not restoring', async () => { - const searchRequest = { params: {} }; - const options = { sessionId, isStored: false, isRestore: false }; - const spyTrackId = jest.spyOn(service, 'trackId').mockResolvedValue(); - mockSearch.mockReturnValueOnce(of({ id: 'my_id' }, { id: 'my_id' })); - - await service.search(mockStrategy, searchRequest, options, mockDeps).toPromise(); - - expect(spyTrackId).toBeCalledTimes(1); - expect(spyTrackId).toBeCalledWith(searchRequest, 'my_id', options, mockDeps); - - spyTrackId.mockRestore(); - }); - - it('does not call `trackId` if restoring', async () => { - const searchRequest = { params: {} }; - const options = { sessionId, isStored: true, isRestore: true }; - const spyGetId = jest.spyOn(service, 'getId').mockResolvedValueOnce('my_id'); - const spyTrackId = jest.spyOn(service, 'trackId').mockResolvedValue(); - mockSearch.mockReturnValueOnce(of({ id: 'my_id' })); - - await service.search(mockStrategy, searchRequest, options, mockDeps).toPromise(); - - expect(spyTrackId).not.toBeCalled(); - - spyGetId.mockRestore(); - spyTrackId.mockRestore(); - }); - }); - - describe('trackId', () => { - it('stores hash in memory when `isStored` is `false` for when `save` is called', async () => { - const searchRequest = { params: {} }; - const requestHash = createRequestHash(searchRequest.params); - const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; - const isStored = false; - const name = 'my saved background search session'; - const appId = 'my_app_id'; - const urlGeneratorId = 'my_url_generator_id'; - const created = new Date().toISOString(); - const expires = new Date().toISOString(); - - await service.trackId( - searchRequest, - searchId, - { sessionId, isStored }, - { savedObjectsClient } - ); - - expect(savedObjectsClient.update).not.toHaveBeenCalled(); - - await service.save( - sessionId, - { name, created, expires, appId, urlGeneratorId }, - { savedObjectsClient } - ); - - expect(savedObjectsClient.create).toHaveBeenCalledWith( - BACKGROUND_SESSION_TYPE, - { - name, - created, - expires, - initialState: {}, - restoreState: {}, - status: BackgroundSessionStatus.IN_PROGRESS, - idMapping: { [requestHash]: searchId }, - appId, - urlGeneratorId, - }, - { id: sessionId } - ); - }); - - it('updates saved object when `isStored` is `true`', async () => { - const searchRequest = { params: {} }; - const requestHash = createRequestHash(searchRequest.params); - const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; - const isStored = true; - - await service.trackId( - searchRequest, - searchId, - { sessionId, isStored }, - { savedObjectsClient } - ); - - expect(savedObjectsClient.update).toHaveBeenCalledWith(BACKGROUND_SESSION_TYPE, sessionId, { - idMapping: { [requestHash]: searchId }, - }); - }); - }); - - describe('getId', () => { - it('throws if `sessionId` is not provided', () => { - const searchRequest = { params: {} }; - - expect(() => - service.getId(searchRequest, {}, { savedObjectsClient }) - ).rejects.toMatchInlineSnapshot(`[Error: Session ID is required]`); - }); - - it('throws if there is not a saved object', () => { - const searchRequest = { params: {} }; - - expect(() => - service.getId(searchRequest, { sessionId, isStored: false }, { savedObjectsClient }) - ).rejects.toMatchInlineSnapshot( - `[Error: Cannot get search ID from a session that is not stored]` - ); - }); - - it('throws if not restoring a saved session', () => { - const searchRequest = { params: {} }; - - expect(() => - service.getId( - searchRequest, - { sessionId, isStored: true, isRestore: false }, - { savedObjectsClient } - ) - ).rejects.toMatchInlineSnapshot( - `[Error: Get search ID is only supported when restoring a session]` - ); - }); - - it('returns the search ID from the saved object ID mapping', async () => { - const searchRequest = { params: {} }; - const requestHash = createRequestHash(searchRequest.params); - const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; - const mockSession = { - id: 'd7170a35-7e2c-48d6-8dec-9a056721b489', - type: BACKGROUND_SESSION_TYPE, - attributes: { - name: 'my_name', - appId: 'my_app_id', - urlGeneratorId: 'my_url_generator_id', - idMapping: { [requestHash]: searchId }, - }, - references: [], - }; - savedObjectsClient.get.mockResolvedValue(mockSession); + const mockRequest = { id: 'bar' }; + const mockOptions = { sessionId: '1234' }; + const mockDeps = { savedObjectsClient: {} } as SearchStrategyDependencies; - const id = await service.getId( - searchRequest, - { sessionId, isStored: true, isRestore: true }, - { savedObjectsClient } - ); + await service.search(mockStrategy, mockRequest, mockOptions, mockDeps); - expect(id).toBe(searchId); - }); + expect(mockSearch).toHaveBeenCalled(); + expect(mockSearch).toHaveBeenCalledWith(mockRequest, mockOptions, mockDeps); }); }); diff --git a/src/plugins/data/server/search/session/session_service.ts b/src/plugins/data/server/search/session/session_service.ts index d997af728b60cb..15021436d8821a 100644 --- a/src/plugins/data/server/search/session/session_service.ts +++ b/src/plugins/data/server/search/session/session_service.ts @@ -17,229 +17,27 @@ * under the License. */ -import { CoreStart, KibanaRequest, SavedObjectsClientContract } from 'kibana/server'; -import { from } from 'rxjs'; -import { switchMap } from 'rxjs/operators'; -import { - BackgroundSessionSavedObjectAttributes, - BackgroundSessionStatus, - IKibanaSearchRequest, - IKibanaSearchResponse, - ISearchOptions, - SearchSessionFindOptions, - tapFirst, -} from '../../../common'; -import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; -import { ISearchStrategy, SearchStrategyDependencies } from '../types'; -import { createRequestHash } from './utils'; - -const DEFAULT_EXPIRATION = 7 * 24 * 60 * 60 * 1000; - -export interface BackgroundSessionDependencies { - savedObjectsClient: SavedObjectsClientContract; -} - -export type ISearchSessionClient = ReturnType< - ReturnType ->; - -export class BackgroundSessionService { - /** - * Map of sessionId to { [requestHash]: searchId } - * @private - */ - private sessionSearchMap = new Map>(); +import { CoreStart, KibanaRequest } from 'kibana/server'; +import { IKibanaSearchRequest, IKibanaSearchResponse } from '../../../common'; +import { ISearchStrategy } from '../types'; +import { ISessionService } from './types'; +/** + * The OSS session service. See data_enhanced in X-Pack for the background session service. + */ +export class SessionService implements ISessionService { constructor() {} - public setup = () => {}; - - public start = (core: CoreStart) => { - return { - asScoped: this.asScopedProvider(core), - }; - }; - - public stop = () => { - this.sessionSearchMap.clear(); - }; - - public search = ( + public search( strategy: ISearchStrategy, - searchRequest: Request, - options: ISearchOptions, - deps: SearchStrategyDependencies - ) => { - // If this is a restored background search session, look up the ID using the provided sessionId - const getSearchRequest = async () => - !options.isRestore || searchRequest.id - ? searchRequest - : { - ...searchRequest, - id: await this.getId(searchRequest, options, deps), - }; - - return from(getSearchRequest()).pipe( - switchMap((request) => strategy.search(request, options, deps)), - tapFirst((response) => { - if (searchRequest.id || !options.sessionId || !response.id || options.isRestore) return; - this.trackId(searchRequest, response.id, options, { - savedObjectsClient: deps.savedObjectsClient, - }); - }) - ); - }; - - // TODO: Generate the `userId` from the realm type/realm name/username - public save = async ( - sessionId: string, - { - name, - appId, - created = new Date().toISOString(), - expires = new Date(Date.now() + DEFAULT_EXPIRATION).toISOString(), - status = BackgroundSessionStatus.IN_PROGRESS, - urlGeneratorId, - initialState = {}, - restoreState = {}, - }: Partial, - { savedObjectsClient }: BackgroundSessionDependencies - ) => { - if (!name) throw new Error('Name is required'); - if (!appId) throw new Error('AppId is required'); - if (!urlGeneratorId) throw new Error('UrlGeneratorId is required'); - - // Get the mapping of request hash/search ID for this session - const searchMap = this.sessionSearchMap.get(sessionId) ?? new Map(); - const idMapping = Object.fromEntries(searchMap.entries()); - const attributes = { - name, - created, - expires, - status, - initialState, - restoreState, - idMapping, - urlGeneratorId, - appId, - }; - const session = await savedObjectsClient.create( - BACKGROUND_SESSION_TYPE, - attributes, - { id: sessionId } - ); - - // Clear out the entries for this session ID so they don't get saved next time - this.sessionSearchMap.delete(sessionId); - - return session; - }; - - // TODO: Throw an error if this session doesn't belong to this user - public get = (sessionId: string, { savedObjectsClient }: BackgroundSessionDependencies) => { - return savedObjectsClient.get( - BACKGROUND_SESSION_TYPE, - sessionId - ); - }; - - // TODO: Throw an error if this session doesn't belong to this user - public find = ( - options: SearchSessionFindOptions, - { savedObjectsClient }: BackgroundSessionDependencies - ) => { - return savedObjectsClient.find({ - ...options, - type: BACKGROUND_SESSION_TYPE, + ...args: Parameters['search']> + ) { + return strategy.search(...args); + } + + public asScopedProvider(core: CoreStart) { + return (request: KibanaRequest) => ({ + search: this.search, }); - }; - - // TODO: Throw an error if this session doesn't belong to this user - public update = ( - sessionId: string, - attributes: Partial, - { savedObjectsClient }: BackgroundSessionDependencies - ) => { - return savedObjectsClient.update( - BACKGROUND_SESSION_TYPE, - sessionId, - attributes - ); - }; - - // TODO: Throw an error if this session doesn't belong to this user - public delete = (sessionId: string, { savedObjectsClient }: BackgroundSessionDependencies) => { - return savedObjectsClient.delete(BACKGROUND_SESSION_TYPE, sessionId); - }; - - /** - * Tracks the given search request/search ID in the saved session (if it exists). Otherwise, just - * store it in memory until a saved session exists. - * @internal - */ - public trackId = async ( - searchRequest: IKibanaSearchRequest, - searchId: string, - { sessionId, isStored }: ISearchOptions, - deps: BackgroundSessionDependencies - ) => { - if (!sessionId || !searchId) return; - const requestHash = createRequestHash(searchRequest.params); - - // If there is already a saved object for this session, update it to include this request/ID. - // Otherwise, just update the in-memory mapping for this session for when the session is saved. - if (isStored) { - const attributes = { idMapping: { [requestHash]: searchId } }; - await this.update(sessionId, attributes, deps); - } else { - const map = this.sessionSearchMap.get(sessionId) ?? new Map(); - map.set(requestHash, searchId); - this.sessionSearchMap.set(sessionId, map); - } - }; - - /** - * Look up an existing search ID that matches the given request in the given session so that the - * request can continue rather than restart. - * @internal - */ - public getId = async ( - searchRequest: IKibanaSearchRequest, - { sessionId, isStored, isRestore }: ISearchOptions, - deps: BackgroundSessionDependencies - ) => { - if (!sessionId) { - throw new Error('Session ID is required'); - } else if (!isStored) { - throw new Error('Cannot get search ID from a session that is not stored'); - } else if (!isRestore) { - throw new Error('Get search ID is only supported when restoring a session'); - } - - const session = await this.get(sessionId, deps); - const requestHash = createRequestHash(searchRequest.params); - if (!session.attributes.idMapping.hasOwnProperty(requestHash)) { - throw new Error('No search ID in this session matching the given search request'); - } - - return session.attributes.idMapping[requestHash]; - }; - - public asScopedProvider = ({ savedObjects }: CoreStart) => { - return (request: KibanaRequest) => { - const savedObjectsClient = savedObjects.getScopedClient(request, { - includedHiddenTypes: [BACKGROUND_SESSION_TYPE], - }); - const deps = { savedObjectsClient }; - return { - save: (sessionId: string, attributes: Partial) => - this.save(sessionId, attributes, deps), - get: (sessionId: string) => this.get(sessionId, deps), - find: (options: SearchSessionFindOptions) => this.find(options, deps), - update: (sessionId: string, attributes: Partial) => - this.update(sessionId, attributes, deps), - delete: (sessionId: string) => this.delete(sessionId, deps), - }; - }; - }; + } } diff --git a/src/plugins/data/common/search/session/types.ts b/src/plugins/data/server/search/session/types.ts similarity index 55% rename from src/plugins/data/common/search/session/types.ts rename to src/plugins/data/server/search/session/types.ts index 50ca3ca390ece0..5e179b99952fea 100644 --- a/src/plugins/data/common/search/session/types.ts +++ b/src/plugins/data/server/search/session/types.ts @@ -17,28 +17,19 @@ * under the License. */ -export interface BackgroundSessionSavedObjectAttributes { - /** - * User-facing session name to be displayed in session management - */ - name: string; - /** - * App that created the session. e.g 'discover' - */ - appId: string; - created: string; - expires: string; - status: string; - urlGeneratorId: string; - initialState: Record; - restoreState: Record; - idMapping: Record; +import { Observable } from 'rxjs'; +import { CoreStart, KibanaRequest } from 'kibana/server'; +import { ISearchStrategy } from '../types'; +import { IKibanaSearchRequest, IKibanaSearchResponse } from '../../../common/search'; + +export interface IScopedSessionService { + search: ( + strategy: ISearchStrategy, + ...args: Parameters['search']> + ) => Observable; + [prop: string]: any; } -export interface SearchSessionFindOptions { - page?: number; - perPage?: number; - sortField?: string; - sortOrder?: string; - filter?: string; +export interface ISessionService { + asScopedProvider: (core: CoreStart) => (request: KibanaRequest) => IScopedSessionService; } diff --git a/src/plugins/data/server/search/session/utils.test.ts b/src/plugins/data/server/search/session/utils.test.ts deleted file mode 100644 index d190f892a7f848..00000000000000 --- a/src/plugins/data/server/search/session/utils.test.ts +++ /dev/null @@ -1,37 +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 { createRequestHash } from './utils'; - -describe('data/search/session utils', () => { - describe('createRequestHash', () => { - it('ignores `preference`', () => { - const request = { - foo: 'bar', - }; - - const withPreference = { - ...request, - preference: 1234, - }; - - expect(createRequestHash(request)).toEqual(createRequestHash(withPreference)); - }); - }); -}); diff --git a/src/plugins/data/server/search/session/utils.ts b/src/plugins/data/server/search/session/utils.ts deleted file mode 100644 index c3332f80b6e3fa..00000000000000 --- a/src/plugins/data/server/search/session/utils.ts +++ /dev/null @@ -1,30 +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 { createHash } from 'crypto'; - -/** - * Generate the hash for this request so that, in the future, this hash can be used to look up - * existing search IDs for this request. Ignores the `preference` parameter since it generally won't - * match from one request to another identical request. - */ -export function createRequestHash(keys: Record) { - const { preference, ...params } = keys; - return createHash(`sha256`).update(JSON.stringify(params)).digest('hex'); -} diff --git a/src/plugins/data/server/search/types.ts b/src/plugins/data/server/search/types.ts index ebce02014c2a4e..db8b8ac72d0e56 100644 --- a/src/plugins/data/server/search/types.ts +++ b/src/plugins/data/server/search/types.ts @@ -34,9 +34,11 @@ import { import { AggsSetup, AggsStart } from './aggs'; import { SearchUsage } from './collectors'; import { IEsSearchRequest, IEsSearchResponse } from './es_search'; +import { ISessionService } from './session'; export interface SearchEnhancements { defaultStrategy: string; + sessionService: ISessionService; } export interface SearchStrategyDependencies { diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 86ec784834ace6..cb71e5fa0460a7 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -13,8 +13,8 @@ import { BfetchServerSetup } from 'src/plugins/bfetch/server'; import { ConfigDeprecationProvider } from '@kbn/config'; import { CoreSetup } from 'src/core/server'; import { CoreSetup as CoreSetup_2 } from 'kibana/server'; -import { CoreStart } from 'src/core/server'; -import { CoreStart as CoreStart_2 } from 'kibana/server'; +import { CoreStart } from 'kibana/server'; +import { CoreStart as CoreStart_2 } from 'src/core/server'; import { Datatable } from 'src/plugins/expressions'; import { Datatable as Datatable_2 } from 'src/plugins/expressions/common'; import { DatatableColumn } from 'src/plugins/expressions'; @@ -36,7 +36,8 @@ import { ISearchOptions as ISearchOptions_2 } from 'src/plugins/data/public'; import { ISearchSource } from 'src/plugins/data/public'; import { IUiSettingsClient } from 'src/core/server'; import { IUiSettingsClient as IUiSettingsClient_3 } from 'kibana/server'; -import { KibanaRequest } from 'src/core/server'; +import { KibanaRequest } from 'kibana/server'; +import { KibanaRequest as KibanaRequest_2 } from 'src/core/server'; import { LegacyAPICaller } from 'src/core/server'; import { Logger } from 'src/core/server'; import { Logger as Logger_2 } from 'kibana/server'; @@ -738,7 +739,7 @@ export class IndexPatternsService implements Plugin_3 Promise; }; } @@ -781,11 +782,11 @@ export interface ISearchStart ISearchClient; + asScoped: (request: KibanaRequest_2) => ISearchClient; getSearchStrategy: (name?: string) => ISearchStrategy; // (undocumented) searchSource: { - asScoped: (request: KibanaRequest) => Promise; + asScoped: (request: KibanaRequest_2) => Promise; }; } @@ -799,6 +800,16 @@ export interface ISearchStrategy Observable; } +// Warning: (ae-missing-release-tag) "ISessionService" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ISessionService { + // Warning: (ae-forgotten-export) The symbol "IScopedSessionService" needs to be exported by the entry point index.d.ts + // + // (undocumented) + asScopedProvider: (core: CoreStart) => (request: KibanaRequest) => IScopedSessionService; +} + // @public (undocumented) export enum KBN_FIELD_TYPES { // (undocumented) @@ -957,7 +968,7 @@ export class Plugin implements Plugin_2 Promise; }; @@ -1091,6 +1102,19 @@ export function searchUsageObserver(logger: Logger_2, usage?: SearchUsage): { error(): void; }; +// Warning: (ae-missing-release-tag) "SessionService" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export class SessionService implements ISessionService { + constructor(); + // (undocumented) + asScopedProvider(core: CoreStart): (request: KibanaRequest) => { + search: , Response_1 extends IKibanaSearchResponse>(strategy: ISearchStrategy, request: Request_1, options: import("../../../common").ISearchOptions, deps: import("../types").SearchStrategyDependencies) => import("rxjs").Observable; + }; + // (undocumented) + search(strategy: ISearchStrategy, ...args: Parameters['search']>): import("rxjs").Observable; +} + // @internal export const shimAbortSignal: (promise: TransportRequestPromise, signal?: AbortSignal | undefined) => TransportRequestPromise; @@ -1236,23 +1260,23 @@ export function usageProvider(core: CoreSetup_2): SearchUsage; // src/plugins/data/server/index.ts:111:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:137:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:137:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:248:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:248:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:248:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:248:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:250:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:251:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:260:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:261:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:262:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:266:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:267:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:271:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:274:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:275:1 - (ae-forgotten-export) The symbol "calcAutoIntervalLessThan" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:250:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:250:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:250:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:250:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:252:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:253:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:262:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:263:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:264:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:268:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:269:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:273:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:276:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:277:1 - (ae-forgotten-export) The symbol "calcAutoIntervalLessThan" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index_patterns/index_patterns_service.ts:58:14 - (ae-forgotten-export) The symbol "IndexPatternsService" needs to be exported by the entry point index.d.ts // src/plugins/data/server/plugin.ts:90:74 - (ae-forgotten-export) The symbol "DataEnhancements" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/search/types.ts:104:5 - (ae-forgotten-export) The symbol "ISearchStartSearchSource" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/search/types.ts:106:5 - (ae-forgotten-export) The symbol "ISearchStartSearchSource" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/embeddable/public/public.api.md b/src/plugins/embeddable/public/public.api.md index 534ab0f331e87d..6099a2d5f73b4f 100644 --- a/src/plugins/embeddable/public/public.api.md +++ b/src/plugins/embeddable/public/public.api.md @@ -73,6 +73,7 @@ import { SavedObjectAttributes } from 'kibana/server'; import { SavedObjectAttributes as SavedObjectAttributes_2 } from 'src/core/public'; import { SavedObjectAttributes as SavedObjectAttributes_3 } from 'kibana/public'; import { SavedObjectsClientContract as SavedObjectsClientContract_3 } from 'src/core/public'; +import { SavedObjectsFindOptions as SavedObjectsFindOptions_3 } from 'kibana/public'; import { SavedObjectsFindResponse as SavedObjectsFindResponse_2 } from 'kibana/server'; import { Search } from '@elastic/elasticsearch/api/requestParams'; import { SearchResponse } from 'elasticsearch'; diff --git a/x-pack/plugins/data_enhanced/common/index.ts b/x-pack/plugins/data_enhanced/common/index.ts index dd1a2d39ab5d14..7b14a723d78777 100644 --- a/x-pack/plugins/data_enhanced/common/index.ts +++ b/x-pack/plugins/data_enhanced/common/index.ts @@ -12,4 +12,7 @@ export { EqlSearchStrategyResponse, IAsyncSearchOptions, pollSearch, + BackgroundSessionSavedObjectAttributes, + BackgroundSessionFindOptions, + BackgroundSessionStatus, } from './search'; diff --git a/x-pack/plugins/data_enhanced/common/search/index.ts b/x-pack/plugins/data_enhanced/common/search/index.ts index 34bb21cb91af14..5617a1780c269e 100644 --- a/x-pack/plugins/data_enhanced/common/search/index.ts +++ b/x-pack/plugins/data_enhanced/common/search/index.ts @@ -6,3 +6,4 @@ export * from './types'; export * from './poll_search'; +export * from './session'; diff --git a/x-pack/plugins/data_enhanced/common/search/session/index.ts b/x-pack/plugins/data_enhanced/common/search/session/index.ts new file mode 100644 index 00000000000000..ef7f3f1c7f2c4c --- /dev/null +++ b/x-pack/plugins/data_enhanced/common/search/session/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './status'; +export * from './types'; diff --git a/x-pack/plugins/data_enhanced/common/search/session/status.ts b/x-pack/plugins/data_enhanced/common/search/session/status.ts new file mode 100644 index 00000000000000..a83dd389e4f13d --- /dev/null +++ b/x-pack/plugins/data_enhanced/common/search/session/status.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export enum BackgroundSessionStatus { + IN_PROGRESS = 'in_progress', + ERROR = 'error', + COMPLETE = 'complete', + CANCELLED = 'cancelled', + EXPIRED = 'expired', +} diff --git a/x-pack/plugins/data_enhanced/common/search/session/types.ts b/x-pack/plugins/data_enhanced/common/search/session/types.ts new file mode 100644 index 00000000000000..0b82c9160ea1a0 --- /dev/null +++ b/x-pack/plugins/data_enhanced/common/search/session/types.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface BackgroundSessionSavedObjectAttributes { + /** + * User-facing session name to be displayed in session management + */ + name: string; + /** + * App that created the session. e.g 'discover' + */ + appId: string; + created: string; + expires: string; + status: string; + urlGeneratorId: string; + initialState: Record; + restoreState: Record; + idMapping: Record; +} + +export interface BackgroundSessionFindOptions { + page?: number; + perPage?: number; + sortField?: string; + sortOrder?: string; + filter?: string; +} diff --git a/x-pack/plugins/data_enhanced/server/plugin.ts b/x-pack/plugins/data_enhanced/server/plugin.ts index ad21216bb7035f..956568fbe7632d 100644 --- a/x-pack/plugins/data_enhanced/server/plugin.ts +++ b/x-pack/plugins/data_enhanced/server/plugin.ts @@ -4,22 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - PluginInitializerContext, - CoreSetup, - CoreStart, - Plugin, - Logger, -} from '../../../../src/core/server'; +import { CoreSetup, CoreStart, Logger, Plugin, PluginInitializerContext } from 'kibana/server'; import { PluginSetup as DataPluginSetup, PluginStart as DataPluginStart, usageProvider, } from '../../../../src/plugins/data/server'; -import { enhancedEsSearchStrategyProvider, eqlSearchStrategyProvider } from './search'; import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/server'; -import { getUiSettings } from './ui_settings'; import { ENHANCED_ES_SEARCH_STRATEGY, EQL_SEARCH_STRATEGY } from '../common'; +import { registerSessionRoutes } from './routes'; +import { backgroundSessionMapping } from './saved_objects'; +import { + BackgroundSessionService, + enhancedEsSearchStrategyProvider, + eqlSearchStrategyProvider, +} from './search'; +import { getUiSettings } from './ui_settings'; interface SetupDependencies { data: DataPluginSetup; @@ -37,6 +37,7 @@ export class EnhancedDataServerPlugin implements Plugin { let mockCoreSetup: MockedKeys>; diff --git a/src/plugins/data/server/search/routes/session.ts b/x-pack/plugins/data_enhanced/server/routes/session.ts similarity index 85% rename from src/plugins/data/server/search/routes/session.ts rename to x-pack/plugins/data_enhanced/server/routes/session.ts index f7dfc776565e08..b6f1187f497816 100644 --- a/src/plugins/data/server/search/routes/session.ts +++ b/x-pack/plugins/data_enhanced/server/routes/session.ts @@ -1,20 +1,7 @@ /* - * 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. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. */ import { schema } from '@kbn/config-schema'; diff --git a/src/plugins/data/server/saved_objects/background_session.ts b/x-pack/plugins/data_enhanced/server/saved_objects/background_session.ts similarity index 51% rename from src/plugins/data/server/saved_objects/background_session.ts rename to x-pack/plugins/data_enhanced/server/saved_objects/background_session.ts index e81272628c091a..337c5f8ad974f5 100644 --- a/src/plugins/data/server/saved_objects/background_session.ts +++ b/x-pack/plugins/data_enhanced/server/saved_objects/background_session.ts @@ -1,20 +1,7 @@ /* - * 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. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. */ import { SavedObjectsType } from 'kibana/server'; diff --git a/x-pack/plugins/data_enhanced/server/saved_objects/index.ts b/x-pack/plugins/data_enhanced/server/saved_objects/index.ts new file mode 100644 index 00000000000000..4e07fe7117eaaa --- /dev/null +++ b/x-pack/plugins/data_enhanced/server/saved_objects/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './background_session'; diff --git a/x-pack/plugins/data_enhanced/server/search/index.ts b/x-pack/plugins/data_enhanced/server/search/index.ts index 64a28cea358e53..67369ef829752f 100644 --- a/x-pack/plugins/data_enhanced/server/search/index.ts +++ b/x-pack/plugins/data_enhanced/server/search/index.ts @@ -6,3 +6,4 @@ export { enhancedEsSearchStrategyProvider } from './es_search_strategy'; export { eqlSearchStrategyProvider } from './eql_search_strategy'; +export * from './session'; diff --git a/x-pack/plugins/data_enhanced/server/search/session/index.ts b/x-pack/plugins/data_enhanced/server/search/session/index.ts new file mode 100644 index 00000000000000..5b75885fb31dfa --- /dev/null +++ b/x-pack/plugins/data_enhanced/server/search/session/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './session_service'; diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts new file mode 100644 index 00000000000000..95dcb0592e23d5 --- /dev/null +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts @@ -0,0 +1,312 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { of } from 'rxjs'; +import type { SavedObject, SavedObjectsClientContract } from 'kibana/server'; +import type { SearchStrategyDependencies } from '../../../../../../src/plugins/data/server'; +import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; +import { BackgroundSessionStatus } from '../../../common'; +import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; +import { BackgroundSessionDependencies, BackgroundSessionService } from './session_service'; +import { createRequestHash } from './utils'; + +describe('BackgroundSessionService', () => { + let savedObjectsClient: jest.Mocked; + let service: BackgroundSessionService; + + const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const mockSavedObject: SavedObject = { + id: 'd7170a35-7e2c-48d6-8dec-9a056721b489', + type: BACKGROUND_SESSION_TYPE, + attributes: { + name: 'my_name', + appId: 'my_app_id', + urlGeneratorId: 'my_url_generator_id', + idMapping: {}, + }, + references: [], + }; + + beforeEach(() => { + savedObjectsClient = savedObjectsClientMock.create(); + service = new BackgroundSessionService(); + }); + + it('search throws if `name` is not provided', () => { + expect(() => service.save(sessionId, {}, { savedObjectsClient })).rejects.toMatchInlineSnapshot( + `[Error: Name is required]` + ); + }); + + it('save throws if `name` is not provided', () => { + expect(() => service.save(sessionId, {}, { savedObjectsClient })).rejects.toMatchInlineSnapshot( + `[Error: Name is required]` + ); + }); + + it('get calls saved objects client', async () => { + savedObjectsClient.get.mockResolvedValue(mockSavedObject); + + const response = await service.get(sessionId, { savedObjectsClient }); + + expect(response).toBe(mockSavedObject); + expect(savedObjectsClient.get).toHaveBeenCalledWith(BACKGROUND_SESSION_TYPE, sessionId); + }); + + it('find calls saved objects client', async () => { + const mockFindSavedObject = { + ...mockSavedObject, + score: 1, + }; + const mockResponse = { + saved_objects: [mockFindSavedObject], + total: 1, + per_page: 1, + page: 0, + }; + savedObjectsClient.find.mockResolvedValue(mockResponse); + + const options = { page: 0, perPage: 5 }; + const response = await service.find(options, { savedObjectsClient }); + + expect(response).toBe(mockResponse); + expect(savedObjectsClient.find).toHaveBeenCalledWith({ + ...options, + type: BACKGROUND_SESSION_TYPE, + }); + }); + + it('update calls saved objects client', async () => { + const mockUpdateSavedObject = { + ...mockSavedObject, + attributes: {}, + }; + savedObjectsClient.update.mockResolvedValue(mockUpdateSavedObject); + + const attributes = { name: 'new_name' }; + const response = await service.update(sessionId, attributes, { savedObjectsClient }); + + expect(response).toBe(mockUpdateSavedObject); + expect(savedObjectsClient.update).toHaveBeenCalledWith( + BACKGROUND_SESSION_TYPE, + sessionId, + attributes + ); + }); + + it('delete calls saved objects client', async () => { + savedObjectsClient.delete.mockResolvedValue({}); + + const response = await service.delete(sessionId, { savedObjectsClient }); + + expect(response).toEqual({}); + expect(savedObjectsClient.delete).toHaveBeenCalledWith(BACKGROUND_SESSION_TYPE, sessionId); + }); + + describe('search', () => { + const mockSearch = jest.fn().mockReturnValue(of({})); + const mockStrategy = { search: mockSearch }; + const mockSearchDeps = {} as SearchStrategyDependencies; + const mockDeps = {} as BackgroundSessionDependencies; + + beforeEach(() => { + mockSearch.mockClear(); + }); + + it('searches using the original request if not restoring', async () => { + const searchRequest = { params: {} }; + const options = { sessionId, isStored: false, isRestore: false }; + + await service + .search(mockStrategy, searchRequest, options, mockSearchDeps, mockDeps) + .toPromise(); + + expect(mockSearch).toBeCalledWith(searchRequest, options, mockSearchDeps); + }); + + it('searches using the original request if `id` is provided', async () => { + const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; + const searchRequest = { id: searchId, params: {} }; + const options = { sessionId, isStored: true, isRestore: true }; + + await service + .search(mockStrategy, searchRequest, options, mockSearchDeps, mockDeps) + .toPromise(); + + expect(mockSearch).toBeCalledWith(searchRequest, options, mockSearchDeps); + }); + + it('searches by looking up an `id` if restoring and `id` is not provided', async () => { + const searchRequest = { params: {} }; + const options = { sessionId, isStored: true, isRestore: true }; + const spyGetId = jest.spyOn(service, 'getId').mockResolvedValueOnce('my_id'); + + await service + .search(mockStrategy, searchRequest, options, mockSearchDeps, mockDeps) + .toPromise(); + + expect(mockSearch).toBeCalledWith({ ...searchRequest, id: 'my_id' }, options, mockSearchDeps); + + spyGetId.mockRestore(); + }); + + it('calls `trackId` once if the response contains an `id` and not restoring', async () => { + const searchRequest = { params: {} }; + const options = { sessionId, isStored: false, isRestore: false }; + const spyTrackId = jest.spyOn(service, 'trackId').mockResolvedValue(); + mockSearch.mockReturnValueOnce(of({ id: 'my_id' }, { id: 'my_id' })); + + await service + .search(mockStrategy, searchRequest, options, mockSearchDeps, mockDeps) + .toPromise(); + + expect(spyTrackId).toBeCalledTimes(1); + expect(spyTrackId).toBeCalledWith(searchRequest, 'my_id', options, {}); + + spyTrackId.mockRestore(); + }); + + it('does not call `trackId` if restoring', async () => { + const searchRequest = { params: {} }; + const options = { sessionId, isStored: true, isRestore: true }; + const spyGetId = jest.spyOn(service, 'getId').mockResolvedValueOnce('my_id'); + const spyTrackId = jest.spyOn(service, 'trackId').mockResolvedValue(); + mockSearch.mockReturnValueOnce(of({ id: 'my_id' })); + + await service + .search(mockStrategy, searchRequest, options, mockSearchDeps, mockDeps) + .toPromise(); + + expect(spyTrackId).not.toBeCalled(); + + spyGetId.mockRestore(); + spyTrackId.mockRestore(); + }); + }); + + describe('trackId', () => { + it('stores hash in memory when `isStored` is `false` for when `save` is called', async () => { + const searchRequest = { params: {} }; + const requestHash = createRequestHash(searchRequest.params); + const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; + const isStored = false; + const name = 'my saved background search session'; + const appId = 'my_app_id'; + const urlGeneratorId = 'my_url_generator_id'; + const created = new Date().toISOString(); + const expires = new Date().toISOString(); + + await service.trackId( + searchRequest, + searchId, + { sessionId, isStored }, + { savedObjectsClient } + ); + + expect(savedObjectsClient.update).not.toHaveBeenCalled(); + + await service.save( + sessionId, + { name, created, expires, appId, urlGeneratorId }, + { savedObjectsClient } + ); + + expect(savedObjectsClient.create).toHaveBeenCalledWith( + BACKGROUND_SESSION_TYPE, + { + name, + created, + expires, + initialState: {}, + restoreState: {}, + status: BackgroundSessionStatus.IN_PROGRESS, + idMapping: { [requestHash]: searchId }, + appId, + urlGeneratorId, + }, + { id: sessionId } + ); + }); + + it('updates saved object when `isStored` is `true`', async () => { + const searchRequest = { params: {} }; + const requestHash = createRequestHash(searchRequest.params); + const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; + const isStored = true; + + await service.trackId( + searchRequest, + searchId, + { sessionId, isStored }, + { savedObjectsClient } + ); + + expect(savedObjectsClient.update).toHaveBeenCalledWith(BACKGROUND_SESSION_TYPE, sessionId, { + idMapping: { [requestHash]: searchId }, + }); + }); + }); + + describe('getId', () => { + it('throws if `sessionId` is not provided', () => { + const searchRequest = { params: {} }; + + expect(() => + service.getId(searchRequest, {}, { savedObjectsClient }) + ).rejects.toMatchInlineSnapshot(`[Error: Session ID is required]`); + }); + + it('throws if there is not a saved object', () => { + const searchRequest = { params: {} }; + + expect(() => + service.getId(searchRequest, { sessionId, isStored: false }, { savedObjectsClient }) + ).rejects.toMatchInlineSnapshot( + `[Error: Cannot get search ID from a session that is not stored]` + ); + }); + + it('throws if not restoring a saved session', () => { + const searchRequest = { params: {} }; + + expect(() => + service.getId( + searchRequest, + { sessionId, isStored: true, isRestore: false }, + { savedObjectsClient } + ) + ).rejects.toMatchInlineSnapshot( + `[Error: Get search ID is only supported when restoring a session]` + ); + }); + + it('returns the search ID from the saved object ID mapping', async () => { + const searchRequest = { params: {} }; + const requestHash = createRequestHash(searchRequest.params); + const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; + const mockSession = { + id: 'd7170a35-7e2c-48d6-8dec-9a056721b489', + type: BACKGROUND_SESSION_TYPE, + attributes: { + name: 'my_name', + appId: 'my_app_id', + urlGeneratorId: 'my_url_generator_id', + idMapping: { [requestHash]: searchId }, + }, + references: [], + }; + savedObjectsClient.get.mockResolvedValue(mockSession); + + const id = await service.getId( + searchRequest, + { sessionId, isStored: true, isRestore: true }, + { savedObjectsClient } + ); + + expect(id).toBe(searchId); + }); + }); +}); diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts new file mode 100644 index 00000000000000..65a9e0901d738f --- /dev/null +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts @@ -0,0 +1,225 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreStart, KibanaRequest, SavedObjectsClientContract } from 'kibana/server'; +import { from, Observable } from 'rxjs'; +import { switchMap } from 'rxjs/operators'; +import { + IKibanaSearchRequest, + IKibanaSearchResponse, + ISearchOptions, + tapFirst, +} from '../../../../../../src/plugins/data/common'; +import { + ISearchStrategy, + ISessionService, + SearchStrategyDependencies, +} from '../../../../../../src/plugins/data/server'; +import { + BackgroundSessionSavedObjectAttributes, + BackgroundSessionFindOptions, + BackgroundSessionStatus, +} from '../../../common'; +import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; +import { createRequestHash } from './utils'; + +const DEFAULT_EXPIRATION = 7 * 24 * 60 * 60 * 1000; + +export interface BackgroundSessionDependencies { + savedObjectsClient: SavedObjectsClientContract; +} + +export class BackgroundSessionService implements ISessionService { + /** + * Map of sessionId to { [requestHash]: searchId } + * @private + */ + private sessionSearchMap = new Map>(); + + constructor() {} + + public search( + strategy: ISearchStrategy, + searchRequest: Request, + options: ISearchOptions, + searchDeps: SearchStrategyDependencies, + deps: BackgroundSessionDependencies + ): Observable { + // If this is a restored background search session, look up the ID using the provided sessionId + const getSearchRequest = async () => + !options.isRestore || searchRequest.id + ? searchRequest + : { + ...searchRequest, + id: await this.getId(searchRequest, options, deps), + }; + + return from(getSearchRequest()).pipe( + switchMap((request) => strategy.search(request, options, searchDeps)), + tapFirst((response) => { + if (searchRequest.id || !options.sessionId || !response.id || options.isRestore) return; + this.trackId(searchRequest, response.id, options, deps); + }) + ); + } + + // TODO: Generate the `userId` from the realm type/realm name/username + public save = async ( + sessionId: string, + { + name, + appId, + created = new Date().toISOString(), + expires = new Date(Date.now() + DEFAULT_EXPIRATION).toISOString(), + status = BackgroundSessionStatus.IN_PROGRESS, + urlGeneratorId, + initialState = {}, + restoreState = {}, + }: Partial, + { savedObjectsClient }: BackgroundSessionDependencies + ) => { + if (!name) throw new Error('Name is required'); + if (!appId) throw new Error('AppId is required'); + if (!urlGeneratorId) throw new Error('UrlGeneratorId is required'); + + // Get the mapping of request hash/search ID for this session + const searchMap = this.sessionSearchMap.get(sessionId) ?? new Map(); + const idMapping = Object.fromEntries(searchMap.entries()); + const attributes = { + name, + created, + expires, + status, + initialState, + restoreState, + idMapping, + urlGeneratorId, + appId, + }; + const session = await savedObjectsClient.create( + BACKGROUND_SESSION_TYPE, + attributes, + { id: sessionId } + ); + + // Clear out the entries for this session ID so they don't get saved next time + this.sessionSearchMap.delete(sessionId); + + return session; + }; + + // TODO: Throw an error if this session doesn't belong to this user + public get = (sessionId: string, { savedObjectsClient }: BackgroundSessionDependencies) => { + return savedObjectsClient.get( + BACKGROUND_SESSION_TYPE, + sessionId + ); + }; + + // TODO: Throw an error if this session doesn't belong to this user + public find = ( + options: BackgroundSessionFindOptions, + { savedObjectsClient }: BackgroundSessionDependencies + ) => { + return savedObjectsClient.find({ + ...options, + type: BACKGROUND_SESSION_TYPE, + }); + }; + + // TODO: Throw an error if this session doesn't belong to this user + public update = ( + sessionId: string, + attributes: Partial, + { savedObjectsClient }: BackgroundSessionDependencies + ) => { + return savedObjectsClient.update( + BACKGROUND_SESSION_TYPE, + sessionId, + attributes + ); + }; + + // TODO: Throw an error if this session doesn't belong to this user + public delete = (sessionId: string, { savedObjectsClient }: BackgroundSessionDependencies) => { + return savedObjectsClient.delete(BACKGROUND_SESSION_TYPE, sessionId); + }; + + /** + * Tracks the given search request/search ID in the saved session (if it exists). Otherwise, just + * store it in memory until a saved session exists. + * @internal + */ + public trackId = async ( + searchRequest: IKibanaSearchRequest, + searchId: string, + { sessionId, isStored }: ISearchOptions, + deps: BackgroundSessionDependencies + ) => { + if (!sessionId || !searchId) return; + const requestHash = createRequestHash(searchRequest.params); + + // If there is already a saved object for this session, update it to include this request/ID. + // Otherwise, just update the in-memory mapping for this session for when the session is saved. + if (isStored) { + const attributes = { idMapping: { [requestHash]: searchId } }; + await this.update(sessionId, attributes, deps); + } else { + const map = this.sessionSearchMap.get(sessionId) ?? new Map(); + map.set(requestHash, searchId); + this.sessionSearchMap.set(sessionId, map); + } + }; + + /** + * Look up an existing search ID that matches the given request in the given session so that the + * request can continue rather than restart. + * @internal + */ + public getId = async ( + searchRequest: IKibanaSearchRequest, + { sessionId, isStored, isRestore }: ISearchOptions, + deps: BackgroundSessionDependencies + ) => { + if (!sessionId) { + throw new Error('Session ID is required'); + } else if (!isStored) { + throw new Error('Cannot get search ID from a session that is not stored'); + } else if (!isRestore) { + throw new Error('Get search ID is only supported when restoring a session'); + } + + const session = await this.get(sessionId, deps); + const requestHash = createRequestHash(searchRequest.params); + if (!session.attributes.idMapping.hasOwnProperty(requestHash)) { + throw new Error('No search ID in this session matching the given search request'); + } + + return session.attributes.idMapping[requestHash]; + }; + + public asScopedProvider = ({ savedObjects }: CoreStart) => { + return (request: KibanaRequest) => { + const savedObjectsClient = savedObjects.getScopedClient(request, { + includedHiddenTypes: [BACKGROUND_SESSION_TYPE], + }); + const deps = { savedObjectsClient }; + return { + search: ( + strategy: ISearchStrategy, + ...args: Parameters['search']> + ) => this.search(strategy, ...args, deps), + save: (sessionId: string, attributes: Partial) => + this.save(sessionId, attributes, deps), + get: (sessionId: string) => this.get(sessionId, deps), + find: (options: BackgroundSessionFindOptions) => this.find(options, deps), + update: (sessionId: string, attributes: Partial) => + this.update(sessionId, attributes, deps), + delete: (sessionId: string) => this.delete(sessionId, deps), + }; + }; + }; +} diff --git a/x-pack/plugins/data_enhanced/server/search/session/utils.test.ts b/x-pack/plugins/data_enhanced/server/search/session/utils.test.ts new file mode 100644 index 00000000000000..aae1819a9c3e18 --- /dev/null +++ b/x-pack/plugins/data_enhanced/server/search/session/utils.test.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { createRequestHash } from './utils'; + +describe('data/search/session utils', () => { + describe('createRequestHash', () => { + it('ignores `preference`', () => { + const request = { + foo: 'bar', + }; + + const withPreference = { + ...request, + preference: 1234, + }; + + expect(createRequestHash(request)).toEqual(createRequestHash(withPreference)); + }); + }); +}); diff --git a/x-pack/plugins/data_enhanced/server/search/session/utils.ts b/x-pack/plugins/data_enhanced/server/search/session/utils.ts new file mode 100644 index 00000000000000..1c314c64f9be3c --- /dev/null +++ b/x-pack/plugins/data_enhanced/server/search/session/utils.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { createHash } from 'crypto'; + +/** + * Generate the hash for this request so that, in the future, this hash can be used to look up + * existing search IDs for this request. Ignores the `preference` parameter since it generally won't + * match from one request to another identical request. + */ +export function createRequestHash(keys: Record) { + const { preference, ...params } = keys; + return createHash(`sha256`).update(JSON.stringify(params)).digest('hex'); +} From 93128a1d8bf19c69f53c68e205643795aa1f80cd Mon Sep 17 00:00:00 2001 From: Liza K Date: Thu, 3 Dec 2020 21:34:05 +0200 Subject: [PATCH 14/58] Better logs Save IDs only in monitoring loop --- .../server/search/session/session_service.ts | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/plugins/data/server/search/session/session_service.ts b/src/plugins/data/server/search/session/session_service.ts index a70a6dc0780aa6..92defc89b41b8f 100644 --- a/src/plugins/data/server/search/session/session_service.ts +++ b/src/plugins/data/server/search/session/session_service.ts @@ -69,7 +69,7 @@ export class BackgroundSessionService { private setupMonitoring = (savedObjects: SavedObjectsServiceStart) => { // TODO: setup monitoring only if BGS is enabled - const internalRepo = savedObjects.createInternalRepository(); + const internalRepo = savedObjects.createInternalRepository([BACKGROUND_SESSION_TYPE]); this.internalSavedObjectsClient = new SavedObjectsClient(internalRepo); this.monitorInterval = setInterval(this.monitorMappedIds.bind(this), INMEM_TRACKING_INTERVAL); }; @@ -102,10 +102,10 @@ export class BackgroundSessionService { moment.duration(curTime.diff(sessionInfo.insertTime)).asSeconds() > INMEM_TRACKING_TIMEOUT_SEC ) { - this.logger.debug(`Deleting expired session ${sessionId}`); + this.logger.debug(`clearSessions | Deleting expired session ${sessionId}`); this.sessionSearchMap.delete(sessionId); } else if (sessionInfo.retryCount >= MAX_UPDATE_RETRIES) { - this.logger.warn(`Deleting failed session ${sessionId}`); + this.logger.warn(`clearSessions | Deleting failed session ${sessionId}`); this.sessionSearchMap.delete(sessionId); } }); @@ -113,7 +113,7 @@ export class BackgroundSessionService { private async monitorMappedIds() { try { - this.logger.debug(`monitorMappedIds. Map contains ${this.sessionSearchMap.size} items`); + this.logger.debug(`monitorMappedIds | Map contains ${this.sessionSearchMap.size} items`); this.clearSessions(); if (!this.sessionSearchMap.size) return; @@ -135,7 +135,7 @@ export class BackgroundSessionService { } }); } catch (e) { - this.logger.error(`Error fetching sessions. ${e}`); + this.logger.error(`monitorMappedIds | Error fetching sessions. ${e}`); } } @@ -144,6 +144,7 @@ export class BackgroundSessionService { ) { if (!activeMappingObjects.length) return []; + this.logger.debug(`updateAllSavedObjects | Updating ${activeMappingObjects.length} items`); const updatedSessions = activeMappingObjects ?.filter((so) => !so.error) .map((sessionSavedObject) => { @@ -195,9 +196,8 @@ export class BackgroundSessionService { if (!appId) throw new Error('AppId is required'); if (!urlGeneratorId) throw new Error('UrlGeneratorId is required'); - // Get the mapping of request hash/search ID for this session - const searchMap = this.sessionSearchMap.get(sessionId); - const idMapping = searchMap ? Object.fromEntries(searchMap.ids.entries()) : {}; + this.logger.debug(`save. Saving ${sessionId}.`); + const attributes = { name, created, @@ -205,7 +205,7 @@ export class BackgroundSessionService { status, initialState, restoreState, - idMapping, + idMapping: {}, urlGeneratorId, appId, }; @@ -215,9 +215,6 @@ export class BackgroundSessionService { { id: sessionId } ); - // Clear out the entries for this session ID so they don't get saved next time - this.sessionSearchMap.delete(sessionId); - return session; }; @@ -270,6 +267,7 @@ export class BackgroundSessionService { deps: BackgroundSessionDependencies ) => { if (!sessionId || !searchId) return; + this.logger.debug(`trackId | Tracking ${sessionId} sid ${searchId}`); const requestHash = createRequestHash(searchRequest.params); // If there is already a saved object for this session, update it to include this request/ID. From ea14d295cec2c85dbf2a83355327aff05209a538 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Thu, 3 Dec 2020 14:23:28 -0700 Subject: [PATCH 15/58] Fix types --- src/plugins/data/server/search/mocks.ts | 15 ----------- .../data_enhanced/server/routes/mocks.ts | 26 +++++++++++++++++++ .../server/routes/session.test.ts | 6 ++--- 3 files changed, 29 insertions(+), 18 deletions(-) create mode 100644 x-pack/plugins/data_enhanced/server/routes/mocks.ts diff --git a/src/plugins/data/server/search/mocks.ts b/src/plugins/data/server/search/mocks.ts index 6d214008e8b173..4914726c85ef8b 100644 --- a/src/plugins/data/server/search/mocks.ts +++ b/src/plugins/data/server/search/mocks.ts @@ -17,8 +17,6 @@ * under the License. */ -import type { RequestHandlerContext } from 'src/core/server'; -import { coreMock } from '../../../../core/server/mocks'; import { ISearchSetup, ISearchStart } from './types'; import { searchAggsSetupMock, searchAggsStartMock } from './aggs/mocks'; import { searchSourceMock } from './search_source/mocks'; @@ -42,16 +40,3 @@ export function createSearchStartMock(): jest.Mocked { searchSource: searchSourceMock.createStartContract(), }; } - -export function createSearchRequestHandlerContext(): jest.Mocked { - return { - core: coreMock.createRequestHandlerContext(), - search: { - search: jest.fn(), - cancel: jest.fn(), - session: { - search: jest.fn(), - }, - }, - }; -} diff --git a/x-pack/plugins/data_enhanced/server/routes/mocks.ts b/x-pack/plugins/data_enhanced/server/routes/mocks.ts new file mode 100644 index 00000000000000..3e7b89ed2cca6a --- /dev/null +++ b/x-pack/plugins/data_enhanced/server/routes/mocks.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RequestHandlerContext } from 'kibana/server'; +import { coreMock } from '../../../../../src/core/server/mocks'; + +export function createSearchRequestHandlerContext() { + return ({ + core: coreMock.createRequestHandlerContext(), + search: { + search: jest.fn(), + cancel: jest.fn(), + session: { + search: jest.fn(), + save: jest.fn(), + get: jest.fn(), + find: jest.fn(), + delete: jest.fn(), + update: jest.fn(), + }, + }, + } as unknown) as jest.Mocked; +} diff --git a/x-pack/plugins/data_enhanced/server/routes/session.test.ts b/x-pack/plugins/data_enhanced/server/routes/session.test.ts index 7342e5cda4e0c1..313dfb1e0f1f07 100644 --- a/x-pack/plugins/data_enhanced/server/routes/session.test.ts +++ b/x-pack/plugins/data_enhanced/server/routes/session.test.ts @@ -6,10 +6,10 @@ import type { MockedKeys } from '@kbn/utility-types/jest'; import type { CoreSetup, RequestHandlerContext } from 'kibana/server'; -import { coreMock, httpServerMock } from 'src/core/server/mocks'; -import { registerSessionRoutes } from './session'; +import { coreMock, httpServerMock } from '../../../../../src/core/server/mocks'; import { PluginStart as DataPluginStart } from '../../../../../src/plugins/data/server'; -import { createSearchRequestHandlerContext } from '../../../../../src/plugins/data/server/search/mocks'; +import { createSearchRequestHandlerContext } from './mocks'; +import { registerSessionRoutes } from './session'; describe('registerSessionRoutes', () => { let mockCoreSetup: MockedKeys>; From a0e2142557274b281a807c43cdf4c63096a09095 Mon Sep 17 00:00:00 2001 From: Liza K Date: Sun, 6 Dec 2020 13:15:40 +0200 Subject: [PATCH 16/58] Space aware session service --- .../saved_objects/background_session.ts | 3 + .../search/session/session_service.test.ts | 59 +++++++++++-------- .../server/search/session/session_service.ts | 27 +++++---- 3 files changed, 56 insertions(+), 33 deletions(-) diff --git a/src/plugins/data/server/saved_objects/background_session.ts b/src/plugins/data/server/saved_objects/background_session.ts index e81272628c091a..82c3d754f7f717 100644 --- a/src/plugins/data/server/saved_objects/background_session.ts +++ b/src/plugins/data/server/saved_objects/background_session.ts @@ -27,6 +27,9 @@ export const backgroundSessionMapping: SavedObjectsType = { hidden: true, mappings: { properties: { + sessionId: { + type: 'keyword', + }, name: { type: 'keyword', }, diff --git a/src/plugins/data/server/search/session/session_service.test.ts b/src/plugins/data/server/search/session/session_service.test.ts index 064d37bcaf2f55..1d8e963a7c0da3 100644 --- a/src/plugins/data/server/search/session/session_service.test.ts +++ b/src/plugins/data/server/search/session/session_service.test.ts @@ -41,13 +41,13 @@ describe('BackgroundSessionService', () => { const MOCK_KEY_HASH = '608de49a4600dbb5b173492759792e4a'; const createMockInternalSavedObjectClient = ( - bulkGetSpy?: jest.SpyInstance, + findSpy?: jest.SpyInstance, bulkUpdateSpy?: jest.SpyInstance ) => { Object.defineProperty(service, 'internalSavedObjectsClient', { get: () => { - const bulkGet = - bulkGetSpy || + const find = + findSpy || (() => { return { saved_objects: [ @@ -73,7 +73,7 @@ describe('BackgroundSessionService', () => { }; }); return { - bulkGet, + find, bulkUpdate, }; }, @@ -199,6 +199,11 @@ describe('BackgroundSessionService', () => { const created = new Date().toISOString(); const expires = new Date().toISOString(); + const mockIdMapping = createMockIdMapping([]); + Object.defineProperty(service, 'sessionSearchMap', { + get: () => mockIdMapping, + }); + await service.trackId( searchRequest, searchId, @@ -223,12 +228,15 @@ describe('BackgroundSessionService', () => { initialState: {}, restoreState: {}, status: BackgroundSessionStatus.IN_PROGRESS, - idMapping: { [requestHash]: searchId }, + idMapping: {}, appId, urlGeneratorId, + sessionId, }, { id: sessionId } ); + + expect(mockIdMapping.get(sessionId)!.ids.get(requestHash)).toBe(searchId); }); it('updates saved object when `isStored` is `true`', async () => { @@ -368,7 +376,7 @@ describe('BackgroundSessionService', () => { expect(deleteSpy).toHaveBeenCalledTimes(1); }); - it('should bot fetch when no IDs are mapped', async () => { + it('should not fetch when no IDs are mapped', async () => { const bulkGetSpy = jest.fn().mockResolvedValueOnce({ saved_objects: [] }); createMockInternalSavedObjectClient(bulkGetSpy); @@ -382,24 +390,26 @@ describe('BackgroundSessionService', () => { get: () => mockIdMapping, }); - const bulkGetSpy = jest.fn().mockResolvedValueOnce({ saved_objects: [] }); + const findSpy = jest.fn().mockResolvedValueOnce({ saved_objects: [] }); const bulkUpdateSpy = jest.fn().mockResolvedValueOnce({ saved_objects: [] }); - createMockInternalSavedObjectClient(bulkGetSpy, bulkUpdateSpy); + createMockInternalSavedObjectClient(findSpy, bulkUpdateSpy); jest.advanceTimersByTime(INMEM_TRACKING_INTERVAL); - expect(bulkGetSpy).toHaveBeenCalledTimes(1); + expect(findSpy).toHaveBeenCalledTimes(1); expect(bulkUpdateSpy).not.toHaveBeenCalled(); }); - it('should update saved objects if they are found, and delete ids on success', async () => { + it('should update saved objects if they are found, and delete session on success', async () => { const mockIdMapping = createMockIdMapping([[MOCK_KEY_HASH, MOCK_ASYNC_ID]], undefined, 1); const mockMapDeleteSpy = jest.fn(); - mockIdMapping.get(MOCK_SESSION_ID)!.ids.delete = mockMapDeleteSpy; + const mockSessionDeleteSpy = jest.fn(); + mockIdMapping.delete = mockMapDeleteSpy; + mockIdMapping.get(MOCK_SESSION_ID)!.ids.delete = mockSessionDeleteSpy; Object.defineProperty(service, 'sessionSearchMap', { get: () => mockIdMapping, }); - const bulkGetSpy = jest.fn().mockResolvedValueOnce({ + const findSpy = jest.fn().mockResolvedValueOnce({ saved_objects: [ { id: MOCK_SESSION_ID, @@ -424,7 +434,7 @@ describe('BackgroundSessionService', () => { }, ], }); - createMockInternalSavedObjectClient(bulkGetSpy, bulkUpdateSpy); + createMockInternalSavedObjectClient(findSpy, bulkUpdateSpy); jest.advanceTimersByTime(INMEM_TRACKING_INTERVAL); @@ -432,23 +442,25 @@ describe('BackgroundSessionService', () => { jest.useRealTimers(); await new Promise((r) => setTimeout(r, 15)); - expect(bulkGetSpy).toHaveBeenCalledTimes(1); + expect(findSpy).toHaveBeenCalledTimes(1); expect(bulkUpdateSpy).toHaveBeenCalledTimes(1); - expect(mockMapDeleteSpy).toHaveBeenCalledTimes(2); - expect(mockMapDeleteSpy).toBeCalledWith('b'); - expect(mockMapDeleteSpy).toBeCalledWith(MOCK_KEY_HASH); - expect(mockIdMapping.get(MOCK_SESSION_ID)?.retryCount).toBe(0); + expect(mockSessionDeleteSpy).toHaveBeenCalledTimes(2); + expect(mockSessionDeleteSpy).toBeCalledWith('b'); + expect(mockSessionDeleteSpy).toBeCalledWith(MOCK_KEY_HASH); + expect(mockMapDeleteSpy).toBeCalledTimes(1); }); it('should update saved objects if they are found, and increase retryCount on error', async () => { const mockIdMapping = createMockIdMapping([[MOCK_KEY_HASH, MOCK_ASYNC_ID]]); const mockMapDeleteSpy = jest.fn(); - mockIdMapping.get(MOCK_SESSION_ID)!.ids.delete = mockMapDeleteSpy; + const mockSessionDeleteSpy = jest.fn(); + mockIdMapping.delete = mockMapDeleteSpy; + mockIdMapping.get(MOCK_SESSION_ID)!.ids.delete = mockSessionDeleteSpy; Object.defineProperty(service, 'sessionSearchMap', { get: () => mockIdMapping, }); - const bulkGetSpy = jest.fn().mockResolvedValueOnce({ + const findSpy = jest.fn().mockResolvedValueOnce({ saved_objects: [ { id: MOCK_SESSION_ID, @@ -468,7 +480,7 @@ describe('BackgroundSessionService', () => { }, ], }); - createMockInternalSavedObjectClient(bulkGetSpy, bulkUpdateSpy); + createMockInternalSavedObjectClient(findSpy, bulkUpdateSpy); jest.advanceTimersByTime(INMEM_TRACKING_INTERVAL); @@ -476,10 +488,11 @@ describe('BackgroundSessionService', () => { jest.useRealTimers(); await new Promise((r) => setTimeout(r, 15)); - expect(bulkGetSpy).toHaveBeenCalledTimes(1); + expect(findSpy).toHaveBeenCalledTimes(1); expect(bulkUpdateSpy).toHaveBeenCalledTimes(1); + expect(mockSessionDeleteSpy).not.toHaveBeenCalled(); expect(mockMapDeleteSpy).not.toHaveBeenCalled(); - expect(mockIdMapping.get(MOCK_SESSION_ID)?.retryCount).toBe(1); + expect(mockIdMapping.get(MOCK_SESSION_ID)!.retryCount).toBe(1); }); }); }); diff --git a/src/plugins/data/server/search/session/session_service.ts b/src/plugins/data/server/search/session/session_service.ts index 92defc89b41b8f..27db6a4a1c2b54 100644 --- a/src/plugins/data/server/search/session/session_service.ts +++ b/src/plugins/data/server/search/session/session_service.ts @@ -83,15 +83,16 @@ export class BackgroundSessionService { * context of a user's session. */ private async getAllMappedSavedObjects() { - const activeMappingIds = Array.from(this.sessionSearchMap.keys()).map((sessionId) => { - return { - id: sessionId, - type: BACKGROUND_SESSION_TYPE, - }; + const activeMappingIds = Array.from(this.sessionSearchMap.keys()) + .map((sessionId) => `"${sessionId}"`) + .join(' | '); + const res = await this.internalSavedObjectsClient.find({ + type: BACKGROUND_SESSION_TYPE, + search: activeMappingIds, + searchFields: ['sessionId'], + namespaces: ['*'], }); - const res = await this.internalSavedObjectsClient.bulkGet( - activeMappingIds - ); + this.logger.debug(`getAllMappedSavedObjects | Got ${res.saved_objects.length} items`); return res.saved_objects; } @@ -127,11 +128,16 @@ export class BackgroundSessionService { // Retry next time sessionInfo.retryCount++; } else if (updatedSavedObject.attributes.idMapping) { - // Delete the ids that we just saved, avoiding a potential new ids being lost (?) + // Delete the ids that we just saved, avoiding a potential new ids being lost. Object.keys(updatedSavedObject.attributes.idMapping).forEach((key) => { sessionInfo.ids.delete(key); }); - sessionInfo.retryCount = 0; + // If the session object is empty, delete it as well + if (!sessionInfo.ids.entries.length) { + this.sessionSearchMap.delete(updatedSavedObject.id); + } else { + sessionInfo.retryCount = 0; + } } }); } catch (e) { @@ -199,6 +205,7 @@ export class BackgroundSessionService { this.logger.debug(`save. Saving ${sessionId}.`); const attributes = { + sessionId, name, created, expires, From 149d94e6628362936f181d5468cc6c013cb94cdd Mon Sep 17 00:00:00 2001 From: Liza K Date: Sun, 6 Dec 2020 17:11:07 +0200 Subject: [PATCH 17/58] ts --- src/plugins/kibana_usage_collection/server/plugin.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/kibana_usage_collection/server/plugin.ts b/src/plugins/kibana_usage_collection/server/plugin.ts index dbd6aaa566ff99..94e13b9a43cc8e 100644 --- a/src/plugins/kibana_usage_collection/server/plugin.ts +++ b/src/plugins/kibana_usage_collection/server/plugin.ts @@ -46,7 +46,6 @@ import { registerUiCounterSavedObjectType, registerUiCountersRollups, } from './collectors'; -import { ConfigSchema } from '../config'; interface KibanaUsageCollectionPluginsDepsSetup { usageCollection: UsageCollectionSetup; From 1fc434a9141d3be82ee60fa23129c94e5c3fff16 Mon Sep 17 00:00:00 2001 From: Liza K Date: Sun, 6 Dec 2020 18:17:50 +0200 Subject: [PATCH 18/58] initial --- x-pack/plugins/data_enhanced/kibana.json | 3 ++- x-pack/plugins/data_enhanced/server/plugin.ts | 16 ++++++++++++++-- .../data_enhanced/server/search/session/index.ts | 1 + .../server/search/session/session_service.ts | 1 + 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/data_enhanced/kibana.json b/x-pack/plugins/data_enhanced/kibana.json index eea0101ec4ed78..3951468f6e569d 100644 --- a/x-pack/plugins/data_enhanced/kibana.json +++ b/x-pack/plugins/data_enhanced/kibana.json @@ -8,7 +8,8 @@ "requiredPlugins": [ "bfetch", "data", - "features" + "features", + "taskManager" ], "optionalPlugins": ["kibanaUtils", "usageCollection"], "server": true, diff --git a/x-pack/plugins/data_enhanced/server/plugin.ts b/x-pack/plugins/data_enhanced/server/plugin.ts index 956568fbe7632d..8664ab162a2cda 100644 --- a/x-pack/plugins/data_enhanced/server/plugin.ts +++ b/x-pack/plugins/data_enhanced/server/plugin.ts @@ -5,6 +5,7 @@ */ import { CoreSetup, CoreStart, Logger, Plugin, PluginInitializerContext } from 'kibana/server'; +import { TaskManagerSetupContract, TaskManagerStartContract } from 'x-pack/plugins/task_manager/server'; import { PluginSetup as DataPluginSetup, PluginStart as DataPluginStart, @@ -19,15 +20,21 @@ import { enhancedEsSearchStrategyProvider, eqlSearchStrategyProvider, } from './search'; +import { registerBackgroundSessionsTask, scheduleBackgroundSessionsTasks } from './search/session'; import { getUiSettings } from './ui_settings'; interface SetupDependencies { data: DataPluginSetup; usageCollection?: UsageCollectionSetup; + taskManager: TaskManagerSetupContract; +} +interface StartDependencies { + taskManager: TaskManagerStartContract; } export class EnhancedDataServerPlugin implements Plugin { private readonly logger: Logger; + private sessionService!: BackgroundSessionService; constructor(private initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get('data_enhanced'); @@ -53,18 +60,23 @@ export class EnhancedDataServerPlugin implements Plugin Date: Sun, 6 Dec 2020 18:43:34 +0200 Subject: [PATCH 19/58] initial --- .../server/search/session/check_sessions.ts | 51 +++++++++++ .../server/search/session/monitoring_task.ts | 85 +++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 x-pack/plugins/data_enhanced/server/search/session/check_sessions.ts create mode 100644 x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_sessions.ts b/x-pack/plugins/data_enhanced/server/search/session/check_sessions.ts new file mode 100644 index 00000000000000..ebe52e03fc066c --- /dev/null +++ b/x-pack/plugins/data_enhanced/server/search/session/check_sessions.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObjectsClient, Logger, ElasticsearchClient } from 'kibana/server'; +import { BackgroundSessionStatus, BackgroundSessionSavedObjectAttributes } from '../../../common'; +import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; + +async function checkAsyncId(client: ElasticsearchClient, logger: Logger, asyncId: string) { + try { + client.asyncSearch. + const method = 'GET'; + const path = encodeURI(`/_async_search/status/${asyncId}`); + const response = await client.transport('transport.request', { method, path }, {}); + + logger.debug( + `${asyncId}, ${response.id}, partial? ${response.is_partial}, running? ${response.is_running}` + ); + } catch (e) { + logger.error(e); + } +} + +export async function checkBackgoundSessions( + savedObjectsClient: SavedObjectsClient, + logger: Logger, + client: ElasticsearchClient +): Promise { + try { + const runningBackgroundSearchesResponse = await savedObjectsClient.find({ + type: BACKGROUND_SESSION_TYPE, + search: BackgroundSessionStatus.IN_PROGRESS.toString(), + searchFields: ['status'], + namespaces: ['*'], + }); + + runningBackgroundSearchesResponse.saved_objects.map((backgroundSearch) => { + const searchIds = Object.keys(backgroundSearch.attributes.idMapping); + searchIds.map((searchId: string) => { + checkAsyncId(client, logger, searchId); + logger.debug(searchId); + }); + }); + + logger.debug(`Got ${runningBackgroundSearchesResponse.total} running background sessios`); + } catch (err) { + return; + } +} \ No newline at end of file diff --git a/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts b/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts new file mode 100644 index 00000000000000..a7c0cf566f2736 --- /dev/null +++ b/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + TaskManagerSetupContract, + TaskManagerStartContract, + RunContext, + } from '../../../../task_manager/server'; + import { checkBackgoundSessions } from './check_sessions'; + import { CoreSetup, SavedObjectsClient, Logger } from '../../../../../../src/core/server'; +import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; + + export const BACKGROUND_SESSIONS_TASK_TYPE = 'bg_monitor'; + export const BACKGROUND_SESSIONS_TASK_ID = `data_enhanced_${BACKGROUND_SESSIONS_TASK_TYPE}`; + export const MONITOR_INTERVAL = `15s`; + + function backgroundSessionRunner(core: CoreSetup, logger: Logger) { + logger.debug(`Registering background search task`); + return ({ taskInstance }: RunContext) => { + return { + async run() { + logger.debug('Background sessions task started'); + const [coreStart] = await core.getStartServices(); + const internalRepo = coreStart.savedObjects.createInternalRepository([BACKGROUND_SESSION_TYPE]); + const internalSavedObjectsClient = new SavedObjectsClient(internalRepo); + await checkBackgoundSessions( + internalSavedObjectsClient, + logger, + coreStart.elasticsearch.client.asInternalUser + ); + + return { + runAt: new Date(Date.now() + 10 * 1000), + state: {}, + }; + }, + }; + }; + } + + export function registerBackgroundSessionsTask( + core: CoreSetup, + taskManager: TaskManagerSetupContract, + logger: Logger + ) { + taskManager.registerTaskDefinitions({ + [BACKGROUND_SESSIONS_TASK_TYPE]: { + title: 'Background Session Monitor', + createTaskRunner: backgroundSessionRunner(core, logger), + }, + }); + } + + export async function scheduleBackgroundSessionsTasks( + taskManager: TaskManagerStartContract, + logger: Logger + ) { + try { + // delete previous task + const deleteResult = await taskManager.remove(BACKGROUND_SESSIONS_TASK_ID); + logger.debug(JSON.stringify(deleteResult, null, 2)); + } catch (e) { + // eslint-disable-line no-empty + } + + try { + const result = await taskManager.ensureScheduled({ + id: BACKGROUND_SESSIONS_TASK_ID, + taskType: BACKGROUND_SESSIONS_TASK_TYPE, + schedule: { + interval: MONITOR_INTERVAL, + }, + state: {}, + params: {}, + }); + + logger.debug(JSON.stringify(result, null, 2)); + logger.debug(`Background search task, scheduled to run`); + } catch (e) { + logger.debug(`Error scheduling task, received ${e.message}`); + } + } \ No newline at end of file From 0b875ac08efc49a76c013555e13398d342bbe916 Mon Sep 17 00:00:00 2001 From: Liza K Date: Mon, 7 Dec 2020 14:22:49 +0200 Subject: [PATCH 20/58] Fix session service saving --- .../server/search/session/session_service.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts index 20bf76551f0944..e2869f2bdaf088 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts @@ -219,6 +219,8 @@ export class BackgroundSessionService implements ISessionService { if (!appId) throw new Error('AppId is required'); if (!urlGeneratorId) throw new Error('UrlGeneratorId is required'); + this.logger.debug(`save | ${sessionId}`); + const attributes = { name, created, @@ -237,14 +239,12 @@ export class BackgroundSessionService implements ISessionService { { id: sessionId } ); - // Clear out the entries for this session ID so they don't get saved next time - this.sessionSearchMap.delete(sessionId); - return session; }; // TODO: Throw an error if this session doesn't belong to this user public get = (sessionId: string, { savedObjectsClient }: BackgroundSessionDependencies) => { + this.logger.debug(`get | ${sessionId}`); return savedObjectsClient.get( BACKGROUND_SESSION_TYPE, sessionId @@ -268,6 +268,7 @@ export class BackgroundSessionService implements ISessionService { attributes: Partial, { savedObjectsClient }: BackgroundSessionDependencies ) => { + this.logger.debug(`update | ${sessionId}`); return savedObjectsClient.update( BACKGROUND_SESSION_TYPE, sessionId, @@ -292,6 +293,7 @@ export class BackgroundSessionService implements ISessionService { deps: BackgroundSessionDependencies ) => { if (!sessionId || !searchId) return; + this.logger.debug(`trackId | ${sessionId} | ${searchId}`); const requestHash = createRequestHash(searchRequest.params); // If there is already a saved object for this session, update it to include this request/ID. From b5b089530db9a17f100bc17f70072ac822583005 Mon Sep 17 00:00:00 2001 From: Liza K Date: Mon, 7 Dec 2020 20:13:00 +0200 Subject: [PATCH 21/58] merge fix --- .../data_enhanced/server/search/session/session_service.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts index a5a7bbf14046c0..98f19d3557de36 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts @@ -222,8 +222,6 @@ export class BackgroundSessionService implements ISessionService { this.logger.debug(`save | ${sessionId}`); // Get the mapping of request hash/search ID for this session - const searchMap = this.sessionSearchMap.get(sessionId) ?? new Map(); - const idMapping = Object.fromEntries(searchMap.entries()); const attributes = { name, created, From f14f042e42d695d5e0b484de06cf96a38ff3c88f Mon Sep 17 00:00:00 2001 From: Liza K Date: Mon, 7 Dec 2020 20:16:02 +0200 Subject: [PATCH 22/58] stable stringify --- x-pack/plugins/data_enhanced/server/search/session/utils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/data_enhanced/server/search/session/utils.ts b/x-pack/plugins/data_enhanced/server/search/session/utils.ts index 1c314c64f9be3c..beaecc5a839d31 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/utils.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/utils.ts @@ -5,6 +5,7 @@ */ import { createHash } from 'crypto'; +import stringify from 'json-stable-stringify'; /** * Generate the hash for this request so that, in the future, this hash can be used to look up @@ -13,5 +14,5 @@ import { createHash } from 'crypto'; */ export function createRequestHash(keys: Record) { const { preference, ...params } = keys; - return createHash(`sha256`).update(JSON.stringify(params)).digest('hex'); + return createHash(`sha256`).update(stringify(params)).digest('hex'); } From 5b9782d444bc55531c27da73879fea85a87daefb Mon Sep 17 00:00:00 2001 From: Liza K Date: Mon, 7 Dec 2020 20:34:30 +0200 Subject: [PATCH 23/58] INMEM_MAX_SESSIONS --- .../server/search/session/session_service.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts index 98f19d3557de36..b7aaca8adfbfd0 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts @@ -5,6 +5,7 @@ */ import moment, { Moment } from 'moment'; +import { orderBy } from 'lodash'; import { from, Observable } from 'rxjs'; import { first, switchMap } from 'rxjs/operators'; import { @@ -35,6 +36,7 @@ import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; import { createRequestHash } from './utils'; import { ConfigSchema } from '../../../config'; +const INMEM_MAX_SESSIONS = 10000; const DEFAULT_EXPIRATION = 7 * 24 * 60 * 60 * 1000; export const INMEM_TRACKING_INTERVAL = 10 * 1000; export const INMEM_TRACKING_TIMEOUT_SEC = 60; @@ -93,6 +95,7 @@ export class BackgroundSessionService implements ISessionService { .map((sessionId) => `"${sessionId}"`) .join(' | '); const res = await this.internalSavedObjectsClient.find({ + perPage: INMEM_MAX_SESSIONS, type: BACKGROUND_SESSION_TYPE, search: activeMappingIds, searchFields: ['sessionId'], @@ -104,6 +107,27 @@ export class BackgroundSessionService implements ISessionService { private clearSessions = () => { const curTime = moment(); + + // Drop old items if map size exceeds max. + if (this.sessionSearchMap.size > INMEM_MAX_SESSIONS) { + const sortedSessionIds = orderBy( + Array.from(this.sessionSearchMap.keys()).map((sessionId) => { + return { + sessionId, + insertTime: this.sessionSearchMap.get(sessionId)!.insertTime, + }; + }), + ['insertTime'], + ['asc'] + ); + + while (this.sessionSearchMap.size > INMEM_MAX_SESSIONS) { + const { sessionId } = sortedSessionIds.shift()!; + this.logger.warn(`clearSessions | Map full | Dropping ${sessionId}`); + this.sessionSearchMap.delete(sessionId); + } + } + this.sessionSearchMap.forEach((sessionInfo, sessionId) => { if ( moment.duration(curTime.diff(sessionInfo.insertTime)).asSeconds() > From 0858b98deefaecb460392d76f8d84c3af0c306ce Mon Sep 17 00:00:00 2001 From: Liza K Date: Mon, 7 Dec 2020 20:36:03 +0200 Subject: [PATCH 24/58] INMEM_MAX_SESSIONS --- .../server/search/session/session_service.ts | 23 +------------------ 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts index b7aaca8adfbfd0..8ec41bd703d97b 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts @@ -5,7 +5,6 @@ */ import moment, { Moment } from 'moment'; -import { orderBy } from 'lodash'; import { from, Observable } from 'rxjs'; import { first, switchMap } from 'rxjs/operators'; import { @@ -95,7 +94,7 @@ export class BackgroundSessionService implements ISessionService { .map((sessionId) => `"${sessionId}"`) .join(' | '); const res = await this.internalSavedObjectsClient.find({ - perPage: INMEM_MAX_SESSIONS, + perPage: INMEM_MAX_SESSIONS, // If there are more sessions in memory, they will be synced when some items are cleared out. type: BACKGROUND_SESSION_TYPE, search: activeMappingIds, searchFields: ['sessionId'], @@ -108,26 +107,6 @@ export class BackgroundSessionService implements ISessionService { private clearSessions = () => { const curTime = moment(); - // Drop old items if map size exceeds max. - if (this.sessionSearchMap.size > INMEM_MAX_SESSIONS) { - const sortedSessionIds = orderBy( - Array.from(this.sessionSearchMap.keys()).map((sessionId) => { - return { - sessionId, - insertTime: this.sessionSearchMap.get(sessionId)!.insertTime, - }; - }), - ['insertTime'], - ['asc'] - ); - - while (this.sessionSearchMap.size > INMEM_MAX_SESSIONS) { - const { sessionId } = sortedSessionIds.shift()!; - this.logger.warn(`clearSessions | Map full | Dropping ${sessionId}`); - this.sessionSearchMap.delete(sessionId); - } - } - this.sessionSearchMap.forEach((sessionInfo, sessionId) => { if ( moment.duration(curTime.diff(sessionInfo.insertTime)).asSeconds() > From cf0b6afad1b9a055d0ede49a60e03c95e3f1ef48 Mon Sep 17 00:00:00 2001 From: Liza K Date: Mon, 7 Dec 2020 21:32:28 +0200 Subject: [PATCH 25/58] use the status API --- x-pack/plugins/data_enhanced/server/plugin.ts | 4 +- .../server/search/session/check_sessions.ts | 50 +++--- .../server/search/session/monitoring_task.ts | 142 +++++++++--------- 3 files changed, 96 insertions(+), 100 deletions(-) diff --git a/x-pack/plugins/data_enhanced/server/plugin.ts b/x-pack/plugins/data_enhanced/server/plugin.ts index 8dc15a72129439..79264f707cc257 100644 --- a/x-pack/plugins/data_enhanced/server/plugin.ts +++ b/x-pack/plugins/data_enhanced/server/plugin.ts @@ -71,11 +71,11 @@ export class EnhancedDataServerPlugin implements Plugin = await client.transport.request({ path, method: 'GET', @@ -40,13 +40,11 @@ async function checkAsyncId(client: ElasticsearchClient, logger: Logger, asyncId return SearchStatus.ERROR; } } catch (e) { - logger.error(e); return SearchStatus.ERROR; } } async function getSessionStatus( - session: SavedObjectsFindResult, searchStatuses: SearchStatus[] ): Promise { if (searchStatuses.some((item) => item === SearchStatus.ERROR)) { @@ -54,7 +52,7 @@ async function getSessionStatus( } else if (searchStatuses.every((item) => item === SearchStatus.COMPLETE)) { return BackgroundSessionStatus.COMPLETE; } else { - // Strill running + // Still running return undefined; } } @@ -74,36 +72,38 @@ export async function checkBackgoundSessions( } ); - logger.debug(`Found ${runningBackgroundSearchesResponse.total} running background sessios`); + logger.debug(`Found ${runningBackgroundSearchesResponse.total} running sessios`); const updatedSessions = new Array< SavedObjectsFindResult >(); - await runningBackgroundSearchesResponse.saved_objects.map(async (session) => { - const searchIds = Object.values(session.attributes.idMapping); - const searchStatuses = await Promise.all( - searchIds.map(async (searchId: string) => { - return await checkAsyncId(client, logger, searchId); - }) - ); - - const sessionStatus = await getSessionStatus(session, searchStatuses); - if (sessionStatus) { - session.attributes.status = sessionStatus; - updatedSessions.push(session); - } - }); + await Promise.all( + runningBackgroundSearchesResponse.saved_objects.map(async (session) => { + const searchIds = Object.values(session.attributes.idMapping); + const searchStatuses = await Promise.all( + searchIds.map(async (searchId: string) => { + return await checkAsyncId(client, logger, searchId); + }) + ); - // If there's an error, we'll try again in the next iteration - const updatedResponse = await savedObjectsClient.bulkUpdate( - updatedSessions.filter((item) => item !== undefined) as Array< - SavedObjectsFindResult - > + const sessionStatus = await getSessionStatus(searchStatuses); + if (sessionStatus) { + session.attributes.status = sessionStatus; + updatedSessions.push(session); + } + }) ); - logger.debug(`Updated ${updatedResponse.saved_objects.length} background sessios`); + if (updatedSessions.length) { + // If there's an error, we'll try again in the next iteration + const updatedResponse = await savedObjectsClient.bulkUpdate( + updatedSessions + ); + logger.debug(`Updated ${updatedResponse.saved_objects.length} background sessios`); + } } catch (err) { + logger.error(err); return; } } diff --git a/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts b/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts index a7c0cf566f2736..7d3d3b713f7c5b 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts @@ -5,81 +5,77 @@ */ import { - TaskManagerSetupContract, - TaskManagerStartContract, - RunContext, - } from '../../../../task_manager/server'; - import { checkBackgoundSessions } from './check_sessions'; - import { CoreSetup, SavedObjectsClient, Logger } from '../../../../../../src/core/server'; + TaskManagerSetupContract, + TaskManagerStartContract, + RunContext, +} from '../../../../task_manager/server'; +import { checkBackgoundSessions } from './check_sessions'; +import { CoreSetup, SavedObjectsClient, Logger } from '../../../../../../src/core/server'; import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; - - export const BACKGROUND_SESSIONS_TASK_TYPE = 'bg_monitor'; - export const BACKGROUND_SESSIONS_TASK_ID = `data_enhanced_${BACKGROUND_SESSIONS_TASK_TYPE}`; - export const MONITOR_INTERVAL = `15s`; - - function backgroundSessionRunner(core: CoreSetup, logger: Logger) { - logger.debug(`Registering background search task`); - return ({ taskInstance }: RunContext) => { - return { - async run() { - logger.debug('Background sessions task started'); - const [coreStart] = await core.getStartServices(); - const internalRepo = coreStart.savedObjects.createInternalRepository([BACKGROUND_SESSION_TYPE]); - const internalSavedObjectsClient = new SavedObjectsClient(internalRepo); - await checkBackgoundSessions( - internalSavedObjectsClient, - logger, - coreStart.elasticsearch.client.asInternalUser - ); - - return { - runAt: new Date(Date.now() + 10 * 1000), - state: {}, - }; - }, - }; + +export const BACKGROUND_SESSIONS_TASK_TYPE = 'bg_monitor'; +export const BACKGROUND_SESSIONS_TASK_ID = `data_enhanced_${BACKGROUND_SESSIONS_TASK_TYPE}`; +export const MONITOR_INTERVAL = `15s`; + +function backgroundSessionRunner(core: CoreSetup, logger: Logger) { + return ({ taskInstance }: RunContext) => { + return { + async run() { + const [coreStart] = await core.getStartServices(); + const internalRepo = coreStart.savedObjects.createInternalRepository([ + BACKGROUND_SESSION_TYPE, + ]); + const internalSavedObjectsClient = new SavedObjectsClient(internalRepo); + await checkBackgoundSessions( + internalSavedObjectsClient, + logger, + coreStart.elasticsearch.client.asInternalUser + ); + + return { + runAt: new Date(Date.now() + 10 * 1000), + state: {}, + }; + }, }; - } - - export function registerBackgroundSessionsTask( - core: CoreSetup, - taskManager: TaskManagerSetupContract, - logger: Logger - ) { - taskManager.registerTaskDefinitions({ - [BACKGROUND_SESSIONS_TASK_TYPE]: { - title: 'Background Session Monitor', - createTaskRunner: backgroundSessionRunner(core, logger), + }; +} + +export function registerBackgroundSessionsTask( + core: CoreSetup, + taskManager: TaskManagerSetupContract, + logger: Logger +) { + taskManager.registerTaskDefinitions({ + [BACKGROUND_SESSIONS_TASK_TYPE]: { + title: 'Background Session Monitor', + createTaskRunner: backgroundSessionRunner(core, logger), + }, + }); +} + +export async function scheduleBackgroundSessionsTasks( + taskManager: TaskManagerStartContract, + logger: Logger +) { + try { + // delete previous task + await taskManager.remove(BACKGROUND_SESSIONS_TASK_ID); + } catch (e) {} // eslint-disable-line no-empty + + try { + await taskManager.ensureScheduled({ + id: BACKGROUND_SESSIONS_TASK_ID, + taskType: BACKGROUND_SESSIONS_TASK_TYPE, + schedule: { + interval: MONITOR_INTERVAL, }, + state: {}, + params: {}, }); + + logger.debug(`Background search task, scheduled to run`); + } catch (e) { + logger.debug(`Error scheduling task, received ${e.message}`); } - - export async function scheduleBackgroundSessionsTasks( - taskManager: TaskManagerStartContract, - logger: Logger - ) { - try { - // delete previous task - const deleteResult = await taskManager.remove(BACKGROUND_SESSIONS_TASK_ID); - logger.debug(JSON.stringify(deleteResult, null, 2)); - } catch (e) { - // eslint-disable-line no-empty - } - - try { - const result = await taskManager.ensureScheduled({ - id: BACKGROUND_SESSIONS_TASK_ID, - taskType: BACKGROUND_SESSIONS_TASK_TYPE, - schedule: { - interval: MONITOR_INTERVAL, - }, - state: {}, - params: {}, - }); - - logger.debug(JSON.stringify(result, null, 2)); - logger.debug(`Background search task, scheduled to run`); - } catch (e) { - logger.debug(`Error scheduling task, received ${e.message}`); - } - } \ No newline at end of file +} From a8d72336e4749b62a33234d52a416af6da73d4c8 Mon Sep 17 00:00:00 2001 From: Liza K Date: Tue, 8 Dec 2020 11:45:45 +0200 Subject: [PATCH 26/58] Move task scheduling behind a feature flag --- x-pack/plugins/data_enhanced/server/plugin.ts | 12 +++++--- .../server/search/session/check_sessions.ts | 2 ++ .../server/search/session/session_service.ts | 28 ++++++++++++++++--- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/data_enhanced/server/plugin.ts b/x-pack/plugins/data_enhanced/server/plugin.ts index 79264f707cc257..7b25deb0bc0eac 100644 --- a/x-pack/plugins/data_enhanced/server/plugin.ts +++ b/x-pack/plugins/data_enhanced/server/plugin.ts @@ -20,7 +20,6 @@ import { enhancedEsSearchStrategyProvider, eqlSearchStrategyProvider, } from './search'; -import { registerBackgroundSessionsTask, scheduleBackgroundSessionsTasks } from './search/session'; import { getUiSettings } from './ui_settings'; interface SetupDependencies { @@ -71,12 +70,17 @@ export class EnhancedDataServerPlugin implements Plugin; } +interface SetupDependencies { + taskManager: TaskManagerSetupContract; +} + +interface StartDependencies { + taskManager: TaskManagerStartContract; + config$: Observable; +} + export class BackgroundSessionService implements ISessionService { /** * Map of sessionId to { [requestHash]: searchId } @@ -62,8 +77,12 @@ export class BackgroundSessionService implements ISessionService { constructor(private readonly logger: Logger) {} - public async start(core: CoreStart, config$: Observable) { - return this.setupMonitoring(core, config$); + public setup(core: CoreSetup, deps: SetupDependencies) { + registerBackgroundSessionsTask(core, deps.taskManager, this.logger); + } + + public async start(core: CoreStart, deps: StartDependencies) { + return this.setupMonitoring(core, deps); } public stop() { @@ -71,9 +90,10 @@ export class BackgroundSessionService implements ISessionService { clearInterval(this.monitorInterval); } - private setupMonitoring = async (core: CoreStart, config$: Observable) => { - const config = await config$.pipe(first()).toPromise(); + private setupMonitoring = async (core: CoreStart, deps: StartDependencies) => { + const config = await deps.config$.pipe(first()).toPromise(); if (config.search.sendToBackground.enabled) { + scheduleBackgroundSessionsTasks(deps.taskManager, this.logger); this.logger.debug(`setupMonitoring | Enabling monitoring`); const internalRepo = core.savedObjects.createInternalRepository([BACKGROUND_SESSION_TYPE]); this.internalSavedObjectsClient = new SavedObjectsClient(internalRepo); From 4876f3ded6cdb6f180315d36e87d4db59e37f1b4 Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Tue, 8 Dec 2020 16:28:52 +0200 Subject: [PATCH 27/58] Update x-pack/plugins/data_enhanced/server/search/session/session_service.ts Co-authored-by: Anton Dosov --- .../data_enhanced/server/search/session/session_service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts index f574d970a33345..9aadc87d1aa591 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts @@ -84,7 +84,7 @@ export class BackgroundSessionService implements ISessionService { /** * Gets all {@link SessionSavedObjectAttributes | Background Searches} that * currently being tracked by the service. - * * + * * @remarks * Uses `internalSavedObjectsClient` as this is called asynchronously, not within the * context of a user's session. From 685b7ae1b00c294cf40b3c88853ec222cc32cf4b Mon Sep 17 00:00:00 2001 From: Liza K Date: Tue, 8 Dec 2020 18:52:18 +0200 Subject: [PATCH 28/58] Add unit tests --- .../session/check_running_sessions.test.ts | 138 ++++++++++++++++++ ..._sessions.ts => check_running_sessions.ts} | 65 ++------- .../server/search/session/constants.ts | 11 ++ .../search/session/get_search_status.test.ts | 71 +++++++++ .../search/session/get_search_status.ts | 32 ++++ .../search/session/get_session_status.test.ts | 43 ++++++ .../search/session/get_session_status.ts | 21 +++ .../server/search/session/monitoring_task.ts | 8 +- .../search/session/session_service.test.ts | 10 +- .../server/search/session/session_service.ts | 13 +- .../server/search/session/types.ts | 27 ++++ .../data_enhanced/server/search/types.ts | 16 +- 12 files changed, 373 insertions(+), 82 deletions(-) create mode 100644 x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts rename x-pack/plugins/data_enhanced/server/search/session/{check_sessions.ts => check_running_sessions.ts} (52%) create mode 100644 x-pack/plugins/data_enhanced/server/search/session/constants.ts create mode 100644 x-pack/plugins/data_enhanced/server/search/session/get_search_status.test.ts create mode 100644 x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts create mode 100644 x-pack/plugins/data_enhanced/server/search/session/get_session_status.test.ts create mode 100644 x-pack/plugins/data_enhanced/server/search/session/get_session_status.ts create mode 100644 x-pack/plugins/data_enhanced/server/search/session/types.ts diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts new file mode 100644 index 00000000000000..73372baa6c8e7f --- /dev/null +++ b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts @@ -0,0 +1,138 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { checkRunningSessions } from './check_running_sessions'; +import { BackgroundSessionSavedObjectAttributes, BackgroundSessionStatus } from '../../../common'; +import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; +import type { SavedObjectsClientContract } from 'kibana/server'; + +describe('getSearchStatus', () => { + let mockClient: any; + let savedObjectsClient: jest.Mocked; + const mockLogger: any = { + debug: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }; + + beforeEach(() => { + savedObjectsClient = savedObjectsClientMock.create(); + mockClient = { + transport: { + request: jest.fn(), + }, + }; + }); + + test('does nothing if there are no open sessions', async () => { + savedObjectsClient.bulkUpdate = jest.fn(); + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [], + total: 0, + } as any); + + await checkRunningSessions(savedObjectsClient, mockClient, mockLogger); + + expect(savedObjectsClient.bulkUpdate).not.toBeCalled(); + }); + + test('does nothing if there are no searchIds in the saved object', async () => { + savedObjectsClient.bulkUpdate = jest.fn(); + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [ + { + attributes: { + idMapping: {}, + }, + }, + ], + total: 1, + } as any); + + await checkRunningSessions(savedObjectsClient, mockClient, mockLogger); + + expect(savedObjectsClient.bulkUpdate).not.toBeCalled(); + }); + + test('does nothing if the search is still running', async () => { + savedObjectsClient.bulkUpdate = jest.fn(); + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [ + { + attributes: { + idMapping: { + 'search-hash': 'search-id', + }, + }, + }, + ], + total: 1, + } as any); + + mockClient.transport.request.mockResolvedValue({ + is_partial: true, + is_running: true, + }); + + await checkRunningSessions(savedObjectsClient, mockClient, mockLogger); + + expect(savedObjectsClient.bulkUpdate).not.toBeCalled(); + }); + + test('updates to complete if the search is done', async () => { + savedObjectsClient.bulkUpdate = jest.fn(); + const so = { + attributes: { + idMapping: { + 'search-hash': 'search-id', + }, + }, + }; + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [so], + total: 1, + } as any); + + mockClient.transport.request.mockResolvedValue({ + is_partial: false, + is_running: false, + completion_status: 200, + }); + + await checkRunningSessions(savedObjectsClient, mockClient, mockLogger); + const [updateInput] = savedObjectsClient.bulkUpdate.mock.calls[0]; + expect((updateInput[0].attributes as BackgroundSessionSavedObjectAttributes).status).toBe( + BackgroundSessionStatus.COMPLETE + ); + }); + + test('updates to error if the search is errored', async () => { + savedObjectsClient.bulkUpdate = jest.fn(); + const so = { + attributes: { + idMapping: { + 'search-hash': 'search-id', + }, + }, + }; + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [so], + total: 1, + } as any); + + mockClient.transport.request.mockResolvedValue({ + is_partial: false, + is_running: false, + completion_status: 500, + }); + + await checkRunningSessions(savedObjectsClient, mockClient, mockLogger); + const [updateInput] = savedObjectsClient.bulkUpdate.mock.calls[0]; + expect((updateInput[0].attributes as BackgroundSessionSavedObjectAttributes).status).toBe( + BackgroundSessionStatus.ERROR + ); + }); +}); diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_sessions.ts b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts similarity index 52% rename from x-pack/plugins/data_enhanced/server/search/session/check_sessions.ts rename to x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts index dc5c93b183c0bf..022441ba8e80de 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/check_sessions.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts @@ -4,63 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ApiResponse } from '@elastic/elasticsearch'; import { - SavedObjectsClient, Logger, ElasticsearchClient, SavedObjectsFindResult, + SavedObjectsClientContract, } from 'kibana/server'; import { BackgroundSessionStatus, BackgroundSessionSavedObjectAttributes } from '../../../common'; import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; -import { isAsyncSearchStatusResponse } from '../types'; +import { getSearchStatus } from './get_search_status'; +import { getSessionStatus } from './get_session_status'; -export enum SearchStatus { - IN_PROGRESS = 'in_progress', - ERROR = 'error', - COMPLETE = 'complete', -} - -async function checkAsyncId(client: ElasticsearchClient, logger: Logger, asyncId: string) { - try { - const path = encodeURI(`/_async_search/status/${asyncId}`); - const response: ApiResponse = await client.transport.request({ - path, - method: 'GET', - }); - if (isAsyncSearchStatusResponse(response)) { - if ((response.is_partial && !response.is_running) || response.completion_status > 200) { - return SearchStatus.ERROR; - } else if (response.is_partial && !response.is_running) { - return SearchStatus.COMPLETE; - } else { - return SearchStatus.IN_PROGRESS; - } - } else { - return SearchStatus.ERROR; - } - } catch (e) { - return SearchStatus.ERROR; - } -} - -async function getSessionStatus( - searchStatuses: SearchStatus[] -): Promise { - if (searchStatuses.some((item) => item === SearchStatus.ERROR)) { - return BackgroundSessionStatus.ERROR; - } else if (searchStatuses.every((item) => item === SearchStatus.COMPLETE)) { - return BackgroundSessionStatus.COMPLETE; - } else { - // Still running - return undefined; - } -} - -export async function checkBackgoundSessions( - savedObjectsClient: SavedObjectsClient, - logger: Logger, - client: ElasticsearchClient +export async function checkRunningSessions( + savedObjectsClient: SavedObjectsClientContract, + client: ElasticsearchClient, + logger: Logger ): Promise { try { const runningBackgroundSearchesResponse = await savedObjectsClient.find( @@ -85,12 +43,12 @@ export async function checkBackgoundSessions( const searchIds = Object.values(session.attributes.idMapping); const searchStatuses = await Promise.all( searchIds.map(async (searchId: string) => { - return await checkAsyncId(client, logger, searchId); + return await getSearchStatus(client, searchId); }) ); - const sessionStatus = await getSessionStatus(searchStatuses); - if (sessionStatus) { + const sessionStatus = getSessionStatus(searchStatuses); + if (sessionStatus !== BackgroundSessionStatus.IN_PROGRESS) { session.attributes.status = sessionStatus; updatedSessions.push(session); } @@ -98,7 +56,7 @@ export async function checkBackgoundSessions( ); if (updatedSessions.length) { - // If there's an error, we'll try again in the next iteration + // If there's an error, we'll try again in the next iteration, so there's no need to check the output. const updatedResponse = await savedObjectsClient.bulkUpdate( updatedSessions ); @@ -106,6 +64,5 @@ export async function checkBackgoundSessions( } } catch (err) { logger.error(err); - return; } } diff --git a/x-pack/plugins/data_enhanced/server/search/session/constants.ts b/x-pack/plugins/data_enhanced/server/search/session/constants.ts new file mode 100644 index 00000000000000..4ac32938c48438 --- /dev/null +++ b/x-pack/plugins/data_enhanced/server/search/session/constants.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const INMEM_MAX_SESSIONS = 10000; +export const DEFAULT_EXPIRATION = 7 * 24 * 60 * 60 * 1000; +export const INMEM_TRACKING_INTERVAL = 10 * 1000; +export const INMEM_TRACKING_TIMEOUT_SEC = 60; +export const MAX_UPDATE_RETRIES = 3; diff --git a/x-pack/plugins/data_enhanced/server/search/session/get_search_status.test.ts b/x-pack/plugins/data_enhanced/server/search/session/get_search_status.test.ts new file mode 100644 index 00000000000000..37d7219c82450a --- /dev/null +++ b/x-pack/plugins/data_enhanced/server/search/session/get_search_status.test.ts @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SearchStatus } from './types'; +import { getSearchStatus } from './get_search_status'; + +describe('getSearchStatus', () => { + let mockClient: any; + beforeEach(() => { + mockClient = { + transport: { + request: jest.fn(), + }, + }; + }); + + test('returns an error status if search is partial and not running', () => { + mockClient.transport.request.mockResolvedValue({ + is_partial: true, + is_running: false, + completion_status: 200, + }); + expect(getSearchStatus(mockClient, '123')).resolves.toBe(SearchStatus.ERROR); + }); + + test('returns an error status if completion_status is an error', () => { + mockClient.transport.request.mockResolvedValue({ + is_partial: false, + is_running: false, + completion_status: 500, + }); + expect(getSearchStatus(mockClient, '123')).resolves.toBe(SearchStatus.ERROR); + }); + + test('returns an error status if gets an ES error', () => { + mockClient.transport.request.mockResolvedValue({ + error: { + root_cause: { + reason: 'not found', + }, + }, + }); + expect(getSearchStatus(mockClient, '123')).resolves.toBe(SearchStatus.ERROR); + }); + + test('returns an error status throws', () => { + mockClient.transport.request.mockRejectedValue(new Error('O_o')); + expect(getSearchStatus(mockClient, '123')).resolves.toBe(SearchStatus.ERROR); + }); + + test('returns a complete status', () => { + mockClient.transport.request.mockResolvedValue({ + is_partial: false, + is_running: false, + completion_status: 200, + }); + expect(getSearchStatus(mockClient, '123')).resolves.toBe(SearchStatus.COMPLETE); + }); + + test('returns a running status otherwise', () => { + mockClient.transport.request.mockResolvedValue({ + is_partial: false, + is_running: true, + completion_status: undefined, + }); + expect(getSearchStatus(mockClient, '123')).resolves.toBe(SearchStatus.IN_PROGRESS); + }); +}); diff --git a/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts b/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts new file mode 100644 index 00000000000000..3b25ca99e05b2c --- /dev/null +++ b/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ApiResponse } from '@elastic/elasticsearch'; +import { ElasticsearchClient } from 'src/core/server'; +import { isAsyncSearchStatusResponse, SearchStatus } from './types'; + +export async function getSearchStatus(client: ElasticsearchClient, asyncId: string) { + try { + const path = encodeURI(`/_async_search/status/${asyncId}`); + const response: ApiResponse = await client.transport.request({ + path, + method: 'GET', + }); + if (isAsyncSearchStatusResponse(response)) { + if ((response.is_partial && !response.is_running) || response.completion_status > 200) { + return SearchStatus.ERROR; + } else if (!response.is_partial && !response.is_running) { + return SearchStatus.COMPLETE; + } else { + return SearchStatus.IN_PROGRESS; + } + } else { + return SearchStatus.ERROR; + } + } catch (e) { + return SearchStatus.ERROR; + } +} diff --git a/x-pack/plugins/data_enhanced/server/search/session/get_session_status.test.ts b/x-pack/plugins/data_enhanced/server/search/session/get_session_status.test.ts new file mode 100644 index 00000000000000..1d72548a7e2164 --- /dev/null +++ b/x-pack/plugins/data_enhanced/server/search/session/get_session_status.test.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SearchStatus } from './types'; +import { getSessionStatus } from './get_session_status'; +import { BackgroundSessionStatus } from '../../../common'; + +describe('getSessionStatus', () => { + test("returns an in_progress status if there's nothing inside the session", () => { + const statuses: SearchStatus[] = []; + expect(getSessionStatus(statuses)).toBe(BackgroundSessionStatus.IN_PROGRESS); + }); + + test("returns an error status if there's at least one error", () => { + const statuses: SearchStatus[] = [ + SearchStatus.IN_PROGRESS, + SearchStatus.ERROR, + SearchStatus.COMPLETE, + ]; + expect(getSessionStatus(statuses)).toBe(BackgroundSessionStatus.ERROR); + }); + + test('returns a complete status if all are complete', () => { + const statuses: SearchStatus[] = [ + SearchStatus.COMPLETE, + SearchStatus.COMPLETE, + SearchStatus.COMPLETE, + ]; + expect(getSessionStatus(statuses)).toBe(BackgroundSessionStatus.COMPLETE); + }); + + test('returns a running status if some are still running', () => { + const statuses: SearchStatus[] = [ + SearchStatus.IN_PROGRESS, + SearchStatus.COMPLETE, + SearchStatus.COMPLETE, + ]; + expect(getSessionStatus(statuses)).toBe(BackgroundSessionStatus.IN_PROGRESS); + }); +}); diff --git a/x-pack/plugins/data_enhanced/server/search/session/get_session_status.ts b/x-pack/plugins/data_enhanced/server/search/session/get_session_status.ts new file mode 100644 index 00000000000000..3b910ff3879138 --- /dev/null +++ b/x-pack/plugins/data_enhanced/server/search/session/get_session_status.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { BackgroundSessionStatus } from '../../../common'; +import { SearchStatus } from './types'; + +export function getSessionStatus(searchStatuses: SearchStatus[]): BackgroundSessionStatus { + if (searchStatuses.some((item) => item === SearchStatus.ERROR)) { + return BackgroundSessionStatus.ERROR; + } else if ( + searchStatuses.length > 0 && + searchStatuses.every((item) => item === SearchStatus.COMPLETE) + ) { + return BackgroundSessionStatus.COMPLETE; + } else { + return BackgroundSessionStatus.IN_PROGRESS; + } +} diff --git a/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts b/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts index 7d3d3b713f7c5b..d3b2277e6aba1e 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts @@ -9,7 +9,7 @@ import { TaskManagerStartContract, RunContext, } from '../../../../task_manager/server'; -import { checkBackgoundSessions } from './check_sessions'; +import { checkRunningSessions } from './check_running_sessions'; import { CoreSetup, SavedObjectsClient, Logger } from '../../../../../../src/core/server'; import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; @@ -26,10 +26,10 @@ function backgroundSessionRunner(core: CoreSetup, logger: Logger) { BACKGROUND_SESSION_TYPE, ]); const internalSavedObjectsClient = new SavedObjectsClient(internalRepo); - await checkBackgoundSessions( + await checkRunningSessions( internalSavedObjectsClient, - logger, - coreStart.elasticsearch.client.asInternalUser + coreStart.elasticsearch.client.asInternalUser, + logger ); return { diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts index 4a518ca259f1d0..791d2a1a815d92 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts @@ -13,14 +13,14 @@ import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; import { BackgroundSessionDependencies, BackgroundSessionService, - INMEM_TRACKING_INTERVAL, - MAX_UPDATE_RETRIES, SessionInfo, } from './session_service'; import { createRequestHash } from './utils'; import moment from 'moment'; import { coreMock } from 'src/core/server/mocks'; import { ConfigSchema } from '../../../config'; +import { taskManagerMock } from '../../../../task_manager/server/mocks'; +import { INMEM_TRACKING_INTERVAL, MAX_UPDATE_RETRIES } from './constants'; const flushPromises = () => new Promise((resolve) => setImmediate(resolve)); @@ -406,7 +406,11 @@ describe('BackgroundSessionService', () => { }, }, }); - await service.start(coreMock.createStart(), config$); + const mockTaskManager = taskManagerMock.createStart(); + await service.start(coreMock.createStart(), { + config$, + taskManager: mockTaskManager, + }); await flushPromises(); }); diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts index 241e9c9998f066..2f34955b64a019 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts @@ -40,12 +40,13 @@ import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; import { createRequestHash } from './utils'; import { ConfigSchema } from '../../../config'; import { registerBackgroundSessionsTask, scheduleBackgroundSessionsTasks } from './monitoring_task'; - -const INMEM_MAX_SESSIONS = 10000; -const DEFAULT_EXPIRATION = 7 * 24 * 60 * 60 * 1000; -export const INMEM_TRACKING_INTERVAL = 10 * 1000; -export const INMEM_TRACKING_TIMEOUT_SEC = 60; -export const MAX_UPDATE_RETRIES = 3; +import { + DEFAULT_EXPIRATION, + INMEM_MAX_SESSIONS, + INMEM_TRACKING_INTERVAL, + INMEM_TRACKING_TIMEOUT_SEC, + MAX_UPDATE_RETRIES, +} from './constants'; export interface BackgroundSessionDependencies { savedObjectsClient: SavedObjectsClientContract; diff --git a/x-pack/plugins/data_enhanced/server/search/session/types.ts b/x-pack/plugins/data_enhanced/server/search/session/types.ts new file mode 100644 index 00000000000000..207218ede8f9cb --- /dev/null +++ b/x-pack/plugins/data_enhanced/server/search/session/types.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ShardsResponse } from 'elasticsearch'; + +export enum SearchStatus { + IN_PROGRESS = 'in_progress', + ERROR = 'error', + COMPLETE = 'complete', +} + +export interface AsyncSearchStatusResponse { + id?: string; + is_partial: boolean; + is_running: boolean; + start_time_in_millis: number; + expiration_time_in_millis: number; + completion_status: number; + _shards: ShardsResponse; +} + +export function isAsyncSearchStatusResponse(response: any): response is AsyncSearchStatusResponse { + return response.is_partial !== undefined && response.is_running !== undefined; +} diff --git a/x-pack/plugins/data_enhanced/server/search/types.ts b/x-pack/plugins/data_enhanced/server/search/types.ts index 3aaf8306e98c3a..f01ac51a1516e1 100644 --- a/x-pack/plugins/data_enhanced/server/search/types.ts +++ b/x-pack/plugins/data_enhanced/server/search/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SearchResponse, ShardsResponse } from 'elasticsearch'; +import { SearchResponse } from 'elasticsearch'; export interface AsyncSearchResponse { id?: string; @@ -13,20 +13,6 @@ export interface AsyncSearchResponse { is_running: boolean; } -export interface AsyncSearchStatusResponse { - id?: string; - is_partial: boolean; - is_running: boolean; - start_time_in_millis: number; - expiration_time_in_millis: number; - completion_status: number; - _shards: ShardsResponse; -} - -export function isAsyncSearchStatusResponse(response: any): response is AsyncSearchStatusResponse { - return !!response.start_time_in_millis; -} - export interface EqlSearchResponse extends SearchResponse { id?: string; is_partial: boolean; From ffabaa9b114359d30fbd549b8118f7eb8b638a6a Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Tue, 8 Dec 2020 19:02:38 +0200 Subject: [PATCH 29/58] Update x-pack/plugins/data_enhanced/server/search/session/session_service.ts Co-authored-by: Anton Dosov --- .../data_enhanced/server/search/session/session_service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts index 9aadc87d1aa591..9975c903184870 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts @@ -161,7 +161,7 @@ export class BackgroundSessionService implements ISessionService { this.logger.debug(`updateAllSavedObjects | Updating ${activeMappingObjects.length} items`); const updatedSessions = activeMappingObjects - ?.filter((so) => !so.error) + .filter((so) => !so.error) .map((sessionSavedObject) => { const sessionInfo = this.sessionSearchMap.get(sessionSavedObject.id); const idMapping = sessionInfo ? Object.fromEntries(sessionInfo.ids.entries()) : {}; From 63c7d6a7f0286b37041116956ed89f6cf7425812 Mon Sep 17 00:00:00 2001 From: Liza K Date: Tue, 8 Dec 2020 20:13:53 +0200 Subject: [PATCH 30/58] Use setTimeout to schedule monitoring steps --- .../search/session/session_service.test.ts | 36 ++++++++++++++----- .../server/search/session/session_service.ts | 13 ++++--- 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts index 4a518ca259f1d0..766de908353f5c 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts @@ -415,9 +415,27 @@ describe('BackgroundSessionService', () => { service.stop(); }); + it('schedules the next iteration', async () => { + const findSpy = jest.fn().mockResolvedValue({ saved_objects: [] }); + createMockInternalSavedObjectClient(findSpy); + + const mockIdMapping = createMockIdMapping([[MOCK_KEY_HASH, MOCK_ASYNC_ID]], moment()); + + Object.defineProperty(service, 'sessionSearchMap', { + get: () => mockIdMapping, + }); + + jest.advanceTimersByTime(INMEM_TRACKING_INTERVAL); + expect(findSpy).toHaveBeenCalledTimes(1); + await flushPromises(); + + jest.advanceTimersByTime(INMEM_TRACKING_INTERVAL); + expect(findSpy).toHaveBeenCalledTimes(2); + }); + it('should delete expired IDs', async () => { - const bulkGetSpy = jest.fn().mockResolvedValueOnce({ saved_objects: [] }); - createMockInternalSavedObjectClient(bulkGetSpy); + const findSpy = jest.fn().mockResolvedValueOnce({ saved_objects: [] }); + createMockInternalSavedObjectClient(findSpy); const mockIdMapping = createMockIdMapping( [[MOCK_KEY_HASH, MOCK_ASYNC_ID]], @@ -432,13 +450,13 @@ describe('BackgroundSessionService', () => { // Get setInterval to fire jest.advanceTimersByTime(INMEM_TRACKING_INTERVAL); - expect(bulkGetSpy).not.toHaveBeenCalled(); + expect(findSpy).not.toHaveBeenCalled(); expect(deleteSpy).toHaveBeenCalledTimes(1); }); it('should delete IDs that passed max retries', async () => { - const bulkGetSpy = jest.fn().mockResolvedValueOnce({ saved_objects: [] }); - createMockInternalSavedObjectClient(bulkGetSpy); + const findSpy = jest.fn().mockResolvedValueOnce({ saved_objects: [] }); + createMockInternalSavedObjectClient(findSpy); const mockIdMapping = createMockIdMapping( [[MOCK_KEY_HASH, MOCK_ASYNC_ID]], @@ -454,16 +472,16 @@ describe('BackgroundSessionService', () => { // Get setInterval to fire jest.advanceTimersByTime(INMEM_TRACKING_INTERVAL); - expect(bulkGetSpy).not.toHaveBeenCalled(); + expect(findSpy).not.toHaveBeenCalled(); expect(deleteSpy).toHaveBeenCalledTimes(1); }); it('should not fetch when no IDs are mapped', async () => { - const bulkGetSpy = jest.fn().mockResolvedValueOnce({ saved_objects: [] }); - createMockInternalSavedObjectClient(bulkGetSpy); + const findSpy = jest.fn().mockResolvedValueOnce({ saved_objects: [] }); + createMockInternalSavedObjectClient(findSpy); jest.advanceTimersByTime(INMEM_TRACKING_INTERVAL); - expect(bulkGetSpy).not.toHaveBeenCalled(); + expect(findSpy).not.toHaveBeenCalled(); }); it('should try to fetch saved objects if some ids are mapped', async () => { diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts index 9975c903184870..c9c6f17ef8b055 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts @@ -58,7 +58,7 @@ export class BackgroundSessionService implements ISessionService { */ private sessionSearchMap = new Map(); private internalSavedObjectsClient!: SavedObjectsClientContract; - private monitorInterval!: NodeJS.Timeout; + private monitorTimer!: NodeJS.Timeout; constructor(private readonly logger: Logger) {} @@ -68,7 +68,7 @@ export class BackgroundSessionService implements ISessionService { public stop() { this.sessionSearchMap.clear(); - clearInterval(this.monitorInterval); + clearTimeout(this.monitorTimer); } private setupMonitoring = async (core: CoreStart, config$: Observable) => { @@ -77,7 +77,7 @@ export class BackgroundSessionService implements ISessionService { this.logger.debug(`setupMonitoring | Enabling monitoring`); const internalRepo = core.savedObjects.createInternalRepository([BACKGROUND_SESSION_TYPE]); this.internalSavedObjectsClient = new SavedObjectsClient(internalRepo); - this.monitorInterval = setInterval(this.monitorMappedIds.bind(this), INMEM_TRACKING_INTERVAL); + this.monitorTimer = setTimeout(this.monitorMappedIds.bind(this), INMEM_TRACKING_INTERVAL); } }; @@ -121,7 +121,7 @@ export class BackgroundSessionService implements ISessionService { }); }; - public async monitorMappedIds() { + private async monitorMappedIds() { try { this.clearSessions(); @@ -150,8 +150,11 @@ export class BackgroundSessionService implements ISessionService { } }); } catch (e) { - this.logger.error(`monitorMappedIds | Error fetching sessions. ${e}`); + this.logger.error(`monitorMappedIds | Error while updating sessions. ${e}`); } + + // Schedule the next iteration + this.monitorTimer = setTimeout(this.monitorMappedIds.bind(this), INMEM_TRACKING_INTERVAL); } private async updateAllSavedObjects( From 7fd8b5eb750b438447079a35af3a925dd4933c54 Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Tue, 8 Dec 2020 20:16:58 +0200 Subject: [PATCH 31/58] Update request_utils.ts --- x-pack/plugins/data_enhanced/server/search/request_utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/data_enhanced/server/search/request_utils.ts b/x-pack/plugins/data_enhanced/server/search/request_utils.ts index a14d7eba5e1ff5..f54ab2199c9054 100644 --- a/x-pack/plugins/data_enhanced/server/search/request_utils.ts +++ b/x-pack/plugins/data_enhanced/server/search/request_utils.ts @@ -58,7 +58,7 @@ export function getDefaultAsyncGetParams(): Pick< 'keep_alive' | 'wait_for_completion_timeout' > { return { - keep_alive: '10m', // Extend the TTL for this search request by one minute + keep_alive: '1m', // Extend the TTL for this search request by one minute wait_for_completion_timeout: '100ms', // Wait up to 100ms for the response to return }; } From 07fe4bf34bbb9794ee2d0fc03a932bfb515fd193 Mon Sep 17 00:00:00 2001 From: Liza K Date: Wed, 9 Dec 2020 12:18:40 +0200 Subject: [PATCH 32/58] settimeout --- .../server/search/session/session_service.ts | 67 ++++++++++--------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts index c9c6f17ef8b055..96d66157c48eca 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts @@ -77,7 +77,7 @@ export class BackgroundSessionService implements ISessionService { this.logger.debug(`setupMonitoring | Enabling monitoring`); const internalRepo = core.savedObjects.createInternalRepository([BACKGROUND_SESSION_TYPE]); this.internalSavedObjectsClient = new SavedObjectsClient(internalRepo); - this.monitorTimer = setTimeout(this.monitorMappedIds.bind(this), INMEM_TRACKING_INTERVAL); + this.monitorMappedIds(); } }; @@ -122,39 +122,40 @@ export class BackgroundSessionService implements ISessionService { }; private async monitorMappedIds() { - try { - this.clearSessions(); - - if (!this.sessionSearchMap.size) return; - this.logger.debug(`monitorMappedIds | Map contains ${this.sessionSearchMap.size} items`); - - const savedSessions = await this.getAllMappedSavedObjects(); - const updatedSessions = await this.updateAllSavedObjects(savedSessions); - - updatedSessions.forEach((updatedSavedObject) => { - const sessionInfo = this.sessionSearchMap.get(updatedSavedObject.id)!; - if (updatedSavedObject.error) { - // Retry next time - sessionInfo.retryCount++; - } else if (updatedSavedObject.attributes.idMapping) { - // Delete the ids that we just saved, avoiding a potential new ids being lost. - Object.keys(updatedSavedObject.attributes.idMapping).forEach((key) => { - sessionInfo.ids.delete(key); - }); - // If the session object is empty, delete it as well - if (!sessionInfo.ids.entries.length) { - this.sessionSearchMap.delete(updatedSavedObject.id); - } else { - sessionInfo.retryCount = 0; + this.monitorTimer = setTimeout(async () => { + try { + this.clearSessions(); + + if (!this.sessionSearchMap.size) return; + this.logger.debug(`monitorMappedIds | Map contains ${this.sessionSearchMap.size} items`); + + const savedSessions = await this.getAllMappedSavedObjects(); + const updatedSessions = await this.updateAllSavedObjects(savedSessions); + + updatedSessions.forEach((updatedSavedObject) => { + const sessionInfo = this.sessionSearchMap.get(updatedSavedObject.id)!; + if (updatedSavedObject.error) { + // Retry next time + sessionInfo.retryCount++; + } else if (updatedSavedObject.attributes.idMapping) { + // Delete the ids that we just saved, avoiding a potential new ids being lost. + Object.keys(updatedSavedObject.attributes.idMapping).forEach((key) => { + sessionInfo.ids.delete(key); + }); + // If the session object is empty, delete it as well + if (!sessionInfo.ids.entries.length) { + this.sessionSearchMap.delete(updatedSavedObject.id); + } else { + sessionInfo.retryCount = 0; + } } - } - }); - } catch (e) { - this.logger.error(`monitorMappedIds | Error while updating sessions. ${e}`); - } - - // Schedule the next iteration - this.monitorTimer = setTimeout(this.monitorMappedIds.bind(this), INMEM_TRACKING_INTERVAL); + }); + } catch (e) { + this.logger.error(`monitorMappedIds | Error while updating sessions. ${e}`); + } finally { + this.monitorMappedIds(); + } + }, INMEM_TRACKING_INTERVAL); } private async updateAllSavedObjects( From cf511f08af41c4b512058a84011b56eea119e592 Mon Sep 17 00:00:00 2001 From: Liza K Date: Mon, 4 Jan 2021 15:20:28 +0200 Subject: [PATCH 33/58] tiny cleanup --- x-pack/plugins/data_enhanced/common/index.ts | 1 - .../data_enhanced/common/search/session/types.ts | 8 -------- x-pack/plugins/data_enhanced/server/plugin.ts | 6 ++++-- .../server/search/session/session_service.ts | 11 ++++------- 4 files changed, 8 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/data_enhanced/common/index.ts b/x-pack/plugins/data_enhanced/common/index.ts index 7b14a723d78777..2949a23ca185c0 100644 --- a/x-pack/plugins/data_enhanced/common/index.ts +++ b/x-pack/plugins/data_enhanced/common/index.ts @@ -13,6 +13,5 @@ export { IAsyncSearchOptions, pollSearch, BackgroundSessionSavedObjectAttributes, - BackgroundSessionFindOptions, BackgroundSessionStatus, } from './search'; diff --git a/x-pack/plugins/data_enhanced/common/search/session/types.ts b/x-pack/plugins/data_enhanced/common/search/session/types.ts index 0b82c9160ea1a0..f59055ad0560ab 100644 --- a/x-pack/plugins/data_enhanced/common/search/session/types.ts +++ b/x-pack/plugins/data_enhanced/common/search/session/types.ts @@ -21,11 +21,3 @@ export interface BackgroundSessionSavedObjectAttributes { restoreState: Record; idMapping: Record; } - -export interface BackgroundSessionFindOptions { - page?: number; - perPage?: number; - sortField?: string; - sortOrder?: string; - filter?: string; -} diff --git a/x-pack/plugins/data_enhanced/server/plugin.ts b/x-pack/plugins/data_enhanced/server/plugin.ts index 7b25deb0bc0eac..c9568803b9dc96 100644 --- a/x-pack/plugins/data_enhanced/server/plugin.ts +++ b/x-pack/plugins/data_enhanced/server/plugin.ts @@ -27,11 +27,13 @@ interface SetupDependencies { usageCollection?: UsageCollectionSetup; taskManager: TaskManagerSetupContract; } -interface StartDependencies { +export interface StartDependencies { + data: DataPluginStart; taskManager: TaskManagerStartContract; } -export class EnhancedDataServerPlugin implements Plugin { +export class EnhancedDataServerPlugin + implements Plugin { private readonly logger: Logger; private sessionService!: BackgroundSessionService; diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts index 39ddc5e8189f27..d41bc653689ad5 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts @@ -16,6 +16,7 @@ import { SavedObject, CoreSetup, SavedObjectsBulkUpdateObject, + SavedObjectsFindOptions, } from '../../../../../../src/core/server'; import { IKibanaSearchRequest, @@ -34,11 +35,7 @@ import { TaskManagerSetupContract, TaskManagerStartContract, } from '../../../../task_manager/server'; -import { - BackgroundSessionSavedObjectAttributes, - BackgroundSessionFindOptions, - BackgroundSessionStatus, -} from '../../../common'; +import { BackgroundSessionSavedObjectAttributes, BackgroundSessionStatus } from '../../../common'; import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; import { createRequestHash } from './utils'; import { ConfigSchema } from '../../../config'; @@ -301,7 +298,7 @@ export class BackgroundSessionService implements ISessionService { // TODO: Throw an error if this session doesn't belong to this user public find = ( - options: BackgroundSessionFindOptions, + options: SavedObjectsFindOptions, { savedObjectsClient }: BackgroundSessionDependencies ) => { return savedObjectsClient.find({ @@ -401,7 +398,7 @@ export class BackgroundSessionService implements ISessionService { save: (sessionId: string, attributes: Partial) => this.save(sessionId, attributes, deps), get: (sessionId: string) => this.get(sessionId, deps), - find: (options: BackgroundSessionFindOptions) => this.find(options, deps), + find: (options: SavedObjectsFindOptions) => this.find(options, deps), update: (sessionId: string, attributes: Partial) => this.update(sessionId, attributes, deps), delete: (sessionId: string) => this.delete(sessionId, deps), From 8a792d13f7282337a5779d5e40003f31710cf773 Mon Sep 17 00:00:00 2001 From: Liza K Date: Mon, 4 Jan 2021 18:27:11 +0200 Subject: [PATCH 34/58] Core review + use client.asyncSearch.status --- .../common/search/session/types.ts | 27 ++++++++++++ .../search/session/check_running_sessions.ts | 2 + .../search/session/get_search_status.ts | 42 ++++++++++++++----- .../search/session/get_session_status.ts | 7 ++-- 4 files changed, 64 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/data_enhanced/common/search/session/types.ts b/x-pack/plugins/data_enhanced/common/search/session/types.ts index f59055ad0560ab..79c02ffb4bd6b9 100644 --- a/x-pack/plugins/data_enhanced/common/search/session/types.ts +++ b/x-pack/plugins/data_enhanced/common/search/session/types.ts @@ -13,11 +13,38 @@ export interface BackgroundSessionSavedObjectAttributes { * App that created the session. e.g 'discover' */ appId: string; + /** + * Creation time of the session + */ created: string; + /** + * Expiration time of the session. Expiration itself is managed by Elasticsearch. + */ expires: string; + /** + * status + */ status: string; + /** + * An optional error. Set if status is set to error. + */ + error?: string; + /** + * urlGeneratorId + */ urlGeneratorId: string; + /** + * The application state that was used to create the session. + * Should be used, for example, to re-load an expired search session. + */ initialState: Record; + /** + * Application state that should be used to restore the session. + * For example, relative dates are conveted to absolute ones. + */ restoreState: Record; + /** + * Mapping of search request hashes to their corresponsing info (async search id, etc.) + */ idMapping: Record; } diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts index 022441ba8e80de..08688f3411ac0c 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts @@ -50,6 +50,8 @@ export async function checkRunningSessions( const sessionStatus = getSessionStatus(searchStatuses); if (sessionStatus !== BackgroundSessionStatus.IN_PROGRESS) { session.attributes.status = sessionStatus; + const firstError = searchStatuses.filter((searchInfo) => searchInfo.status === undefined); + session.attributes.error = firstError ? firstError[0].error : undefined; updatedSessions.push(session); } }) diff --git a/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts b/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts index 3b25ca99e05b2c..b8726a7cf2e060 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts @@ -8,25 +8,45 @@ import { ApiResponse } from '@elastic/elasticsearch'; import { ElasticsearchClient } from 'src/core/server'; import { isAsyncSearchStatusResponse, SearchStatus } from './types'; -export async function getSearchStatus(client: ElasticsearchClient, asyncId: string) { +export interface SearchStatusInfo { + status: SearchStatus; + error?: string; +} + +export async function getSearchStatus( + client: ElasticsearchClient, + asyncId: string +): Promise { try { - const path = encodeURI(`/_async_search/status/${asyncId}`); - const response: ApiResponse = await client.transport.request({ - path, - method: 'GET', - }); + // TODO: Handle strategies other than KQL + const response: ApiResponse = await client.asyncSearch.status({ id: asyncId }); if (isAsyncSearchStatusResponse(response)) { if ((response.is_partial && !response.is_running) || response.completion_status > 200) { - return SearchStatus.ERROR; + return { + status: SearchStatus.ERROR, + error: `Search completed with a ${response.completion_status} status`, + }; } else if (!response.is_partial && !response.is_running) { - return SearchStatus.COMPLETE; + return { + status: SearchStatus.COMPLETE, + }; } else { - return SearchStatus.IN_PROGRESS; + return { + status: SearchStatus.IN_PROGRESS, + }; } } else { - return SearchStatus.ERROR; + return { + status: SearchStatus.ERROR, + error: 'Unknown response format', + }; } } catch (e) { - return SearchStatus.ERROR; + // eslint-disable-next-line no-console + console.error(e); + return { + status: SearchStatus.ERROR, + error: e.message, + }; } } diff --git a/x-pack/plugins/data_enhanced/server/search/session/get_session_status.ts b/x-pack/plugins/data_enhanced/server/search/session/get_session_status.ts index 3b910ff3879138..e779e5a2d5b429 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/get_session_status.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/get_session_status.ts @@ -5,14 +5,15 @@ */ import { BackgroundSessionStatus } from '../../../common'; +import { SearchStatusInfo } from './get_search_status'; import { SearchStatus } from './types'; -export function getSessionStatus(searchStatuses: SearchStatus[]): BackgroundSessionStatus { - if (searchStatuses.some((item) => item === SearchStatus.ERROR)) { +export function getSessionStatus(searchStatuses: SearchStatusInfo[]): BackgroundSessionStatus { + if (searchStatuses.some((item) => item.status === SearchStatus.ERROR)) { return BackgroundSessionStatus.ERROR; } else if ( searchStatuses.length > 0 && - searchStatuses.every((item) => item === SearchStatus.COMPLETE) + searchStatuses.every((item) => item.status === SearchStatus.COMPLETE) ) { return BackgroundSessionStatus.COMPLETE; } else { From 202a56b6c1572f160fa1dd8973afc642c289299c Mon Sep 17 00:00:00 2001 From: Liza K Date: Mon, 4 Jan 2021 21:12:24 +0200 Subject: [PATCH 35/58] update ts --- .../search/session/get_session_status.test.ts | 27 ++++++++++--------- .../server/search/session/session_service.ts | 2 +- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/data_enhanced/server/search/session/get_session_status.test.ts b/x-pack/plugins/data_enhanced/server/search/session/get_session_status.test.ts index 1d72548a7e2164..a9bbafec44c365 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/get_session_status.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/get_session_status.test.ts @@ -7,36 +7,37 @@ import { SearchStatus } from './types'; import { getSessionStatus } from './get_session_status'; import { BackgroundSessionStatus } from '../../../common'; +import { SearchStatusInfo } from './get_search_status'; describe('getSessionStatus', () => { test("returns an in_progress status if there's nothing inside the session", () => { - const statuses: SearchStatus[] = []; + const statuses: SearchStatusInfo[] = []; expect(getSessionStatus(statuses)).toBe(BackgroundSessionStatus.IN_PROGRESS); }); test("returns an error status if there's at least one error", () => { - const statuses: SearchStatus[] = [ - SearchStatus.IN_PROGRESS, - SearchStatus.ERROR, - SearchStatus.COMPLETE, + const statuses: SearchStatusInfo[] = [ + { status: SearchStatus.IN_PROGRESS }, + { status: SearchStatus.ERROR, error: 'Nope' }, + { status: SearchStatus.COMPLETE }, ]; expect(getSessionStatus(statuses)).toBe(BackgroundSessionStatus.ERROR); }); test('returns a complete status if all are complete', () => { - const statuses: SearchStatus[] = [ - SearchStatus.COMPLETE, - SearchStatus.COMPLETE, - SearchStatus.COMPLETE, + const statuses: SearchStatusInfo[] = [ + { status: SearchStatus.COMPLETE }, + { status: SearchStatus.COMPLETE }, + { status: SearchStatus.COMPLETE }, ]; expect(getSessionStatus(statuses)).toBe(BackgroundSessionStatus.COMPLETE); }); test('returns a running status if some are still running', () => { - const statuses: SearchStatus[] = [ - SearchStatus.IN_PROGRESS, - SearchStatus.COMPLETE, - SearchStatus.COMPLETE, + const statuses: SearchStatusInfo[] = [ + { status: SearchStatus.IN_PROGRESS }, + { status: SearchStatus.COMPLETE }, + { status: SearchStatus.COMPLETE }, ]; expect(getSessionStatus(statuses)).toBe(BackgroundSessionStatus.IN_PROGRESS); }); diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts index d41bc653689ad5..4a2fd539ee2039 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts @@ -298,7 +298,7 @@ export class BackgroundSessionService implements ISessionService { // TODO: Throw an error if this session doesn't belong to this user public find = ( - options: SavedObjectsFindOptions, + options: Omit, { savedObjectsClient }: BackgroundSessionDependencies ) => { return savedObjectsClient.find({ From e41daa66d0dd0b0117a690054e7e88959c3ca478 Mon Sep 17 00:00:00 2001 From: Liza K Date: Tue, 5 Jan 2021 14:02:16 +0200 Subject: [PATCH 36/58] fix unit test --- .../search/session/check_running_sessions.test.ts | 10 +++++----- .../server/search/session/check_running_sessions.ts | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts index 73372baa6c8e7f..bc6236ffaf0a39 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts @@ -21,8 +21,8 @@ describe('getSearchStatus', () => { beforeEach(() => { savedObjectsClient = savedObjectsClientMock.create(); mockClient = { - transport: { - request: jest.fn(), + asyncSearch: { + status: jest.fn(), }, }; }); @@ -72,7 +72,7 @@ describe('getSearchStatus', () => { total: 1, } as any); - mockClient.transport.request.mockResolvedValue({ + mockClient.asyncSearch.status.mockResolvedValue({ is_partial: true, is_running: true, }); @@ -96,7 +96,7 @@ describe('getSearchStatus', () => { total: 1, } as any); - mockClient.transport.request.mockResolvedValue({ + mockClient.asyncSearch.status.mockResolvedValue({ is_partial: false, is_running: false, completion_status: 200, @@ -123,7 +123,7 @@ describe('getSearchStatus', () => { total: 1, } as any); - mockClient.transport.request.mockResolvedValue({ + mockClient.asyncSearch.status.mockResolvedValue({ is_partial: false, is_running: false, completion_status: 500, diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts index 08688f3411ac0c..05058864c35aa0 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts @@ -51,7 +51,7 @@ export async function checkRunningSessions( if (sessionStatus !== BackgroundSessionStatus.IN_PROGRESS) { session.attributes.status = sessionStatus; const firstError = searchStatuses.filter((searchInfo) => searchInfo.status === undefined); - session.attributes.error = firstError ? firstError[0].error : undefined; + session.attributes.error = firstError.length ? firstError[0].error : undefined; updatedSessions.push(session); } }) @@ -62,7 +62,7 @@ export async function checkRunningSessions( const updatedResponse = await savedObjectsClient.bulkUpdate( updatedSessions ); - logger.debug(`Updated ${updatedResponse.saved_objects.length} background sessios`); + logger.debug(`Updated ${updatedResponse.saved_objects.length} background sessions`); } } catch (err) { logger.error(err); From f3d25d3386e780aabdf93f7a769994d6a2a858b7 Mon Sep 17 00:00:00 2001 From: Liza K Date: Tue, 5 Jan 2021 14:31:58 +0200 Subject: [PATCH 37/58] code review fixes --- .../server/search/session/check_running_sessions.ts | 12 ++++++++---- .../server/search/session/get_search_status.ts | 13 ++++++++++--- .../data_enhanced/server/search/session/types.ts | 12 +----------- x-pack/plugins/data_enhanced/server/search/types.ts | 8 +++++++- 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts index 05058864c35aa0..80178805a308f6 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts @@ -10,7 +10,11 @@ import { SavedObjectsFindResult, SavedObjectsClientContract, } from 'kibana/server'; -import { BackgroundSessionStatus, BackgroundSessionSavedObjectAttributes } from '../../../common'; +import { + BackgroundSessionStatus, + BackgroundSessionSavedObjectAttributes, + BackgroundSessionSearchInfo, +} from '../../../common'; import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; import { getSearchStatus } from './get_search_status'; import { getSessionStatus } from './get_session_status'; @@ -32,7 +36,7 @@ export async function checkRunningSessions( if (!runningBackgroundSearchesResponse.total) return; - logger.debug(`Found ${runningBackgroundSearchesResponse.total} running sessios`); + logger.debug(`Found ${runningBackgroundSearchesResponse.total} running sessions`); const updatedSessions = new Array< SavedObjectsFindResult @@ -42,8 +46,8 @@ export async function checkRunningSessions( runningBackgroundSearchesResponse.saved_objects.map(async (session) => { const searchIds = Object.values(session.attributes.idMapping); const searchStatuses = await Promise.all( - searchIds.map(async (searchId: string) => { - return await getSearchStatus(client, searchId); + searchIds.map(async (sessionInfo: BackgroundSessionSearchInfo) => { + return await getSearchStatus(client, sessionInfo.strategy); }) ); diff --git a/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts b/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts index b8726a7cf2e060..71f022fbc465f7 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts @@ -4,9 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ +import { i18n } from '@kbn/i18n'; import { ApiResponse } from '@elastic/elasticsearch'; import { ElasticsearchClient } from 'src/core/server'; import { isAsyncSearchStatusResponse, SearchStatus } from './types'; +import { AsyncSearchResponse } from '../types'; export interface SearchStatusInfo { status: SearchStatus; @@ -19,12 +21,17 @@ export async function getSearchStatus( ): Promise { try { // TODO: Handle strategies other than KQL - const response: ApiResponse = await client.asyncSearch.status({ id: asyncId }); + const response: ApiResponse = await client.asyncSearch.status({ + id: asyncId, + }); if (isAsyncSearchStatusResponse(response)) { - if ((response.is_partial && !response.is_running) || response.completion_status > 200) { + if ((response.is_partial && !response.is_running) || response.completion_status >= 400) { return { status: SearchStatus.ERROR, - error: `Search completed with a ${response.completion_status} status`, + error: i18n.translate('xpack.data.search.statusError', { + defaultMessage: `Search completed with a {errorCode} status`, + values: { errorCode: response.completion_status }, + }), }; } else if (!response.is_partial && !response.is_running) { return { diff --git a/x-pack/plugins/data_enhanced/server/search/session/types.ts b/x-pack/plugins/data_enhanced/server/search/session/types.ts index 207218ede8f9cb..4600ccb3a9bc8a 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/types.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ShardsResponse } from 'elasticsearch'; +import { AsyncSearchStatusResponse } from '../types'; export enum SearchStatus { IN_PROGRESS = 'in_progress', @@ -12,16 +12,6 @@ export enum SearchStatus { COMPLETE = 'complete', } -export interface AsyncSearchStatusResponse { - id?: string; - is_partial: boolean; - is_running: boolean; - start_time_in_millis: number; - expiration_time_in_millis: number; - completion_status: number; - _shards: ShardsResponse; -} - export function isAsyncSearchStatusResponse(response: any): response is AsyncSearchStatusResponse { return response.is_partial !== undefined && response.is_running !== undefined; } diff --git a/x-pack/plugins/data_enhanced/server/search/types.ts b/x-pack/plugins/data_enhanced/server/search/types.ts index f01ac51a1516e1..b052d162ee427b 100644 --- a/x-pack/plugins/data_enhanced/server/search/types.ts +++ b/x-pack/plugins/data_enhanced/server/search/types.ts @@ -4,14 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SearchResponse } from 'elasticsearch'; +import { SearchResponse, ShardsResponse } from 'elasticsearch'; export interface AsyncSearchResponse { id?: string; response: SearchResponse; + start_time_in_millis: number; + expiration_time_in_millis: number; is_partial: boolean; is_running: boolean; } +export interface AsyncSearchStatusResponse extends Omit { + completion_status: number; + _shards: ShardsResponse; +} export interface EqlSearchResponse extends SearchResponse { id?: string; From 44fcbf79255530c9822275ef23ac8f0470e3a64e Mon Sep 17 00:00:00 2001 From: Liza K Date: Tue, 5 Jan 2021 15:22:30 +0200 Subject: [PATCH 38/58] Save individual search errors on SO --- .../common/search/session/types.ts | 22 ++++++--- .../session/check_running_sessions.test.ts | 36 ++++++++++---- .../search/session/check_running_sessions.ts | 33 ++++++++----- .../search/session/get_search_status.test.ts | 16 +++--- .../search/session/get_search_status.ts | 8 +-- .../search/session/get_session_status.test.ts | 49 +++++++++++-------- .../search/session/get_session_status.ts | 8 +-- .../server/search/session/session_service.ts | 2 + 8 files changed, 108 insertions(+), 66 deletions(-) diff --git a/x-pack/plugins/data_enhanced/common/search/session/types.ts b/x-pack/plugins/data_enhanced/common/search/session/types.ts index 4cc23a28f87d84..6feaac044202ab 100644 --- a/x-pack/plugins/data_enhanced/common/search/session/types.ts +++ b/x-pack/plugins/data_enhanced/common/search/session/types.ts @@ -25,10 +25,6 @@ export interface BackgroundSessionSavedObjectAttributes { * status */ status: string; - /** - * An optional error. Set if status is set to error. - */ - error?: string; /** * urlGeneratorId */ @@ -50,6 +46,20 @@ export interface BackgroundSessionSavedObjectAttributes { } export interface BackgroundSessionSearchInfo { - id: string; // ID of the async search request - strategy: string; // Search strategy used to submit the search request + /** + * ID of the async search request + */ + id: string; + /** + * Search strategy used to submit the search request + */ + strategy: string; + /** + * status + */ + status: string; + /** + * An optional error. Set if status is set to error. + */ + error?: string; } diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts index bc6236ffaf0a39..dd2a56396cde20 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts @@ -8,6 +8,7 @@ import { checkRunningSessions } from './check_running_sessions'; import { BackgroundSessionSavedObjectAttributes, BackgroundSessionStatus } from '../../../common'; import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; import type { SavedObjectsClientContract } from 'kibana/server'; +import { SearchStatus } from './types'; describe('getSearchStatus', () => { let mockClient: any; @@ -62,9 +63,11 @@ describe('getSearchStatus', () => { savedObjectsClient.find.mockResolvedValue({ saved_objects: [ { - attributes: { - idMapping: { - 'search-hash': 'search-id', + idMapping: { + 'search-hash': { + id: 'search-id', + strategy: 'cool', + status: SearchStatus.IN_PROGRESS, }, }, }, @@ -87,7 +90,11 @@ describe('getSearchStatus', () => { const so = { attributes: { idMapping: { - 'search-hash': 'search-id', + 'search-hash': { + id: 'search-id', + strategy: 'cool', + status: SearchStatus.IN_PROGRESS, + }, }, }, }; @@ -104,9 +111,10 @@ describe('getSearchStatus', () => { await checkRunningSessions(savedObjectsClient, mockClient, mockLogger); const [updateInput] = savedObjectsClient.bulkUpdate.mock.calls[0]; - expect((updateInput[0].attributes as BackgroundSessionSavedObjectAttributes).status).toBe( - BackgroundSessionStatus.COMPLETE - ); + const updatedAttributes = updateInput[0].attributes as BackgroundSessionSavedObjectAttributes; + expect(updatedAttributes.status).toBe(BackgroundSessionStatus.COMPLETE); + expect(updatedAttributes.idMapping['search-hash'].status).toBe(SearchStatus.COMPLETE); + expect(updatedAttributes.idMapping['search-hash'].error).toBeUndefined(); }); test('updates to error if the search is errored', async () => { @@ -114,7 +122,11 @@ describe('getSearchStatus', () => { const so = { attributes: { idMapping: { - 'search-hash': 'search-id', + 'search-hash': { + id: 'search-id', + strategy: 'cool', + status: SearchStatus.IN_PROGRESS, + }, }, }, }; @@ -131,8 +143,12 @@ describe('getSearchStatus', () => { await checkRunningSessions(savedObjectsClient, mockClient, mockLogger); const [updateInput] = savedObjectsClient.bulkUpdate.mock.calls[0]; - expect((updateInput[0].attributes as BackgroundSessionSavedObjectAttributes).status).toBe( - BackgroundSessionStatus.ERROR + + const updatedAttributes = updateInput[0].attributes as BackgroundSessionSavedObjectAttributes; + expect(updatedAttributes.status).toBe(BackgroundSessionStatus.ERROR); + expect(updatedAttributes.idMapping['search-hash'].status).toBe(SearchStatus.ERROR); + expect(updatedAttributes.idMapping['search-hash'].error).toBe( + 'Search completed with a 500 status' ); }); }); diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts index 80178805a308f6..c00047e38d1cb6 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts @@ -10,14 +10,11 @@ import { SavedObjectsFindResult, SavedObjectsClientContract, } from 'kibana/server'; -import { - BackgroundSessionStatus, - BackgroundSessionSavedObjectAttributes, - BackgroundSessionSearchInfo, -} from '../../../common'; +import { BackgroundSessionStatus, BackgroundSessionSavedObjectAttributes } from '../../../common'; import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; import { getSearchStatus } from './get_search_status'; import { getSessionStatus } from './get_session_status'; +import { SearchStatus } from './types'; export async function checkRunningSessions( savedObjectsClient: SavedObjectsClientContract, @@ -42,20 +39,32 @@ export async function checkRunningSessions( SavedObjectsFindResult >(); + let sessionUpdated = false; + await Promise.all( runningBackgroundSearchesResponse.saved_objects.map(async (session) => { - const searchIds = Object.values(session.attributes.idMapping); - const searchStatuses = await Promise.all( - searchIds.map(async (sessionInfo: BackgroundSessionSearchInfo) => { - return await getSearchStatus(client, sessionInfo.strategy); + await Promise.all( + Object.keys(session.attributes.idMapping).map(async (searchKey: string) => { + const searchInfo = session.attributes.idMapping[searchKey]; + if (searchInfo.status === SearchStatus.IN_PROGRESS) { + const searchStatus = await getSearchStatus(client, searchInfo.strategy); + + sessionUpdated = true; + session.attributes.idMapping[searchKey] = { + ...session.attributes.idMapping[searchKey], + ...searchStatus, + }; + } }) ); - const sessionStatus = getSessionStatus(searchStatuses); + const sessionStatus = getSessionStatus(session.attributes); if (sessionStatus !== BackgroundSessionStatus.IN_PROGRESS) { session.attributes.status = sessionStatus; - const firstError = searchStatuses.filter((searchInfo) => searchInfo.status === undefined); - session.attributes.error = firstError.length ? firstError[0].error : undefined; + sessionUpdated = true; + } + + if (sessionUpdated) { updatedSessions.push(session); } }) diff --git a/x-pack/plugins/data_enhanced/server/search/session/get_search_status.test.ts b/x-pack/plugins/data_enhanced/server/search/session/get_search_status.test.ts index 37d7219c82450a..2aaa8d5203aaf0 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/get_search_status.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/get_search_status.test.ts @@ -11,14 +11,14 @@ describe('getSearchStatus', () => { let mockClient: any; beforeEach(() => { mockClient = { - transport: { - request: jest.fn(), + asyncSearch: { + status: jest.fn(), }, }; }); test('returns an error status if search is partial and not running', () => { - mockClient.transport.request.mockResolvedValue({ + mockClient.asyncSearch.status.mockResolvedValue({ is_partial: true, is_running: false, completion_status: 200, @@ -27,7 +27,7 @@ describe('getSearchStatus', () => { }); test('returns an error status if completion_status is an error', () => { - mockClient.transport.request.mockResolvedValue({ + mockClient.asyncSearch.status.mockResolvedValue({ is_partial: false, is_running: false, completion_status: 500, @@ -36,7 +36,7 @@ describe('getSearchStatus', () => { }); test('returns an error status if gets an ES error', () => { - mockClient.transport.request.mockResolvedValue({ + mockClient.asyncSearch.status.mockResolvedValue({ error: { root_cause: { reason: 'not found', @@ -47,12 +47,12 @@ describe('getSearchStatus', () => { }); test('returns an error status throws', () => { - mockClient.transport.request.mockRejectedValue(new Error('O_o')); + mockClient.asyncSearch.status.mockRejectedValue(new Error('O_o')); expect(getSearchStatus(mockClient, '123')).resolves.toBe(SearchStatus.ERROR); }); test('returns a complete status', () => { - mockClient.transport.request.mockResolvedValue({ + mockClient.asyncSearch.status.mockResolvedValue({ is_partial: false, is_running: false, completion_status: 200, @@ -61,7 +61,7 @@ describe('getSearchStatus', () => { }); test('returns a running status otherwise', () => { - mockClient.transport.request.mockResolvedValue({ + mockClient.asyncSearch.status.mockResolvedValue({ is_partial: false, is_running: true, completion_status: undefined, diff --git a/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts b/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts index 71f022fbc465f7..f3f759cb7e8f12 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts @@ -9,16 +9,12 @@ import { ApiResponse } from '@elastic/elasticsearch'; import { ElasticsearchClient } from 'src/core/server'; import { isAsyncSearchStatusResponse, SearchStatus } from './types'; import { AsyncSearchResponse } from '../types'; - -export interface SearchStatusInfo { - status: SearchStatus; - error?: string; -} +import { BackgroundSessionSearchInfo } from '../../../common'; export async function getSearchStatus( client: ElasticsearchClient, asyncId: string -): Promise { +): Promise> { try { // TODO: Handle strategies other than KQL const response: ApiResponse = await client.asyncSearch.status({ diff --git a/x-pack/plugins/data_enhanced/server/search/session/get_session_status.test.ts b/x-pack/plugins/data_enhanced/server/search/session/get_session_status.test.ts index a9bbafec44c365..1d394af732b76d 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/get_session_status.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/get_session_status.test.ts @@ -7,38 +7,45 @@ import { SearchStatus } from './types'; import { getSessionStatus } from './get_session_status'; import { BackgroundSessionStatus } from '../../../common'; -import { SearchStatusInfo } from './get_search_status'; describe('getSessionStatus', () => { test("returns an in_progress status if there's nothing inside the session", () => { - const statuses: SearchStatusInfo[] = []; - expect(getSessionStatus(statuses)).toBe(BackgroundSessionStatus.IN_PROGRESS); + const session: any = { + idMapping: {}, + }; + expect(getSessionStatus(session)).toBe(BackgroundSessionStatus.IN_PROGRESS); }); test("returns an error status if there's at least one error", () => { - const statuses: SearchStatusInfo[] = [ - { status: SearchStatus.IN_PROGRESS }, - { status: SearchStatus.ERROR, error: 'Nope' }, - { status: SearchStatus.COMPLETE }, - ]; - expect(getSessionStatus(statuses)).toBe(BackgroundSessionStatus.ERROR); + const session: any = { + idMapping: { + a: { status: SearchStatus.IN_PROGRESS }, + b: { status: SearchStatus.ERROR, error: 'Nope' }, + c: { status: SearchStatus.COMPLETE }, + }, + }; + expect(getSessionStatus(session)).toBe(BackgroundSessionStatus.ERROR); }); test('returns a complete status if all are complete', () => { - const statuses: SearchStatusInfo[] = [ - { status: SearchStatus.COMPLETE }, - { status: SearchStatus.COMPLETE }, - { status: SearchStatus.COMPLETE }, - ]; - expect(getSessionStatus(statuses)).toBe(BackgroundSessionStatus.COMPLETE); + const session: any = { + idMapping: { + a: { status: SearchStatus.COMPLETE }, + b: { status: SearchStatus.COMPLETE }, + c: { status: SearchStatus.COMPLETE }, + }, + }; + expect(getSessionStatus(session)).toBe(BackgroundSessionStatus.COMPLETE); }); test('returns a running status if some are still running', () => { - const statuses: SearchStatusInfo[] = [ - { status: SearchStatus.IN_PROGRESS }, - { status: SearchStatus.COMPLETE }, - { status: SearchStatus.COMPLETE }, - ]; - expect(getSessionStatus(statuses)).toBe(BackgroundSessionStatus.IN_PROGRESS); + const session: any = { + idMapping: { + a: { status: SearchStatus.IN_PROGRESS }, + b: { status: SearchStatus.COMPLETE }, + c: { status: SearchStatus.IN_PROGRESS }, + }, + }; + expect(getSessionStatus(session)).toBe(BackgroundSessionStatus.IN_PROGRESS); }); }); diff --git a/x-pack/plugins/data_enhanced/server/search/session/get_session_status.ts b/x-pack/plugins/data_enhanced/server/search/session/get_session_status.ts index e779e5a2d5b429..2c78d15dd1a96a 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/get_session_status.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/get_session_status.ts @@ -4,11 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { BackgroundSessionStatus } from '../../../common'; -import { SearchStatusInfo } from './get_search_status'; +import { BackgroundSessionSavedObjectAttributes, BackgroundSessionStatus } from '../../../common'; import { SearchStatus } from './types'; -export function getSessionStatus(searchStatuses: SearchStatusInfo[]): BackgroundSessionStatus { +export function getSessionStatus( + session: BackgroundSessionSavedObjectAttributes +): BackgroundSessionStatus { + const searchStatuses = Object.values(session.idMapping); if (searchStatuses.some((item) => item.status === SearchStatus.ERROR)) { return BackgroundSessionStatus.ERROR; } else if ( diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts index 422248ef45f8a2..813b5473a9d03a 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts @@ -51,6 +51,7 @@ import { INMEM_TRACKING_TIMEOUT_SEC, MAX_UPDATE_RETRIES, } from './constants'; +import { SearchStatus } from './types'; export interface BackgroundSessionDependencies { savedObjectsClient: SavedObjectsClientContract; @@ -347,6 +348,7 @@ export class BackgroundSessionService implements ISessionService { const searchInfo = { id: searchId, strategy: strategy!, + status: SearchStatus.IN_PROGRESS, }; // If there is already a saved object for this session, update it to include this request/ID. From 4be1a93b6d6c5812be66ab99e743c887cc4617ec Mon Sep 17 00:00:00 2001 From: Liza K Date: Tue, 5 Jan 2021 16:17:08 +0200 Subject: [PATCH 39/58] Don't re-fetch completed or errored searches --- .../session/check_running_sessions.test.ts | 49 +++++++++++++++---- .../search/session/check_running_sessions.ts | 16 +++--- .../search/session/session_service.test.ts | 2 + 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts index dd2a56396cde20..a964803bf713db 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts @@ -60,18 +60,19 @@ describe('getSearchStatus', () => { test('does nothing if the search is still running', async () => { savedObjectsClient.bulkUpdate = jest.fn(); - savedObjectsClient.find.mockResolvedValue({ - saved_objects: [ - { - idMapping: { - 'search-hash': { - id: 'search-id', - strategy: 'cool', - status: SearchStatus.IN_PROGRESS, - }, + const so = { + attributes: { + idMapping: { + 'search-hash': { + id: 'search-id', + strategy: 'cool', + status: SearchStatus.IN_PROGRESS, }, }, - ], + }, + }; + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [so], total: 1, } as any); @@ -85,6 +86,34 @@ describe('getSearchStatus', () => { expect(savedObjectsClient.bulkUpdate).not.toBeCalled(); }); + test("doesn't re-check completed or errored searches", async () => { + savedObjectsClient.bulkUpdate = jest.fn(); + const so = { + attributes: { + idMapping: { + 'search-hash': { + id: 'search-id', + strategy: 'cool', + status: SearchStatus.COMPLETE, + }, + 'another-search-hash': { + id: 'search-id', + strategy: 'cool', + status: SearchStatus.ERROR, + }, + }, + }, + }; + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [so], + total: 1, + } as any); + + await checkRunningSessions(savedObjectsClient, mockClient, mockLogger); + + expect(mockClient.asyncSearch.status).not.toBeCalled(); + }); + test('updates to complete if the search is done', async () => { savedObjectsClient.bulkUpdate = jest.fn(); const so = { diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts index c00047e38d1cb6..8275a8e10a3bef 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts @@ -43,21 +43,25 @@ export async function checkRunningSessions( await Promise.all( runningBackgroundSearchesResponse.saved_objects.map(async (session) => { + // Check statuses of all running searches await Promise.all( Object.keys(session.attributes.idMapping).map(async (searchKey: string) => { const searchInfo = session.attributes.idMapping[searchKey]; if (searchInfo.status === SearchStatus.IN_PROGRESS) { - const searchStatus = await getSearchStatus(client, searchInfo.strategy); + const currentStatus = await getSearchStatus(client, searchInfo.strategy); - sessionUpdated = true; - session.attributes.idMapping[searchKey] = { - ...session.attributes.idMapping[searchKey], - ...searchStatus, - }; + if (currentStatus.status !== SearchStatus.IN_PROGRESS) { + sessionUpdated = true; + session.attributes.idMapping[searchKey] = { + ...session.attributes.idMapping[searchKey], + ...currentStatus, + }; + } } }) ); + // And only then derive the session's status const sessionStatus = getSessionStatus(session.attributes); if (sessionStatus !== BackgroundSessionStatus.IN_PROGRESS) { session.attributes.status = sessionStatus; diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts index a406d0d59d5a67..3ae10a5b631898 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts @@ -21,6 +21,7 @@ import { coreMock } from 'src/core/server/mocks'; import { ConfigSchema } from '../../../config'; import { taskManagerMock } from '../../../../task_manager/server/mocks'; import { INMEM_TRACKING_INTERVAL, MAX_UPDATE_RETRIES } from './constants'; +import { SearchStatus } from './types'; const flushPromises = () => new Promise((resolve) => setImmediate(resolve)); @@ -340,6 +341,7 @@ describe('BackgroundSessionService', () => { [requestHash]: { id: searchId, strategy: MOCK_STRATEGY, + status: SearchStatus.IN_PROGRESS, }, }, }); From 36ad90c5ee819466ca4e4307ebe3bd831dacb274 Mon Sep 17 00:00:00 2001 From: Liza K Date: Wed, 6 Jan 2021 16:54:18 +0200 Subject: [PATCH 40/58] Rename Background Sessions to Search Sessions (with a send to background action) --- .../search_examples/public/components/app.tsx | 2 +- rfcs/text/0013_background_sessions.md | 92 +++++------ src/plugins/data/public/index.ts | 2 +- src/plugins/data/public/public.api.md | 2 +- src/plugins/data/public/search/index.ts | 2 +- .../data/public/search/session/index.ts | 2 +- .../data/public/search/session/mocks.ts | 4 +- ...e.test.ts => search_session_state.test.ts} | 48 +++--- ...ssion_state.ts => search_session_state.ts} | 36 +++-- .../search/session/session_service.test.ts | 16 +- .../public/search/session/session_service.ts | 16 +- .../public/search/session/sessions_client.ts | 2 +- src/plugins/data/public/search/types.ts | 4 +- .../server/search/session/session_service.ts | 2 +- .../public/application/angular/discover.js | 2 +- x-pack/plugins/data_enhanced/common/index.ts | 8 +- .../common/search/session/status.ts | 2 +- .../common/search/session/types.ts | 8 +- x-pack/plugins/data_enhanced/public/plugin.ts | 4 +- .../public/search/search_interceptor.test.ts | 8 +- .../public/search/search_interceptor.ts | 4 +- .../background_session_indicator.stories.tsx | 39 ----- ...nnected_search_session_indicator.test.tsx} | 44 +++-- .../connected_search_session_indicator.tsx} | 14 +- .../index.ts | 6 +- .../search_session_view_state.ts} | 6 +- .../data_enhanced/public/search/ui/index.ts | 2 +- .../index.tsx | 10 +- .../search_session_indicator.scss} | 6 +- .../search_session_indicator.stories.tsx | 39 +++++ .../search_session_indicator.test.tsx} | 26 +-- .../search_session_indicator.tsx} | 153 ++++++++---------- x-pack/plugins/data_enhanced/server/plugin.ts | 10 +- .../server/saved_objects/index.ts | 2 +- ...ackground_session.ts => search_session.ts} | 6 +- .../search/session/session_service.test.ts | 34 ++-- .../server/search/session/session_service.ts | 82 +++++----- .../services/send_to_background.ts | 14 +- 38 files changed, 376 insertions(+), 383 deletions(-) rename src/plugins/data/public/search/session/{session_state.test.ts => search_session_state.test.ts} (61%) rename src/plugins/data/public/search/session/{session_state.ts => search_session_state.ts} (86%) delete mode 100644 x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.stories.tsx rename x-pack/plugins/data_enhanced/public/search/ui/{connected_background_session_indicator/connected_background_session_indicator.test.tsx => connected_search_session_indicator/connected_search_session_indicator.test.tsx} (62%) rename x-pack/plugins/data_enhanced/public/search/ui/{connected_background_session_indicator/connected_background_session_indicator.tsx => connected_search_session_indicator/connected_search_session_indicator.tsx} (85%) rename x-pack/plugins/data_enhanced/public/search/ui/{connected_background_session_indicator => connected_search_session_indicator}/index.ts (65%) rename x-pack/plugins/data_enhanced/public/search/ui/{connected_background_session_indicator/background_session_view_state.ts => connected_search_session_indicator/search_session_view_state.ts} (85%) rename x-pack/plugins/data_enhanced/public/search/ui/{background_session_indicator => search_session_indicator}/index.tsx (57%) rename x-pack/plugins/data_enhanced/public/search/ui/{background_session_indicator/background_session_indicator.scss => search_session_indicator/search_session_indicator.scss} (69%) create mode 100644 x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.stories.tsx rename x-pack/plugins/data_enhanced/public/search/ui/{background_session_indicator/background_session_indicator.test.tsx => search_session_indicator/search_session_indicator.test.tsx} (69%) rename x-pack/plugins/data_enhanced/public/search/ui/{background_session_indicator/background_session_indicator.tsx => search_session_indicator/search_session_indicator.tsx} (57%) rename x-pack/plugins/data_enhanced/server/saved_objects/{background_session.ts => search_session.ts} (86%) diff --git a/examples/search_examples/public/components/app.tsx b/examples/search_examples/public/components/app.tsx index 33ad8bbfe3d357..afdcc8d4a8bd6f 100644 --- a/examples/search_examples/public/components/app.tsx +++ b/examples/search_examples/public/components/app.tsx @@ -295,7 +295,7 @@ export const SearchExamplesApp = ({ Index Pattern Promise + store: (sessionId: string, name: string, url: string) => Promise /** * @returns Is the current session stored (i.e. is there a saved object corresponding with this sessionId). @@ -188,17 +188,17 @@ interface ISessionService { /** * @param sessionId the ID of the session to retrieve the saved object. - * @returns a filtered list of BackgroundSessionAttributes objects. + * @returns a filtered list of SearchSessionAttributes objects. * @throws Throws an error in OSS. */ - get: (sessionId: string) => Promise + get: (sessionId: string) => Promise /** - * @param options The options to query for specific background session saved objects. - * @returns a filtered list of BackgroundSessionAttributes objects. + * @param options The options to query for specific search session saved objects. + * @returns a filtered list of SearchSessionAttributes objects. * @throws Throws an error in OSS. */ - list: (options: SavedObjectsFindOptions) => Promise + list: (options: SavedObjectsFindOptions) => Promise /** * Clears out any session info as well as the current session. Called internally whenever the user navigates @@ -241,12 +241,12 @@ attempt to find the correct id within the saved object, and use it to retrieve t ```ts interface ISessionService { /** - * Adds a search ID to a Background Session, if it exists. + * Adds a search ID to a Search Session, if it exists. * Also extends the expiration of the search ID to match the session's expiration. * @param request * @param sessionId * @param searchId - * @returns true if id was added, false if Background Session doesn't exist or if there was an error while updating. + * @returns true if id was added, false if Search Session doesn't exist or if there was an error while updating. * @throws an error if `searchId` already exists in the mapping for this `sessionId` */ trackSearchId: ( @@ -256,21 +256,21 @@ interface ISessionService { ) => Promise /** - * Get a Background Session object. + * Get a Search Session object. * @param request * @param sessionId - * @returns the Background Session object if exists, or undefined. + * @returns the Search Session object if exists, or undefined. */ get: async ( request: KibanaRequest, sessionId: string - ) => Promise + ) => Promise /** - * Get a searchId from a Background Session object. + * Get a searchId from a Search Session object. * @param request * @param sessionId - * @returns the searchID if exists on the Background Session, or undefined. + * @returns the searchID if exists on the Search Session, or undefined. */ getSearchId: async ( request: KibanaRequest, @@ -283,7 +283,7 @@ interface ISessionService { * @param sessionId Session ID to store. Probably retrieved from `sessionService.get()`. * @param searchIdMap A mapping of hashed requests mapped to the corresponding searchId. * @param url TODO: is the URL provided here? How? - * @returns The stored `BackgroundSessionAttributes` object + * @returns The stored `SearchSessionAttributes` object * @throws Throws an error in OSS. * @internal (Consumers should use searchInterceptor.sendToBackground()) */ @@ -293,7 +293,7 @@ interface ISessionService { name: string, url: string, searchIdMapping?: Record - ) => Promise + ) => Promise /** * Mark a session as and all associated searchIds as expired. @@ -322,7 +322,7 @@ interface ISessionService { ) => Promise /** - * Get a list of background session objects. + * Get a list of Search Session objects. * @param request * @param sessionId * @returns success status @@ -330,7 +330,7 @@ interface ISessionService { */ list: async ( request: KibanaRequest, - ) => Promise + ) => Promise /** * Update the status of a given session @@ -343,7 +343,7 @@ interface ISessionService { updateStatus: async ( request: KibanaRequest, sessionId: string, - status: BackgroundSessionStatus + status: SearchSessionStatus ) => Promise } @@ -381,13 +381,13 @@ Each route exposes the corresponding method from the Session Service (used only ### Search Strategy Integration -If the `EnhancedEsSearchStrategy` receives a `restore` option, it will attempt reloading data using the Background Session saved object matching the provided `sessionId`. If there are any errors during that process, the strategy will return an error response and *not attempt to re-run the request. +If the `EnhancedEsSearchStrategy` receives a `restore` option, it will attempt reloading data using the Search Session saved object matching the provided `sessionId`. If there are any errors during that process, the strategy will return an error response and *not attempt to re-run the request. The strategy will track the asyncId on the server side, if `trackId` option is provided. ### Monitoring Service -The `data` plugin will register a task with the task manager, periodically monitoring the status of incomplete background sessions. +The `data` plugin will register a task with the task manager, periodically monitoring the status of incomplete search sessions. It will query the list of all incomplete sessions, and check the status of each search that is executing. If the search requests are all complete, it will update the corresponding saved object to have a `status` of `complete`. If any of the searches return an error, it will update the saved object to an `error` state. If the search requests have expired, it will update the saved object to an `expired` state. Expired sessions will be purged once they are older than the time definedby the `EXPIRED_SESSION_TTL` advanced setting. @@ -405,23 +405,23 @@ There are two potential scenarios: Both scenarios require careful attention during the UI design and implementation. -The former can be resolved by clearly displaying the creation time of the restored Background Session. We could also attempt translating relative dates to absolute one's, but this might be challenging as relative dates may appear deeply nested within the DSL. +The former can be resolved by clearly displaying the creation time of the restored Search Session. We could also attempt translating relative dates to absolute one's, but this might be challenging as relative dates may appear deeply nested within the DSL. The latter case happens at the moment for the timepicker only: The relative date is being translated each time into an absolute one, before being sent to Elasticsearch. In order to avoid issues, we'll have to make sure that restore URLs are generated with an absolute date, to make sure they are restored correctly. #### Changing a restored session -If you have restored a Background Session, making any type of change to it (time range, filters, etc.) will trigger new (potentially long) searches. There should be a clear indication in the UI that the data is no longer stored. A user then may choose to send it to background, resulting in a new Background Session being saved. +If you have restored a Search Session, making any type of change to it (time range, filters, etc.) will trigger new (potentially long) searches. There should be a clear indication in the UI that the data is no longer stored. A user then may choose to send it to background, resulting in a new Search Session being saved. #### Loading an errored \ expired \ canceled session -When trying to restore a Background Session, if any of the requests hashes don't match the ones saved, or if any of the saved async search IDs are expired, a meaningful error code will be returned by the server **by those requests**. It is each application's responsibility to handle these errors appropriately. +When trying to restore a Search Session, if any of the requests hashes don't match the ones saved, or if any of the saved async search IDs are expired, a meaningful error code will be returned by the server **by those requests**. It is each application's responsibility to handle these errors appropriately. In such a scenario, the session will be partially restored. #### Extending Expiration -Sessions are given an expiration date defined in an advanced setting (5 days by default). This expiration date is measured from the time the Background Session is saved, and it includes the time it takes to generate the results. +Sessions are given an expiration date defined in an advanced setting (5 days by default). This expiration date is measured from the time the Search Session is saved, and it includes the time it takes to generate the results. A session's expiration date may be extended indefinitely. However, if a session was canceled or has already expired, it needs to be re-run. @@ -444,7 +444,7 @@ so we feel comfortable moving forward with this approach. Two potential drawbacks stem from storing things in server memory. If a Kibana server is restarted, in-memory results will be lost. (This can be an issue if a search request has started, and the user has sent to background, but the -background session saved object has not yet been updated with the search request ID.) In such cases, the user interface +search session saved object has not yet been updated with the search request ID.) In such cases, the user interface will need to indicate errors for requests that were not stored in the saved object. There is also the consideration of the memory footprint of the Kibana server; however, since @@ -452,7 +452,7 @@ we are only storing a hash of the request and search request ID, and are periodi Services and Routes), we do not anticipate the footprint to increase significantly. The results of search requests that have been sent to the background will be stored in Elasticsearch for several days, -even if they will only be retrieved once. This will be mitigated by allowing the user manually delete a background +even if they will only be retrieved once. This will be mitigated by allowing the user manually delete a search session object after it has been accessed. # Alternatives @@ -463,7 +463,7 @@ What other designs have been considered? What is the impact of not doing this? (See "Basic example" above.) -Any application or solution that uses the `data` plugin `search` services will be able to facilitate background sessions +Any application or solution that uses the `data` plugin `search` services will be able to facilitate search sessions fairly simply. The public side will need to create/clear sessions when appropriate, and ensure the `sessionId` is sent with all search requests. It will also need to ensure that any necessary application data, as well as a `restoreUrl` is sent when creating the saved object. diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 7b15e2576e704a..bcb65aa0ee205b 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -385,7 +385,7 @@ export { SearchRequest, SearchSourceFields, SortDirection, - SessionState, + SearchSessionState, // expression functions and types EsdslExpressionFunctionDefinition, EsRawResponseExpressionTypeDefinition, diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 3493844a71ac15..a824f502cbc2d9 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -2419,7 +2419,7 @@ export class SearchTimeoutError extends KbnError { } // @public -export enum SessionState { +export enum SearchSessionState { BackgroundCompleted = "backgroundCompleted", BackgroundLoading = "backgroundLoading", Canceled = "canceled", diff --git a/src/plugins/data/public/search/index.ts b/src/plugins/data/public/search/index.ts index 2a767d1bf7c0d1..47aedd49d66e9e 100644 --- a/src/plugins/data/public/search/index.ts +++ b/src/plugins/data/public/search/index.ts @@ -45,7 +45,7 @@ export { SessionService, ISessionService, SearchSessionInfoProvider, - SessionState, + SearchSessionState, SessionsClient, ISessionsClient, } from './session'; diff --git a/src/plugins/data/public/search/session/index.ts b/src/plugins/data/public/search/session/index.ts index ee0121aacad5e8..a40b8857fc0e09 100644 --- a/src/plugins/data/public/search/session/index.ts +++ b/src/plugins/data/public/search/session/index.ts @@ -18,5 +18,5 @@ */ export { SessionService, ISessionService, SearchSessionInfoProvider } from './session_service'; -export { SessionState } from './session_state'; +export { SearchSessionState } from './search_session_state'; export { SessionsClient, ISessionsClient } from './sessions_client'; diff --git a/src/plugins/data/public/search/session/mocks.ts b/src/plugins/data/public/search/session/mocks.ts index 0ff99b3080365a..ea0cd8be03f273 100644 --- a/src/plugins/data/public/search/session/mocks.ts +++ b/src/plugins/data/public/search/session/mocks.ts @@ -20,7 +20,7 @@ import { BehaviorSubject, Subject } from 'rxjs'; import { ISessionsClient } from './sessions_client'; import { ISessionService } from './session_service'; -import { SessionState } from './session_state'; +import { SearchSessionState } from './search_session_state'; export function getSessionsClientMock(): jest.Mocked { return { @@ -39,7 +39,7 @@ export function getSessionServiceMock(): jest.Mocked { restore: jest.fn(), getSessionId: jest.fn(), getSession$: jest.fn(() => new BehaviorSubject(undefined).asObservable()), - state$: new BehaviorSubject(SessionState.None).asObservable(), + state$: new BehaviorSubject(SearchSessionState.None).asObservable(), setSearchSessionInfoProvider: jest.fn(), trackSearch: jest.fn((searchDescriptor) => () => {}), destroy: jest.fn(), diff --git a/src/plugins/data/public/search/session/session_state.test.ts b/src/plugins/data/public/search/session/search_session_state.test.ts similarity index 61% rename from src/plugins/data/public/search/session/session_state.test.ts rename to src/plugins/data/public/search/session/search_session_state.test.ts index 5f709c75bb5d21..539fc8252b2a5f 100644 --- a/src/plugins/data/public/search/session/session_state.test.ts +++ b/src/plugins/data/public/search/session/search_session_state.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { createSessionStateContainer, SessionState } from './session_state'; +import { createSessionStateContainer, SearchSessionState } from './search_session_state'; describe('Session state container', () => { const { stateContainer: state } = createSessionStateContainer(); @@ -29,7 +29,7 @@ describe('Session state container', () => { describe('transitions', () => { test('start', () => { state.transitions.start(); - expect(state.selectors.getState()).toBe(SessionState.None); + expect(state.selectors.getState()).toBe(SearchSessionState.None); expect(state.get().sessionId).not.toBeUndefined(); }); @@ -39,22 +39,22 @@ describe('Session state container', () => { state.transitions.start(); state.transitions.trackSearch({}); - expect(state.selectors.getState()).toBe(SessionState.Loading); + expect(state.selectors.getState()).toBe(SearchSessionState.Loading); }); test('untrack', () => { state.transitions.start(); const search = {}; state.transitions.trackSearch(search); - expect(state.selectors.getState()).toBe(SessionState.Loading); + expect(state.selectors.getState()).toBe(SearchSessionState.Loading); state.transitions.unTrackSearch(search); - expect(state.selectors.getState()).toBe(SessionState.Completed); + expect(state.selectors.getState()).toBe(SearchSessionState.Completed); }); test('clear', () => { state.transitions.start(); state.transitions.clear(); - expect(state.selectors.getState()).toBe(SessionState.None); + expect(state.selectors.getState()).toBe(SearchSessionState.None); expect(state.get().sessionId).toBeUndefined(); }); @@ -64,11 +64,11 @@ describe('Session state container', () => { state.transitions.start(); const search = {}; state.transitions.trackSearch(search); - expect(state.selectors.getState()).toBe(SessionState.Loading); + expect(state.selectors.getState()).toBe(SearchSessionState.Loading); state.transitions.cancel(); - expect(state.selectors.getState()).toBe(SessionState.Canceled); + expect(state.selectors.getState()).toBe(SearchSessionState.Canceled); state.transitions.clear(); - expect(state.selectors.getState()).toBe(SessionState.None); + expect(state.selectors.getState()).toBe(SearchSessionState.None); }); test('store -> completed', () => { @@ -77,48 +77,48 @@ describe('Session state container', () => { state.transitions.start(); const search = {}; state.transitions.trackSearch(search); - expect(state.selectors.getState()).toBe(SessionState.Loading); + expect(state.selectors.getState()).toBe(SearchSessionState.Loading); state.transitions.store(); - expect(state.selectors.getState()).toBe(SessionState.BackgroundLoading); + expect(state.selectors.getState()).toBe(SearchSessionState.BackgroundLoading); state.transitions.unTrackSearch(search); - expect(state.selectors.getState()).toBe(SessionState.BackgroundCompleted); + expect(state.selectors.getState()).toBe(SearchSessionState.BackgroundCompleted); state.transitions.clear(); - expect(state.selectors.getState()).toBe(SessionState.None); + expect(state.selectors.getState()).toBe(SearchSessionState.None); }); test('store -> cancel', () => { state.transitions.start(); const search = {}; state.transitions.trackSearch(search); - expect(state.selectors.getState()).toBe(SessionState.Loading); + expect(state.selectors.getState()).toBe(SearchSessionState.Loading); state.transitions.store(); - expect(state.selectors.getState()).toBe(SessionState.BackgroundLoading); + expect(state.selectors.getState()).toBe(SearchSessionState.BackgroundLoading); state.transitions.cancel(); - expect(state.selectors.getState()).toBe(SessionState.Canceled); + expect(state.selectors.getState()).toBe(SearchSessionState.Canceled); state.transitions.trackSearch(search); - expect(state.selectors.getState()).toBe(SessionState.Canceled); + expect(state.selectors.getState()).toBe(SearchSessionState.Canceled); state.transitions.start(); - expect(state.selectors.getState()).toBe(SessionState.None); + expect(state.selectors.getState()).toBe(SearchSessionState.None); }); test('restore', () => { const id = 'id'; state.transitions.restore(id); - expect(state.selectors.getState()).toBe(SessionState.None); + expect(state.selectors.getState()).toBe(SearchSessionState.None); const search = {}; state.transitions.trackSearch(search); - expect(state.selectors.getState()).toBe(SessionState.BackgroundLoading); + expect(state.selectors.getState()).toBe(SearchSessionState.BackgroundLoading); state.transitions.unTrackSearch(search); - expect(state.selectors.getState()).toBe(SessionState.Restored); + expect(state.selectors.getState()).toBe(SearchSessionState.Restored); expect(() => state.transitions.store()).toThrowError(); - expect(state.selectors.getState()).toBe(SessionState.Restored); + expect(state.selectors.getState()).toBe(SearchSessionState.Restored); expect(() => state.transitions.cancel()).toThrowError(); - expect(state.selectors.getState()).toBe(SessionState.Restored); + expect(state.selectors.getState()).toBe(SearchSessionState.Restored); state.transitions.start(); - expect(state.selectors.getState()).toBe(SessionState.None); + expect(state.selectors.getState()).toBe(SearchSessionState.None); }); }); }); diff --git a/src/plugins/data/public/search/session/session_state.ts b/src/plugins/data/public/search/session/search_session_state.ts similarity index 86% rename from src/plugins/data/public/search/session/session_state.ts rename to src/plugins/data/public/search/session/search_session_state.ts index 087417263e5bf0..7a35a65a600d75 100644 --- a/src/plugins/data/public/search/session/session_state.ts +++ b/src/plugins/data/public/search/session/search_session_state.ts @@ -27,7 +27,7 @@ import { createStateContainer, StateContainer } from '../../../../kibana_utils/p * * @public */ -export enum SessionState { +export enum SearchSessionState { /** * Session is not active, e.g. didn't start */ @@ -39,18 +39,18 @@ export enum SessionState { Loading = 'loading', /** - * No action was taken and the page completed loading without background session creation. + * No action was taken and the page completed loading without search session creation. */ Completed = 'completed', /** - * Search request was sent to the background. + * Search session was sent to the background. * The page is loading in background. */ BackgroundLoading = 'backgroundLoading', /** - * Page load completed with background session created. + * Page load completed with search session created. */ BackgroundCompleted = 'backgroundCompleted', @@ -68,7 +68,7 @@ export enum SessionState { /** * Internal state of SessionService - * {@link SessionState} is inferred from this state + * {@link SearchSessionState} is inferred from this state * * @private */ @@ -179,27 +179,29 @@ export interface SessionPureSelectors< SearchDescriptor = unknown, S = SessionStateInternal > { - getState: (state: S) => () => SessionState; + getState: (state: S) => () => SearchSessionState; } export const sessionPureSelectors: SessionPureSelectors = { getState: (state) => () => { - if (!state.sessionId) return SessionState.None; - if (!state.isStarted) return SessionState.None; - if (state.isCanceled) return SessionState.Canceled; + if (!state.sessionId) return SearchSessionState.None; + if (!state.isStarted) return SearchSessionState.None; + if (state.isCanceled) return SearchSessionState.Canceled; switch (true) { case state.isRestore: return state.pendingSearches.length > 0 - ? SessionState.BackgroundLoading - : SessionState.Restored; + ? SearchSessionState.BackgroundLoading + : SearchSessionState.Restored; case state.isStored: return state.pendingSearches.length > 0 - ? SessionState.BackgroundLoading - : SessionState.BackgroundCompleted; + ? SearchSessionState.BackgroundLoading + : SearchSessionState.BackgroundCompleted; default: - return state.pendingSearches.length > 0 ? SessionState.Loading : SessionState.Completed; + return state.pendingSearches.length > 0 + ? SearchSessionState.Loading + : SearchSessionState.Completed; } - return SessionState.None; + return SearchSessionState.None; }, }; @@ -213,7 +215,7 @@ export const createSessionStateContainer = ( { freeze = true }: { freeze: boolean } = { freeze: true } ): { stateContainer: SessionStateContainer; - sessionState$: Observable; + sessionState$: Observable; } => { const stateContainer = createStateContainer( createSessionDefaultState(), @@ -222,7 +224,7 @@ export const createSessionStateContainer = ( freeze ? undefined : { freeze: (s) => s } ) as SessionStateContainer; - const sessionState$: Observable = stateContainer.state$.pipe( + const sessionState$: Observable = stateContainer.state$.pipe( map(() => stateContainer.selectors.getState()), distinctUntilChanged(), shareReplay(1) diff --git a/src/plugins/data/public/search/session/session_service.test.ts b/src/plugins/data/public/search/session/session_service.test.ts index 83c3185ead63eb..cf083239b15719 100644 --- a/src/plugins/data/public/search/session/session_service.test.ts +++ b/src/plugins/data/public/search/session/session_service.test.ts @@ -22,11 +22,11 @@ import { coreMock } from '../../../../../core/public/mocks'; import { take, toArray } from 'rxjs/operators'; import { getSessionsClientMock } from './mocks'; import { BehaviorSubject } from 'rxjs'; -import { SessionState } from './session_state'; +import { SearchSessionState } from './search_session_state'; describe('Session service', () => { let sessionService: ISessionService; - let state$: BehaviorSubject; + let state$: BehaviorSubject; beforeEach(() => { const initializerContext = coreMock.createPluginInitializerContext(); @@ -36,7 +36,7 @@ describe('Session service', () => { getSessionsClientMock(), { freezeState: false } // needed to use mocks inside state container ); - state$ = new BehaviorSubject(SessionState.None); + state$ = new BehaviorSubject(SearchSessionState.None); sessionService.state$.subscribe(state$); }); @@ -65,17 +65,17 @@ describe('Session service', () => { it('Tracks searches for current session', () => { expect(() => sessionService.trackSearch({ abort: () => {} })).toThrowError(); - expect(state$.getValue()).toBe(SessionState.None); + expect(state$.getValue()).toBe(SearchSessionState.None); sessionService.start(); const untrack1 = sessionService.trackSearch({ abort: () => {} }); - expect(state$.getValue()).toBe(SessionState.Loading); + expect(state$.getValue()).toBe(SearchSessionState.Loading); const untrack2 = sessionService.trackSearch({ abort: () => {} }); - expect(state$.getValue()).toBe(SessionState.Loading); + expect(state$.getValue()).toBe(SearchSessionState.Loading); untrack1(); - expect(state$.getValue()).toBe(SessionState.Loading); + expect(state$.getValue()).toBe(SearchSessionState.Loading); untrack2(); - expect(state$.getValue()).toBe(SessionState.Completed); + expect(state$.getValue()).toBe(SearchSessionState.Completed); }); it('Cancels all tracked searches within current session', async () => { diff --git a/src/plugins/data/public/search/session/session_service.ts b/src/plugins/data/public/search/session/session_service.ts index ef0b36a33be52e..2bbb762fcfe9fa 100644 --- a/src/plugins/data/public/search/session/session_service.ts +++ b/src/plugins/data/public/search/session/session_service.ts @@ -23,7 +23,11 @@ import { Observable, Subject, Subscription } from 'rxjs'; import { PluginInitializerContext, StartServicesAccessor } from 'kibana/public'; import { UrlGeneratorId, UrlGeneratorStateMapping } from '../../../../share/public/'; import { ConfigSchema } from '../../../config'; -import { createSessionStateContainer, SessionState, SessionStateContainer } from './session_state'; +import { + createSessionStateContainer, + SearchSessionState, + SessionStateContainer, +} from './search_session_state'; import { ISessionsClient } from './sessions_client'; export type ISessionService = PublicContract; @@ -33,12 +37,12 @@ export interface TrackSearchDescriptor { } /** - * Provide info about current search session to be stored in backgroundSearch saved object + * Provide info about current search session to be stored in the Search Session saved object */ export interface SearchSessionInfoProvider { /** * User-facing name of the session. - * e.g. will be displayed in background sessions management list + * e.g. will be displayed in saved Search Sessions management list */ getName: () => Promise; getUrlGeneratorData: () => Promise<{ @@ -52,7 +56,7 @@ export interface SearchSessionInfoProvider; + public readonly state$: Observable; private readonly state: SessionStateContainer; private searchSessionInfoProvider?: SearchSessionInfoProvider; @@ -95,7 +99,7 @@ export class SessionService { /** * Set a provider of info about current session - * This will be used for creating a background session saved object + * This will be used for creating a search session saved object * @param searchSessionInfoProvider */ public setSearchSessionInfoProvider( @@ -184,7 +188,7 @@ export class SessionService { private refresh$ = new Subject(); /** * Observable emits when search result refresh was requested - * For example, search to background UI could have it's own "refresh" button + * For example, the UI could have it's own "refresh" button * Application would use this observable to handle user interaction on that button */ public onRefresh$ = this.refresh$.asObservable(); diff --git a/src/plugins/data/public/search/session/sessions_client.ts b/src/plugins/data/public/search/session/sessions_client.ts index 38be647a37c7ac..a8031e4e467e70 100644 --- a/src/plugins/data/public/search/session/sessions_client.ts +++ b/src/plugins/data/public/search/session/sessions_client.ts @@ -27,7 +27,7 @@ export interface SessionsClientDeps { } /** - * CRUD backgroundSession SO + * CRUD Search Session SO */ export class SessionsClient { private readonly http: HttpSetup; diff --git a/src/plugins/data/public/search/types.ts b/src/plugins/data/public/search/types.ts index 057b242c22f20a..7b0b501af81690 100644 --- a/src/plugins/data/public/search/types.ts +++ b/src/plugins/data/public/search/types.ts @@ -45,7 +45,7 @@ export interface ISearchSetup { */ session: ISessionService; /** - * Background search sessions SO CRUD + * Search sessions SO CRUD * {@link ISessionsClient} */ sessionsClient: ISessionsClient; @@ -84,7 +84,7 @@ export interface ISearchStart { */ session: ISessionService; /** - * Background search sessions SO CRUD + * Search sessions SO CRUD * {@link ISessionsClient} */ sessionsClient: ISessionsClient; diff --git a/src/plugins/data/server/search/session/session_service.ts b/src/plugins/data/server/search/session/session_service.ts index 15021436d8821a..37484185cb7794 100644 --- a/src/plugins/data/server/search/session/session_service.ts +++ b/src/plugins/data/server/search/session/session_service.ts @@ -23,7 +23,7 @@ import { ISearchStrategy } from '../types'; import { ISessionService } from './types'; /** - * The OSS session service. See data_enhanced in X-Pack for the background session service. + * The OSS session service. See data_enhanced in X-Pack for the search session service. */ export class SessionService implements ISessionService { constructor() {} diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index 639e2212392cc3..4c5cb864b5111d 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -204,7 +204,7 @@ function discoverController($element, $route, $scope, $timeout, Promise, uiCapab }; const history = getHistory(); - // used for restoring background session + // used for restoring a search session let isInitialSearch = true; // search session requested a data refresh diff --git a/x-pack/plugins/data_enhanced/common/index.ts b/x-pack/plugins/data_enhanced/common/index.ts index f26ba0dd0b46cd..e3e91ccf967c1d 100644 --- a/x-pack/plugins/data_enhanced/common/index.ts +++ b/x-pack/plugins/data_enhanced/common/index.ts @@ -12,8 +12,8 @@ export { EqlSearchStrategyResponse, IAsyncSearchOptions, pollSearch, - BackgroundSessionSavedObjectAttributes, - BackgroundSessionFindOptions, - BackgroundSessionStatus, - BackgroundSessionSearchInfo, + SearchSessionSavedObjectAttributes, + SearchSessionFindOptions, + SearchSessionStatus, + SearchSessionRequestInfo, } from './search'; diff --git a/x-pack/plugins/data_enhanced/common/search/session/status.ts b/x-pack/plugins/data_enhanced/common/search/session/status.ts index a83dd389e4f13d..e3a5bc02cdd41e 100644 --- a/x-pack/plugins/data_enhanced/common/search/session/status.ts +++ b/x-pack/plugins/data_enhanced/common/search/session/status.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export enum BackgroundSessionStatus { +export enum SearchSessionStatus { IN_PROGRESS = 'in_progress', ERROR = 'error', COMPLETE = 'complete', diff --git a/x-pack/plugins/data_enhanced/common/search/session/types.ts b/x-pack/plugins/data_enhanced/common/search/session/types.ts index 1310c05ed6854e..6f75e608563625 100644 --- a/x-pack/plugins/data_enhanced/common/search/session/types.ts +++ b/x-pack/plugins/data_enhanced/common/search/session/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export interface BackgroundSessionSavedObjectAttributes { +export interface SearchSessionSavedObjectAttributes { /** * User-facing session name to be displayed in session management */ @@ -19,15 +19,15 @@ export interface BackgroundSessionSavedObjectAttributes { urlGeneratorId: string; initialState: Record; restoreState: Record; - idMapping: Record; + idMapping: Record; } -export interface BackgroundSessionSearchInfo { +export interface SearchSessionRequestInfo { id: string; // ID of the async search request strategy: string; // Search strategy used to submit the search request } -export interface BackgroundSessionFindOptions { +export interface SearchSessionFindOptions { page?: number; perPage?: number; sortField?: string; diff --git a/x-pack/plugins/data_enhanced/public/plugin.ts b/x-pack/plugins/data_enhanced/public/plugin.ts index a3b37e47287e50..c7d1c8624cb1fc 100644 --- a/x-pack/plugins/data_enhanced/public/plugin.ts +++ b/x-pack/plugins/data_enhanced/public/plugin.ts @@ -13,7 +13,7 @@ import { setAutocompleteService } from './services'; import { setupKqlQuerySuggestionProvider, KUERY_LANGUAGE_NAME } from './autocomplete'; import { EnhancedSearchInterceptor } from './search/search_interceptor'; import { toMountPoint } from '../../../../src/plugins/kibana_react/public'; -import { createConnectedBackgroundSessionIndicator } from './search'; +import { createConnectedSearchSessionIndicator } from './search'; import { ConfigSchema } from '../config'; export interface DataEnhancedSetupDependencies { @@ -66,7 +66,7 @@ export class DataEnhancedPlugin core.chrome.setBreadcrumbsAppendExtension({ content: toMountPoint( React.createElement( - createConnectedBackgroundSessionIndicator({ + createConnectedSearchSessionIndicator({ sessionService: plugins.data.search.session, application: core.application, timeFilter: plugins.data.query.timefilter.timefilter, diff --git a/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts b/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts index 20b55d9688edba..fc6c860f907f67 100644 --- a/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts +++ b/x-pack/plugins/data_enhanced/public/search/search_interceptor.test.ts @@ -9,7 +9,7 @@ import { EnhancedSearchInterceptor } from './search_interceptor'; import { CoreSetup, CoreStart } from 'kibana/public'; import { UI_SETTINGS } from '../../../../../src/plugins/data/common'; import { AbortError } from '../../../../../src/plugins/kibana_utils/public'; -import { ISessionService, SearchTimeoutError, SessionState } from 'src/plugins/data/public'; +import { ISessionService, SearchTimeoutError, SearchSessionState } from 'src/plugins/data/public'; import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks'; import { bfetchPluginMock } from '../../../../../src/plugins/bfetch/public/mocks'; import { BehaviorSubject } from 'rxjs'; @@ -45,12 +45,12 @@ function mockFetchImplementation(responses: any[]) { describe('EnhancedSearchInterceptor', () => { let mockUsageCollector: any; let sessionService: jest.Mocked; - let sessionState$: BehaviorSubject; + let sessionState$: BehaviorSubject; beforeEach(() => { mockCoreSetup = coreMock.createSetup(); mockCoreStart = coreMock.createStart(); - sessionState$ = new BehaviorSubject(SessionState.None); + sessionState$ = new BehaviorSubject(SearchSessionState.None); const dataPluginMockStart = dataPluginMock.createStartContract(); sessionService = { ...(dataPluginMockStart.search.session as jest.Mocked), @@ -408,7 +408,7 @@ describe('EnhancedSearchInterceptor', () => { expect(next).toHaveBeenCalled(); expect(error).not.toHaveBeenCalled(); - sessionState$.next(SessionState.BackgroundLoading); + sessionState$.next(SearchSessionState.BackgroundLoading); await timeTravel(240); diff --git a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts index 0e87c093d2a8d9..b0f194115f0b8a 100644 --- a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts +++ b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts @@ -12,7 +12,7 @@ import { SearchInterceptorDeps, UI_SETTINGS, IKibanaSearchRequest, - SessionState, + SearchSessionState, } from '../../../../../src/plugins/data/public'; import { AbortError } from '../../../../../src/plugins/kibana_utils/common'; import { ENHANCED_ES_SEARCH_STRATEGY, IAsyncSearchOptions, pollSearch } from '../../common'; @@ -77,7 +77,7 @@ export class EnhancedSearchInterceptor extends SearchInterceptor { this.deps.session.state$ .pipe( skip(1), // ignore any state, we are only interested in transition x -> BackgroundLoading - filter((state) => isCurrentSession() && state === SessionState.BackgroundLoading), + filter((state) => isCurrentSession() && state === SearchSessionState.BackgroundLoading), take(1) ) .subscribe(() => { diff --git a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.stories.tsx b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.stories.tsx deleted file mode 100644 index 4a6a852be755b5..00000000000000 --- a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.stories.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { storiesOf } from '@storybook/react'; -import { BackgroundSessionIndicator } from './background_session_indicator'; -import { SessionState } from '../../../../../../../src/plugins/data/public'; - -storiesOf('components/BackgroundSessionIndicator', module).add('default', () => ( - <> -
- -
-
- -
-
- -
-
- -
-
- -
-
- -
- -)); diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.test.tsx b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx similarity index 62% rename from x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.test.tsx rename to x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx index 6fa9abd0f1ab6a..2c74f9c995a5af 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx @@ -7,12 +7,12 @@ import React from 'react'; import { render, waitFor, screen, act } from '@testing-library/react'; import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; -import { createConnectedBackgroundSessionIndicator } from './connected_background_session_indicator'; +import { createConnectedSearchSessionIndicator } from './connected_search_session_indicator'; import { BehaviorSubject } from 'rxjs'; import { ISessionService, RefreshInterval, - SessionState, + SearchSessionState, TimefilterContract, } from '../../../../../../../src/plugins/data/public'; import { coreMock } from '../../../../../../../src/core/public/mocks'; @@ -31,78 +31,76 @@ beforeEach(() => { }); test("shouldn't show indicator in case no active search session", async () => { - const BackgroundSessionIndicator = createConnectedBackgroundSessionIndicator({ + const SearchSessionIndicator = createConnectedSearchSessionIndicator({ sessionService, application: coreStart.application, timeFilter, }); - const { getByTestId, container } = render(); + const { getByTestId, container } = render(); - // make sure `backgroundSessionIndicator` isn't appearing after some time (lazy-loading) + // make sure `searchSessionIndicator` isn't appearing after some time (lazy-loading) await expect( - waitFor(() => getByTestId('backgroundSessionIndicator'), { timeout: 100 }) + waitFor(() => getByTestId('searchSessionIndicator'), { timeout: 100 }) ).rejects.toThrow(); expect(container).toMatchInlineSnapshot(`
`); }); test('should show indicator in case there is an active search session', async () => { - const state$ = new BehaviorSubject(SessionState.Loading); - const BackgroundSessionIndicator = createConnectedBackgroundSessionIndicator({ + const state$ = new BehaviorSubject(SearchSessionState.Loading); + const SearchSessionIndicator = createConnectedSearchSessionIndicator({ sessionService: { ...sessionService, state$ }, application: coreStart.application, timeFilter, }); - const { getByTestId } = render(); + const { getByTestId } = render(); - await waitFor(() => getByTestId('backgroundSessionIndicator')); + await waitFor(() => getByTestId('searchSessionIndicator')); }); test('should be disabled when permissions are off', async () => { - const state$ = new BehaviorSubject(SessionState.Loading); + const state$ = new BehaviorSubject(SearchSessionState.Loading); coreStart.application.currentAppId$ = new BehaviorSubject('discover'); (coreStart.application.capabilities as any) = { discover: { storeSearchSession: false, }, }; - const BackgroundSessionIndicator = createConnectedBackgroundSessionIndicator({ + const SearchSessionIndicator = createConnectedSearchSessionIndicator({ sessionService: { ...sessionService, state$ }, application: coreStart.application, timeFilter, }); - render(); + render(); - await waitFor(() => screen.getByTestId('backgroundSessionIndicator')); + await waitFor(() => screen.getByTestId('searchSessionIndicator')); - expect(screen.getByTestId('backgroundSessionIndicator').querySelector('button')).toBeDisabled(); + expect(screen.getByTestId('searchSessionIndicator').querySelector('button')).toBeDisabled(); }); test('should be disabled during auto-refresh', async () => { - const state$ = new BehaviorSubject(SessionState.Loading); + const state$ = new BehaviorSubject(SearchSessionState.Loading); coreStart.application.currentAppId$ = new BehaviorSubject('discover'); (coreStart.application.capabilities as any) = { discover: { storeSearchSession: true, }, }; - const BackgroundSessionIndicator = createConnectedBackgroundSessionIndicator({ + const SearchSessionIndicator = createConnectedSearchSessionIndicator({ sessionService: { ...sessionService, state$ }, application: coreStart.application, timeFilter, }); - render(); + render(); - await waitFor(() => screen.getByTestId('backgroundSessionIndicator')); + await waitFor(() => screen.getByTestId('searchSessionIndicator')); - expect( - screen.getByTestId('backgroundSessionIndicator').querySelector('button') - ).not.toBeDisabled(); + expect(screen.getByTestId('searchSessionIndicator').querySelector('button')).not.toBeDisabled(); act(() => { refreshInterval$.next({ value: 0, pause: false }); }); - expect(screen.getByTestId('backgroundSessionIndicator').querySelector('button')).toBeDisabled(); + expect(screen.getByTestId('searchSessionIndicator').querySelector('button')).toBeDisabled(); }); diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.tsx b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx similarity index 85% rename from x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.tsx rename to x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx index 1469c96d7166ec..5c8c01064bff43 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx @@ -8,22 +8,22 @@ import React from 'react'; import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators'; import useObservable from 'react-use/lib/useObservable'; import { i18n } from '@kbn/i18n'; -import { BackgroundSessionIndicator } from '../background_session_indicator'; +import { SearchSessionIndicator } from '../search_session_indicator'; import { ISessionService, TimefilterContract } from '../../../../../../../src/plugins/data/public/'; import { RedirectAppLinks } from '../../../../../../../src/plugins/kibana_react/public'; import { ApplicationStart } from '../../../../../../../src/core/public'; -export interface BackgroundSessionIndicatorDeps { +export interface SearchSessionIndicatorDeps { sessionService: ISessionService; timeFilter: TimefilterContract; application: ApplicationStart; } -export const createConnectedBackgroundSessionIndicator = ({ +export const createConnectedSearchSessionIndicator = ({ sessionService, application, timeFilter, -}: BackgroundSessionIndicatorDeps): React.FC => { +}: SearchSessionIndicatorDeps): React.FC => { const isAutoRefreshEnabled = () => !timeFilter.getRefreshInterval().pause; const isAutoRefreshEnabled$ = timeFilter .getRefreshIntervalUpdate$() @@ -53,7 +53,7 @@ export const createConnectedBackgroundSessionIndicator = ({ if (getCapabilitiesByAppId(application.capabilities, appId)?.storeSearchSession !== true) { disabled = true; - disabledReasonText = i18n.translate('xpack.data.backgroundSessionIndicator.noCapability', { + disabledReasonText = i18n.translate('xpack.data.searchSessionIndicator.noCapability', { defaultMessage: "You don't have permissions to send to background.", }); } @@ -61,7 +61,7 @@ export const createConnectedBackgroundSessionIndicator = ({ if (autoRefreshEnabled) { disabled = true; disabledReasonText = i18n.translate( - 'xpack.data.backgroundSessionIndicator.disabledDueToAutoRefreshMessage', + 'xpack.data.searchSessionIndicator.disabledDueToAutoRefreshMessage', { defaultMessage: 'Send to background is not available when auto refresh is enabled.', } @@ -71,7 +71,7 @@ export const createConnectedBackgroundSessionIndicator = ({ if (!state) return null; return ( - { sessionService.save(); diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/index.ts b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/index.ts similarity index 65% rename from x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/index.ts rename to x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/index.ts index 223a0537129df5..da6ce470e6b816 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/index.ts +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/index.ts @@ -5,6 +5,6 @@ */ export { - BackgroundSessionIndicatorDeps, - createConnectedBackgroundSessionIndicator, -} from './connected_background_session_indicator'; + SearchSessionIndicatorDeps, + createConnectedSearchSessionIndicator, +} from './connected_search_session_indicator'; diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/background_session_view_state.ts b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/search_session_view_state.ts similarity index 85% rename from x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/background_session_view_state.ts rename to x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/search_session_view_state.ts index b75c2a536f6248..7b9b182453082f 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/background_session_view_state.ts +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/search_session_view_state.ts @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -export enum BackgroundSessionViewState { +export enum SearchSessionViewState { /** * Pending search request has not been sent to the background yet */ Loading = 'loading', /** - * No action was taken and the page completed loading without background session creation. + * No action was taken and the page completed loading without search session creation. */ Completed = 'completed', @@ -22,7 +22,7 @@ export enum BackgroundSessionViewState { BackgroundLoading = 'backgroundLoading', /** - * Page load completed with background session created. + * Page load completed with search session created. */ BackgroundCompleted = 'backgroundCompleted', diff --git a/x-pack/plugins/data_enhanced/public/search/ui/index.ts b/x-pack/plugins/data_enhanced/public/search/ui/index.ts index 04201325eb5db3..fce8f215a4b7fe 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/index.ts +++ b/x-pack/plugins/data_enhanced/public/search/ui/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './connected_background_session_indicator'; +export * from './connected_search_session_indicator'; diff --git a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/index.tsx b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/index.tsx similarity index 57% rename from x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/index.tsx rename to x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/index.tsx index 55c8c453dd5d2f..16ee6b49a761f9 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/index.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/index.tsx @@ -6,8 +6,8 @@ import { EuiDelayRender, EuiLoadingSpinner } from '@elastic/eui'; import React from 'react'; -import type { BackgroundSessionIndicatorProps } from './background_session_indicator'; -export type { BackgroundSessionIndicatorProps }; +import type { SearchSessionIndicatorProps } from './search_session_indicator'; +export type { SearchSessionIndicatorProps }; const Fallback = () => ( @@ -15,9 +15,9 @@ const Fallback = () => ( ); -const LazyBackgroundSessionIndicator = React.lazy(() => import('./background_session_indicator')); -export const BackgroundSessionIndicator = (props: BackgroundSessionIndicatorProps) => ( +const LazySearchSessionIndicator = React.lazy(() => import('./search_session_indicator')); +export const SearchSessionIndicator = (props: SearchSessionIndicatorProps) => ( }> - + ); diff --git a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.scss b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.scss similarity index 69% rename from x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.scss rename to x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.scss index 2d13d320ae78b6..6f3ae5b5846fd9 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.scss +++ b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.scss @@ -1,14 +1,14 @@ -.backgroundSessionIndicator { +.searchSessionIndicator { padding: 0 $euiSizeXS; } @include euiBreakpoint('xs', 's') { - .backgroundSessionIndicator__popoverContainer.euiFlexGroup--responsive .euiFlexItem { + .searchSessionIndicator__popoverContainer.euiFlexGroup--responsive .euiFlexItem { margin-bottom: $euiSizeXS !important; } } -.backgroundSessionIndicator__verticalDivider { +.searchSessionIndicator__verticalDivider { @include euiBreakpoint('xs', 's') { margin-left: $euiSizeXS; padding-left: $euiSizeXS; diff --git a/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.stories.tsx b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.stories.tsx new file mode 100644 index 00000000000000..f3b526a8743eab --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.stories.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { SearchSessionIndicator } from './search_session_indicator'; +import { SearchSessionState } from '../../../../../../../src/plugins/data/public'; + +storiesOf('components/SearchSessionIndicator', module).add('default', () => ( + <> +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +)); diff --git a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.test.tsx b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.test.tsx similarity index 69% rename from x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.test.tsx rename to x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.test.tsx index b7d342300f3119..01026c8beefeb3 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.test.tsx @@ -7,9 +7,9 @@ import React, { ReactNode } from 'react'; import { screen, render } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { BackgroundSessionIndicator } from './background_session_indicator'; +import { SearchSessionIndicator } from './search_session_indicator'; import { IntlProvider } from 'react-intl'; -import { SessionState } from '../../../../../../../src/plugins/data/public'; +import { SearchSessionState } from '../../../../../../../src/plugins/data/public'; function Container({ children }: { children?: ReactNode }) { return {children}; @@ -19,7 +19,7 @@ test('Loading state', async () => { const onCancel = jest.fn(); render( - + ); @@ -33,7 +33,7 @@ test('Completed state', async () => { const onSave = jest.fn(); render( - + ); @@ -47,7 +47,7 @@ test('Loading in the background state', async () => { const onCancel = jest.fn(); render( - + ); @@ -60,15 +60,15 @@ test('Loading in the background state', async () => { test('BackgroundCompleted state', async () => { render( - ); await userEvent.click(screen.getByLabelText('Results loaded in the background')); - expect(screen.getByRole('link', { name: 'View background sessions' }).getAttribute('href')).toBe( + expect(screen.getByRole('link', { name: 'View all sessions' }).getAttribute('href')).toBe( '__link__' ); }); @@ -77,7 +77,7 @@ test('Restored state', async () => { const onRefresh = jest.fn(); render( - + ); @@ -91,7 +91,7 @@ test('Canceled state', async () => { const onRefresh = jest.fn(); render( - + ); @@ -104,9 +104,9 @@ test('Canceled state', async () => { test('Disabled state', async () => { render( - + ); - expect(screen.getByTestId('backgroundSessionIndicator').querySelector('button')).toBeDisabled(); + expect(screen.getByTestId('searchSessionIndicator').querySelector('button')).toBeDisabled(); }); diff --git a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.tsx b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.tsx similarity index 57% rename from x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.tsx rename to x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.tsx index ce77686c4f3c10..3ed84e79bb6513 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.tsx @@ -20,31 +20,31 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import './background_session_indicator.scss'; -import { SessionState } from '../../../../../../../src/plugins/data/public/'; +import './search_session_indicator.scss'; +import { SearchSessionState } from '../../../../../../../src/plugins/data/public'; -export interface BackgroundSessionIndicatorProps { - state: SessionState; +export interface SearchSessionIndicatorProps { + state: SearchSessionState; onContinueInBackground?: () => void; onCancel?: () => void; - viewBackgroundSessionsLink?: string; + viewSearchSessionsLink?: string; onSaveResults?: () => void; onRefresh?: () => void; disabled?: boolean; disabledReasonText?: string; } -type ActionButtonProps = BackgroundSessionIndicatorProps & { buttonProps: EuiButtonEmptyProps }; +type ActionButtonProps = SearchSessionIndicatorProps & { buttonProps: EuiButtonEmptyProps }; const CancelButton = ({ onCancel = () => {}, buttonProps = {} }: ActionButtonProps) => ( ); @@ -55,28 +55,28 @@ const ContinueInBackgroundButton = ({ }: ActionButtonProps) => ( ); -const ViewBackgroundSessionsButton = ({ - viewBackgroundSessionsLink = 'management', +const ViewAllSearchSessionsButton = ({ + viewSearchSessionsLink = 'management', buttonProps = {}, }: ActionButtonProps) => ( ); @@ -84,11 +84,11 @@ const ViewBackgroundSessionsButton = ({ const RefreshButton = ({ onRefresh = () => {}, buttonProps = {} }: ActionButtonProps) => ( @@ -97,18 +97,18 @@ const RefreshButton = ({ onRefresh = () => {}, buttonProps = {} }: ActionButtonP const SaveButton = ({ onSaveResults = () => {}, buttonProps = {} }: ActionButtonProps) => ( ); -const backgroundSessionIndicatorViewStateToProps: { - [state in SessionState]: { +const searchSessionIndicatorViewStateToProps: { + [state in SearchSessionState]: { button: Pick & { tooltipText: string; }; @@ -119,162 +119,151 @@ const backgroundSessionIndicatorViewStateToProps: { }; } | null; } = { - [SessionState.None]: null, - [SessionState.Loading]: { + [SearchSessionState.None]: null, + [SearchSessionState.Loading]: { button: { color: 'subdued', iconType: 'clock', 'aria-label': i18n.translate( - 'xpack.data.backgroundSessionIndicator.loadingResultsIconAriaLabel', - { defaultMessage: 'Loading results' } + 'xpack.data.searchSessionIndicator.loadingResultsIconAriaLabel', + { defaultMessage: 'Loading' } ), tooltipText: i18n.translate( - 'xpack.data.backgroundSessionIndicator.loadingResultsIconTooltipText', - { defaultMessage: 'Loading results' } + 'xpack.data.searchSessionIndicator.loadingResultsIconTooltipText', + { defaultMessage: 'Loading' } ), }, popover: { - text: i18n.translate('xpack.data.backgroundSessionIndicator.loadingResultsText', { + text: i18n.translate('xpack.data.searchSessionIndicator.loadingResultsText', { defaultMessage: 'Loading', }), primaryAction: CancelButton, secondaryAction: ContinueInBackgroundButton, }, }, - [SessionState.Completed]: { + [SearchSessionState.Completed]: { button: { color: 'subdued', iconType: 'checkInCircleFilled', - 'aria-label': i18n.translate( - 'xpack.data.backgroundSessionIndicator.resultsLoadedIconAriaLabel', - { - defaultMessage: 'Results loaded', - } - ), + 'aria-label': i18n.translate('xpack.data.searchSessionIndicator.resultsLoadedIconAriaLabel', { + defaultMessage: 'Loaded', + }), tooltipText: i18n.translate( - 'xpack.data.backgroundSessionIndicator.resultsLoadedIconTooltipText', + 'xpack.data.searchSessionIndicator.resultsLoadedIconTooltipText', { defaultMessage: 'Results loaded', } ), }, popover: { - text: i18n.translate('xpack.data.backgroundSessionIndicator.resultsLoadedText', { + text: i18n.translate('xpack.data.searchSessionIndicator.resultsLoadedText', { defaultMessage: 'Results loaded', }), primaryAction: SaveButton, - secondaryAction: ViewBackgroundSessionsButton, + secondaryAction: ViewAllSearchSessionsButton, }, }, - [SessionState.BackgroundLoading]: { + [SearchSessionState.BackgroundLoading]: { button: { iconType: EuiLoadingSpinner, 'aria-label': i18n.translate( - 'xpack.data.backgroundSessionIndicator.loadingInTheBackgroundIconAriaLabel', + 'xpack.data.searchSessionIndicator.loadingInTheBackgroundIconAriaLabel', { defaultMessage: 'Loading results in the background', } ), tooltipText: i18n.translate( - 'xpack.data.backgroundSessionIndicator.loadingInTheBackgroundIconTooltipText', + 'xpack.data.searchSessionIndicator.loadingInTheBackgroundIconTooltipText', { defaultMessage: 'Loading results in the background', } ), }, popover: { - text: i18n.translate('xpack.data.backgroundSessionIndicator.loadingInTheBackgroundText', { + text: i18n.translate('xpack.data.searchSessionIndicator.loadingInTheBackgroundText', { defaultMessage: 'Loading in the background', }), primaryAction: CancelButton, - secondaryAction: ViewBackgroundSessionsButton, + secondaryAction: ViewAllSearchSessionsButton, }, }, - [SessionState.BackgroundCompleted]: { + [SearchSessionState.BackgroundCompleted]: { button: { color: 'success', iconType: 'checkInCircleFilled', 'aria-label': i18n.translate( - 'xpack.data.backgroundSessionIndicator.resultLoadedInTheBackgroundIconAraText', + 'xpack.data.searchSessionIndicator.resultLoadedInTheBackgroundIconAraText', { defaultMessage: 'Results loaded in the background', } ), tooltipText: i18n.translate( - 'xpack.data.backgroundSessionIndicator.resultLoadedInTheBackgroundIconTooltipText', + 'xpack.data.searchSessionIndicator.resultLoadedInTheBackgroundIconTooltipText', { defaultMessage: 'Results loaded in the background', } ), }, popover: { - text: i18n.translate( - 'xpack.data.backgroundSessionIndicator.resultLoadedInTheBackgroundText', - { - defaultMessage: 'Results loaded', - } - ), - primaryAction: ViewBackgroundSessionsButton, + text: i18n.translate('xpack.data.searchSessionIndicator.resultLoadedInTheBackgroundText', { + defaultMessage: 'Results loaded', + }), + primaryAction: ViewAllSearchSessionsButton, }, }, - [SessionState.Restored]: { + [SearchSessionState.Restored]: { button: { color: 'warning', iconType: 'refresh', 'aria-label': i18n.translate( - 'xpack.data.backgroundSessionIndicator.restoredResultsIconAriaLabel', - { - defaultMessage: 'Results no longer current', - } - ), - tooltipText: i18n.translate( - 'xpack.data.backgroundSessionIndicator.restoredResultsTooltipText', + 'xpack.data.searchSessionIndicator.restoredResultsIconAriaLabel', { defaultMessage: 'Results no longer current', } ), + tooltipText: i18n.translate('xpack.data.searchSessionIndicator.restoredResultsTooltipText', { + defaultMessage: 'Results no longer current', + }), }, popover: { - text: i18n.translate('xpack.data.backgroundSessionIndicator.restoredText', { + text: i18n.translate('xpack.data.searchSessionIndicator.restoredText', { defaultMessage: 'Results no longer current', }), primaryAction: RefreshButton, - secondaryAction: ViewBackgroundSessionsButton, + secondaryAction: ViewAllSearchSessionsButton, }, }, - [SessionState.Canceled]: { + [SearchSessionState.Canceled]: { button: { color: 'subdued', iconType: 'refresh', - 'aria-label': i18n.translate('xpack.data.backgroundSessionIndicator.canceledIconAriaLabel', { + 'aria-label': i18n.translate('xpack.data.searchSessionIndicator.canceledIconAriaLabel', { defaultMessage: 'Canceled', }), - tooltipText: i18n.translate('xpack.data.backgroundSessionIndicator.canceledTooltipText', { + tooltipText: i18n.translate('xpack.data.searchSessionIndicator.canceledTooltipText', { defaultMessage: 'Search was canceled', }), }, popover: { - text: i18n.translate('xpack.data.backgroundSessionIndicator.canceledText', { + text: i18n.translate('xpack.data.searchSessionIndicator.canceledText', { defaultMessage: 'Search was canceled', }), primaryAction: RefreshButton, - secondaryAction: ViewBackgroundSessionsButton, + secondaryAction: ViewAllSearchSessionsButton, }, }, }; -const VerticalDivider: React.FC = () => ( -
-); +const VerticalDivider: React.FC = () =>
; -export const BackgroundSessionIndicator: React.FC = (props) => { +export const SearchSessionIndicator: React.FC = (props) => { const [isPopoverOpen, setIsPopoverOpen] = React.useState(false); const onButtonClick = () => setIsPopoverOpen((isOpen) => !isOpen); const closePopover = () => setIsPopoverOpen(false); - if (!backgroundSessionIndicatorViewStateToProps[props.state]) return null; + if (!searchSessionIndicatorViewStateToProps[props.state]) return null; - const { button, popover } = backgroundSessionIndicatorViewStateToProps[props.state]!; + const { button, popover } = searchSessionIndicatorViewStateToProps[props.state]!; return ( @@ -302,8 +291,8 @@ export const BackgroundSessionIndicator: React.FC @@ -332,4 +321,4 @@ export const BackgroundSessionIndicator: React.FC { private readonly logger: Logger; - private sessionService!: BackgroundSessionService; + private sessionService!: SearchSessionService; constructor(private initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get('data_enhanced'); @@ -38,7 +38,7 @@ export class EnhancedDataServerPlugin implements Plugin new Promise((resolve) => setImmediate(resolve)); -describe('BackgroundSessionService', () => { +describe('SearchSessionService', () => { let savedObjectsClient: jest.Mocked; - let service: BackgroundSessionService; + let service: SearchSessionService; const MOCK_SESSION_ID = 'session-id-mock'; const MOCK_ASYNC_ID = '123456'; @@ -93,7 +93,7 @@ describe('BackgroundSessionService', () => { const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; const mockSavedObject: SavedObject = { id: 'd7170a35-7e2c-48d6-8dec-9a056721b489', - type: BACKGROUND_SESSION_TYPE, + type: SEARCH_SESSION_TYPE, attributes: { name: 'my_name', appId: 'my_app_id', @@ -110,7 +110,7 @@ describe('BackgroundSessionService', () => { warn: jest.fn(), error: jest.fn(), }; - service = new BackgroundSessionService(mockLogger); + service = new SearchSessionService(mockLogger); }); it('search throws if `name` is not provided', () => { @@ -131,7 +131,7 @@ describe('BackgroundSessionService', () => { const response = await service.get(sessionId, { savedObjectsClient }); expect(response).toBe(mockSavedObject); - expect(savedObjectsClient.get).toHaveBeenCalledWith(BACKGROUND_SESSION_TYPE, sessionId); + expect(savedObjectsClient.get).toHaveBeenCalledWith(SEARCH_SESSION_TYPE, sessionId); }); it('find calls saved objects client', async () => { @@ -153,7 +153,7 @@ describe('BackgroundSessionService', () => { expect(response).toBe(mockResponse); expect(savedObjectsClient.find).toHaveBeenCalledWith({ ...options, - type: BACKGROUND_SESSION_TYPE, + type: SEARCH_SESSION_TYPE, }); }); @@ -169,7 +169,7 @@ describe('BackgroundSessionService', () => { expect(response).toBe(mockUpdateSavedObject); expect(savedObjectsClient.update).toHaveBeenCalledWith( - BACKGROUND_SESSION_TYPE, + SEARCH_SESSION_TYPE, sessionId, attributes ); @@ -181,14 +181,14 @@ describe('BackgroundSessionService', () => { const response = await service.delete(sessionId, { savedObjectsClient }); expect(response).toEqual({}); - expect(savedObjectsClient.delete).toHaveBeenCalledWith(BACKGROUND_SESSION_TYPE, sessionId); + expect(savedObjectsClient.delete).toHaveBeenCalledWith(SEARCH_SESSION_TYPE, sessionId); }); describe('search', () => { const mockSearch = jest.fn().mockReturnValue(of({})); const mockStrategy = { search: mockSearch }; const mockSearchDeps = {} as SearchStrategyDependencies; - const mockDeps = {} as BackgroundSessionDependencies; + const mockDeps = {} as SearchSessionDependencies; beforeEach(() => { mockSearch.mockClear(); @@ -300,14 +300,14 @@ describe('BackgroundSessionService', () => { ); expect(savedObjectsClient.create).toHaveBeenCalledWith( - BACKGROUND_SESSION_TYPE, + SEARCH_SESSION_TYPE, { name, created, expires, initialState: {}, restoreState: {}, - status: BackgroundSessionStatus.IN_PROGRESS, + status: SearchSessionStatus.IN_PROGRESS, idMapping: {}, appId, urlGeneratorId, @@ -335,7 +335,7 @@ describe('BackgroundSessionService', () => { { savedObjectsClient } ); - expect(savedObjectsClient.update).toHaveBeenCalledWith(BACKGROUND_SESSION_TYPE, sessionId, { + expect(savedObjectsClient.update).toHaveBeenCalledWith(SEARCH_SESSION_TYPE, sessionId, { idMapping: { [requestHash]: { id: searchId, @@ -385,7 +385,7 @@ describe('BackgroundSessionService', () => { const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; const mockSession = { id: 'd7170a35-7e2c-48d6-8dec-9a056721b489', - type: BACKGROUND_SESSION_TYPE, + type: SEARCH_SESSION_TYPE, attributes: { name: 'my_name', appId: 'my_app_id', diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts index 01291919001f5a..8f590e1639524b 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts @@ -30,12 +30,12 @@ import { SearchStrategyDependencies, } from '../../../../../../src/plugins/data/server'; import { - BackgroundSessionSavedObjectAttributes, - BackgroundSessionFindOptions, - BackgroundSessionSearchInfo, - BackgroundSessionStatus, + SearchSessionSavedObjectAttributes, + SearchSessionFindOptions, + SearchSessionRequestInfo, + SearchSessionStatus, } from '../../../common'; -import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; +import { SEARCH_SESSION_TYPE } from '../../saved_objects'; import { createRequestHash } from './utils'; import { ConfigSchema } from '../../../config'; @@ -45,17 +45,17 @@ export const INMEM_TRACKING_INTERVAL = 10 * 1000; export const INMEM_TRACKING_TIMEOUT_SEC = 60; export const MAX_UPDATE_RETRIES = 3; -export interface BackgroundSessionDependencies { +export interface SearchSessionDependencies { savedObjectsClient: SavedObjectsClientContract; } export interface SessionInfo { insertTime: Moment; retryCount: number; - ids: Map; + ids: Map; } -export class BackgroundSessionService implements ISessionService { +export class SearchSessionService implements ISessionService { /** * Map of sessionId to { [requestHash]: searchId } * @private @@ -79,7 +79,7 @@ export class BackgroundSessionService implements ISessionService { const config = await config$.pipe(first()).toPromise(); if (config.search.sendToBackground.enabled) { this.logger.debug(`setupMonitoring | Enabling monitoring`); - const internalRepo = core.savedObjects.createInternalRepository([BACKGROUND_SESSION_TYPE]); + const internalRepo = core.savedObjects.createInternalRepository([SEARCH_SESSION_TYPE]); this.internalSavedObjectsClient = new SavedObjectsClient(internalRepo); this.monitorMappedIds(); } @@ -92,7 +92,7 @@ export class BackgroundSessionService implements ISessionService { private sessionIdsAsFilters(sessionIds: string[]): KueryNode { return nodeBuilder.or( sessionIds.map((id) => { - return nodeBuilder.is(`${BACKGROUND_SESSION_TYPE}.attributes.sessionId`, id); + return nodeBuilder.is(`${SEARCH_SESSION_TYPE}.attributes.sessionId`, id); }) ); } @@ -107,9 +107,9 @@ export class BackgroundSessionService implements ISessionService { */ private async getAllMappedSavedObjects() { const filter = this.sessionIdsAsFilters(Array.from(this.sessionSearchMap.keys())); - const res = await this.internalSavedObjectsClient.find({ + const res = await this.internalSavedObjectsClient.find({ perPage: INMEM_MAX_SESSIONS, // If there are more sessions in memory, they will be synced when some items are cleared out. - type: BACKGROUND_SESSION_TYPE, + type: SEARCH_SESSION_TYPE, filter, namespaces: ['*'], }); @@ -175,13 +175,13 @@ export class BackgroundSessionService implements ISessionService { } private async updateAllSavedObjects( - activeMappingObjects: Array> + activeMappingObjects: Array> ) { if (!activeMappingObjects.length) return []; this.logger.debug(`updateAllSavedObjects | Updating ${activeMappingObjects.length} items`); const updatedSessions: Array< - SavedObjectsBulkUpdateObject + SavedObjectsBulkUpdateObject > = activeMappingObjects .filter((so) => !so.error) .map((sessionSavedObject) => { @@ -197,7 +197,7 @@ export class BackgroundSessionService implements ISessionService { }; }); - const updateResults = await this.internalSavedObjectsClient.bulkUpdate( + const updateResults = await this.internalSavedObjectsClient.bulkUpdate( updatedSessions ); return updateResults.saved_objects; @@ -208,7 +208,7 @@ export class BackgroundSessionService implements ISessionService { searchRequest: Request, options: ISearchOptions, searchDeps: SearchStrategyDependencies, - deps: BackgroundSessionDependencies + deps: SearchSessionDependencies ): Observable { // If this is a restored background search session, look up the ID using the provided sessionId const getSearchRequest = async () => @@ -236,12 +236,12 @@ export class BackgroundSessionService implements ISessionService { appId, created = new Date().toISOString(), expires = new Date(Date.now() + DEFAULT_EXPIRATION).toISOString(), - status = BackgroundSessionStatus.IN_PROGRESS, + status = SearchSessionStatus.IN_PROGRESS, urlGeneratorId, initialState = {}, restoreState = {}, - }: Partial, - { savedObjectsClient }: BackgroundSessionDependencies + }: Partial, + { savedObjectsClient }: SearchSessionDependencies ) => { if (!name) throw new Error('Name is required'); if (!appId) throw new Error('AppId is required'); @@ -261,8 +261,8 @@ export class BackgroundSessionService implements ISessionService { appId, sessionId, }; - const session = await savedObjectsClient.create( - BACKGROUND_SESSION_TYPE, + const session = await savedObjectsClient.create( + SEARCH_SESSION_TYPE, attributes, { id: sessionId } ); @@ -271,42 +271,42 @@ export class BackgroundSessionService implements ISessionService { }; // TODO: Throw an error if this session doesn't belong to this user - public get = (sessionId: string, { savedObjectsClient }: BackgroundSessionDependencies) => { + public get = (sessionId: string, { savedObjectsClient }: SearchSessionDependencies) => { this.logger.debug(`get | ${sessionId}`); - return savedObjectsClient.get( - BACKGROUND_SESSION_TYPE, + return savedObjectsClient.get( + SEARCH_SESSION_TYPE, sessionId ); }; // TODO: Throw an error if this session doesn't belong to this user public find = ( - options: BackgroundSessionFindOptions, - { savedObjectsClient }: BackgroundSessionDependencies + options: SearchSessionFindOptions, + { savedObjectsClient }: SearchSessionDependencies ) => { - return savedObjectsClient.find({ + return savedObjectsClient.find({ ...options, - type: BACKGROUND_SESSION_TYPE, + type: SEARCH_SESSION_TYPE, }); }; // TODO: Throw an error if this session doesn't belong to this user public update = ( sessionId: string, - attributes: Partial, - { savedObjectsClient }: BackgroundSessionDependencies + attributes: Partial, + { savedObjectsClient }: SearchSessionDependencies ) => { this.logger.debug(`update | ${sessionId}`); - return savedObjectsClient.update( - BACKGROUND_SESSION_TYPE, + return savedObjectsClient.update( + SEARCH_SESSION_TYPE, sessionId, attributes ); }; // TODO: Throw an error if this session doesn't belong to this user - public delete = (sessionId: string, { savedObjectsClient }: BackgroundSessionDependencies) => { - return savedObjectsClient.delete(BACKGROUND_SESSION_TYPE, sessionId); + public delete = (sessionId: string, { savedObjectsClient }: SearchSessionDependencies) => { + return savedObjectsClient.delete(SEARCH_SESSION_TYPE, sessionId); }; /** @@ -318,7 +318,7 @@ export class BackgroundSessionService implements ISessionService { searchRequest: IKibanaSearchRequest, searchId: string, { sessionId, isStored, strategy }: ISearchOptions, - deps: BackgroundSessionDependencies + deps: SearchSessionDependencies ) => { if (!sessionId || !searchId) return; this.logger.debug(`trackId | ${sessionId} | ${searchId}`); @@ -339,7 +339,7 @@ export class BackgroundSessionService implements ISessionService { const map = this.sessionSearchMap.get(sessionId) ?? { insertTime: moment(), retryCount: 0, - ids: new Map(), + ids: new Map(), }; map.ids.set(requestHash, searchInfo); this.sessionSearchMap.set(sessionId, map); @@ -354,7 +354,7 @@ export class BackgroundSessionService implements ISessionService { public getId = async ( searchRequest: IKibanaSearchRequest, { sessionId, isStored, isRestore }: ISearchOptions, - deps: BackgroundSessionDependencies + deps: SearchSessionDependencies ) => { if (!sessionId) { throw new Error('Session ID is required'); @@ -376,7 +376,7 @@ export class BackgroundSessionService implements ISessionService { public asScopedProvider = ({ savedObjects }: CoreStart) => { return (request: KibanaRequest) => { const savedObjectsClient = savedObjects.getScopedClient(request, { - includedHiddenTypes: [BACKGROUND_SESSION_TYPE], + includedHiddenTypes: [SEARCH_SESSION_TYPE], }); const deps = { savedObjectsClient }; return { @@ -384,11 +384,11 @@ export class BackgroundSessionService implements ISessionService { strategy: ISearchStrategy, ...args: Parameters['search']> ) => this.search(strategy, ...args, deps), - save: (sessionId: string, attributes: Partial) => + save: (sessionId: string, attributes: Partial) => this.save(sessionId, attributes, deps), get: (sessionId: string) => this.get(sessionId, deps), - find: (options: BackgroundSessionFindOptions) => this.find(options, deps), - update: (sessionId: string, attributes: Partial) => + find: (options: SearchSessionFindOptions) => this.find(options, deps), + update: (sessionId: string, attributes: Partial) => this.update(sessionId, attributes, deps), delete: (sessionId: string) => this.delete(sessionId, deps), }; diff --git a/x-pack/test/send_search_to_background_integration/services/send_to_background.ts b/x-pack/test/send_search_to_background_integration/services/send_to_background.ts index 7fce2267099b90..319496239de34a 100644 --- a/x-pack/test/send_search_to_background_integration/services/send_to_background.ts +++ b/x-pack/test/send_search_to_background_integration/services/send_to_background.ts @@ -7,8 +7,8 @@ import { FtrProviderContext } from '../ftr_provider_context'; import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper'; -const SEND_TO_BACKGROUND_TEST_SUBJ = 'backgroundSessionIndicator'; -const SEND_TO_BACKGROUND_POPOVER_CONTENT_TEST_SUBJ = 'backgroundSessionIndicatorPopoverContainer'; +const SEND_TO_BACKGROUND_TEST_SUBJ = 'searchSessionIndicator'; +const SEND_TO_BACKGROUND_POPOVER_CONTENT_TEST_SUBJ = 'searchSessionIndicatorPopoverContainer'; type SessionStateType = | 'none' @@ -42,26 +42,26 @@ export function SendToBackgroundProvider({ getService }: FtrProviderContext) { }); } - public async viewBackgroundSessions() { + public async viewSearchSessions() { await this.ensurePopoverOpened(); - await testSubjects.click('backgroundSessionIndicatorViewBackgroundSessionsLink'); + await testSubjects.click('searchSessionIndicatorviewSearchSessionsLink'); } public async save() { await this.ensurePopoverOpened(); - await testSubjects.click('backgroundSessionIndicatorSaveBtn'); + await testSubjects.click('searchSessionIndicatorSaveBtn'); await this.ensurePopoverClosed(); } public async cancel() { await this.ensurePopoverOpened(); - await testSubjects.click('backgroundSessionIndicatorCancelBtn'); + await testSubjects.click('searchSessionIndicatorCancelBtn'); await this.ensurePopoverClosed(); } public async refresh() { await this.ensurePopoverOpened(); - await testSubjects.click('backgroundSessionIndicatorRefreshBtn'); + await testSubjects.click('searchSessionIndicatorRefreshBtn'); await this.ensurePopoverClosed(); } From d0df853367d51def226296d9afc7bfcd892257f3 Mon Sep 17 00:00:00 2001 From: Liza K Date: Wed, 6 Jan 2021 17:33:54 +0200 Subject: [PATCH 41/58] doc --- ...plugin-plugins-data-public.isearchsetup.md | 2 +- ...data-public.isearchsetup.sessionsclient.md | 2 +- ...plugin-plugins-data-public.isearchstart.md | 2 +- ...data-public.isearchstart.sessionsclient.md | 2 +- .../kibana-plugin-plugins-data-public.md | 4 +-- ...ublic.searchsessioninfoprovider.getname.md | 2 +- ...s-data-public.searchsessioninfoprovider.md | 4 +-- ...plugin-plugins-data-public.sessionstate.md | 26 ------------------- src/plugins/data/public/public.api.md | 24 ++++++++--------- 9 files changed, 21 insertions(+), 47 deletions(-) delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.sessionstate.md diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.md index a370c67f460f4e..6768712f385290 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.md @@ -18,6 +18,6 @@ export interface ISearchSetup | --- | --- | --- | | [aggs](./kibana-plugin-plugins-data-public.isearchsetup.aggs.md) | AggsSetup | | | [session](./kibana-plugin-plugins-data-public.isearchsetup.session.md) | ISessionService | Current session management [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) | -| [sessionsClient](./kibana-plugin-plugins-data-public.isearchsetup.sessionsclient.md) | ISessionsClient | Background search sessions SO CRUD [ISessionsClient](./kibana-plugin-plugins-data-public.isessionsclient.md) | +| [sessionsClient](./kibana-plugin-plugins-data-public.isearchsetup.sessionsclient.md) | ISessionsClient | Search sessions SO CRUD [ISessionsClient](./kibana-plugin-plugins-data-public.isessionsclient.md) | | [usageCollector](./kibana-plugin-plugins-data-public.isearchsetup.usagecollector.md) | SearchUsageCollector | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.sessionsclient.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.sessionsclient.md index d9af202cf1018f..4c3c10dec6ab98 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.sessionsclient.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsetup.sessionsclient.md @@ -4,7 +4,7 @@ ## ISearchSetup.sessionsClient property -Background search sessions SO CRUD [ISessionsClient](./kibana-plugin-plugins-data-public.isessionsclient.md) +Search sessions SO CRUD [ISessionsClient](./kibana-plugin-plugins-data-public.isessionsclient.md) Signature: diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.md index a27e155dda1117..34a7614ff2ae36 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.md @@ -20,6 +20,6 @@ export interface ISearchStart | [search](./kibana-plugin-plugins-data-public.isearchstart.search.md) | ISearchGeneric | low level search [ISearchGeneric](./kibana-plugin-plugins-data-public.isearchgeneric.md) | | [searchSource](./kibana-plugin-plugins-data-public.isearchstart.searchsource.md) | ISearchStartSearchSource | high level search [ISearchStartSearchSource](./kibana-plugin-plugins-data-public.isearchstartsearchsource.md) | | [session](./kibana-plugin-plugins-data-public.isearchstart.session.md) | ISessionService | Current session management [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) | -| [sessionsClient](./kibana-plugin-plugins-data-public.isearchstart.sessionsclient.md) | ISessionsClient | Background search sessions SO CRUD [ISessionsClient](./kibana-plugin-plugins-data-public.isessionsclient.md) | +| [sessionsClient](./kibana-plugin-plugins-data-public.isearchstart.sessionsclient.md) | ISessionsClient | Search sessions SO CRUD [ISessionsClient](./kibana-plugin-plugins-data-public.isessionsclient.md) | | [showError](./kibana-plugin-plugins-data-public.isearchstart.showerror.md) | (e: Error) => void | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.sessionsclient.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.sessionsclient.md index 9c3210d2ec4175..2248a9b2f82291 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.sessionsclient.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchstart.sessionsclient.md @@ -4,7 +4,7 @@ ## ISearchStart.sessionsClient property -Background search sessions SO CRUD [ISessionsClient](./kibana-plugin-plugins-data-public.isessionsclient.md) +Search sessions SO CRUD [ISessionsClient](./kibana-plugin-plugins-data-public.isessionsclient.md) Signature: diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md index 2040043d4351b9..6a3e7662e59bc4 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md @@ -34,7 +34,7 @@ | [KBN\_FIELD\_TYPES](./kibana-plugin-plugins-data-public.kbn_field_types.md) | \* | | [METRIC\_TYPES](./kibana-plugin-plugins-data-public.metric_types.md) | | | [QuerySuggestionTypes](./kibana-plugin-plugins-data-public.querysuggestiontypes.md) | | -| [SessionState](./kibana-plugin-plugins-data-public.sessionstate.md) | Possible state that current session can be in | +| [SearchSessionState](./kibana-plugin-plugins-data-public.searchsessionstate.md) | Possible state that current session can be in | | [SortDirection](./kibana-plugin-plugins-data-public.sortdirection.md) | | | [TimeoutErrorMode](./kibana-plugin-plugins-data-public.timeouterrormode.md) | | @@ -90,7 +90,7 @@ | [SavedQueryService](./kibana-plugin-plugins-data-public.savedqueryservice.md) | | | [SearchError](./kibana-plugin-plugins-data-public.searcherror.md) | | | [SearchInterceptorDeps](./kibana-plugin-plugins-data-public.searchinterceptordeps.md) | | -| [SearchSessionInfoProvider](./kibana-plugin-plugins-data-public.searchsessioninfoprovider.md) | Provide info about current search session to be stored in backgroundSearch saved object | +| [SearchSessionInfoProvider](./kibana-plugin-plugins-data-public.searchsessioninfoprovider.md) | Provide info about current search session to be stored in the Search Session saved object | | [SearchSourceFields](./kibana-plugin-plugins-data-public.searchsourcefields.md) | search source fields | | [TabbedAggColumn](./kibana-plugin-plugins-data-public.tabbedaggcolumn.md) | \* | | [TabbedTable](./kibana-plugin-plugins-data-public.tabbedtable.md) | \* | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessioninfoprovider.getname.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessioninfoprovider.getname.md index 2a5e1d2a3135fe..75351434a7bb91 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessioninfoprovider.getname.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessioninfoprovider.getname.md @@ -4,7 +4,7 @@ ## SearchSessionInfoProvider.getName property -User-facing name of the session. e.g. will be displayed in background sessions management list +User-facing name of the session. e.g. will be displayed in saved Search Sessions management list Signature: diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessioninfoprovider.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessioninfoprovider.md index bcc4a5508eb59c..77125bc8deeadd 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessioninfoprovider.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessioninfoprovider.md @@ -4,7 +4,7 @@ ## SearchSessionInfoProvider interface -Provide info about current search session to be stored in backgroundSearch saved object +Provide info about current search session to be stored in the Search Session saved object Signature: @@ -16,6 +16,6 @@ export interface SearchSessionInfoProvider() => Promise<string> | User-facing name of the session. e.g. will be displayed in background sessions management list | +| [getName](./kibana-plugin-plugins-data-public.searchsessioninfoprovider.getname.md) | () => Promise<string> | User-facing name of the session. e.g. will be displayed in saved Search Sessions management list | | [getUrlGeneratorData](./kibana-plugin-plugins-data-public.searchsessioninfoprovider.geturlgeneratordata.md) | () => Promise<{
urlGeneratorId: ID;
initialState: UrlGeneratorStateMapping[ID]['State'];
restoreState: UrlGeneratorStateMapping[ID]['State'];
}> | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.sessionstate.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.sessionstate.md deleted file mode 100644 index 9a60a5b2a9f9be..00000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.sessionstate.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SessionState](./kibana-plugin-plugins-data-public.sessionstate.md) - -## SessionState enum - -Possible state that current session can be in - -Signature: - -```typescript -export declare enum SessionState -``` - -## Enumeration Members - -| Member | Value | Description | -| --- | --- | --- | -| BackgroundCompleted | "backgroundCompleted" | Page load completed with background session created. | -| BackgroundLoading | "backgroundLoading" | Search request was sent to the background. The page is loading in background. | -| Canceled | "canceled" | Current session requests where explicitly canceled by user Displaying none or partial results | -| Completed | "completed" | No action was taken and the page completed loading without background session creation. | -| Loading | "loading" | Pending search request has not been sent to the background yet | -| None | "none" | Session is not active, e.g. didn't start | -| Restored | "restored" | Revisiting the page after background completion | - diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index a824f502cbc2d9..27a40b4e5ffcbe 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -2311,6 +2311,17 @@ export interface SearchSessionInfoProvider; } +// @public +export enum SearchSessionState { + BackgroundCompleted = "backgroundCompleted", + BackgroundLoading = "backgroundLoading", + Canceled = "canceled", + Completed = "completed", + Loading = "loading", + None = "none", + Restored = "restored" +} + // @public (undocumented) export class SearchSource { // Warning: (ae-forgotten-export) The symbol "SearchSourceDependencies" needs to be exported by the entry point index.d.ts @@ -2418,17 +2429,6 @@ export class SearchTimeoutError extends KbnError { mode: TimeoutErrorMode; } -// @public -export enum SearchSessionState { - BackgroundCompleted = "backgroundCompleted", - BackgroundLoading = "backgroundLoading", - Canceled = "canceled", - Completed = "completed", - Loading = "loading", - None = "none", - Restored = "restored" -} - // Warning: (ae-missing-release-tag) "SortDirection" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -2620,7 +2620,7 @@ export const UI_SETTINGS: { // src/plugins/data/public/index.ts:433:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:436:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:45:5 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/search/session/session_service.ts:46:5 - (ae-forgotten-export) The symbol "UrlGeneratorStateMapping" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/search/session/session_service.ts:50:5 - (ae-forgotten-export) The symbol "UrlGeneratorStateMapping" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) From e9b690c6511c9d96619a255ca15eafd91396212c Mon Sep 17 00:00:00 2001 From: Liza K Date: Wed, 6 Jan 2021 17:35:16 +0200 Subject: [PATCH 42/58] doc --- ...-plugins-data-public.searchsessionstate.md | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessionstate.md diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessionstate.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessionstate.md new file mode 100644 index 00000000000000..c650ec6b261662 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsessionstate.md @@ -0,0 +1,26 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSessionState](./kibana-plugin-plugins-data-public.searchsessionstate.md) + +## SearchSessionState enum + +Possible state that current session can be in + +Signature: + +```typescript +export declare enum SearchSessionState +``` + +## Enumeration Members + +| Member | Value | Description | +| --- | --- | --- | +| BackgroundCompleted | "backgroundCompleted" | Page load completed with search session created. | +| BackgroundLoading | "backgroundLoading" | Search session was sent to the background. The page is loading in background. | +| Canceled | "canceled" | Current session requests where explicitly canceled by user Displaying none or partial results | +| Completed | "completed" | No action was taken and the page completed loading without search session creation. | +| Loading | "loading" | Pending search request has not been sent to the background yet | +| None | "none" | Session is not active, e.g. didn't start | +| Restored | "restored" | Revisiting the page after background completion | + From 561825221f6969bc9d4234135011bd69a990278c Mon Sep 17 00:00:00 2001 From: Liza K Date: Wed, 6 Jan 2021 18:25:53 +0200 Subject: [PATCH 43/58] jest fun --- .../search_session_indicator.test.tsx | 10 +++++----- .../search_session_indicator.tsx | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.test.tsx b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.test.tsx index 01026c8beefeb3..6cefa1237f357c 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.test.tsx @@ -23,8 +23,8 @@ test('Loading state', async () => { ); - await userEvent.click(screen.getByLabelText('Loading results')); - await userEvent.click(screen.getByText('Cancel')); + await userEvent.click(screen.getByLabelText('Loading')); + await userEvent.click(screen.getByText('Cancel session')); expect(onCancel).toBeCalled(); }); @@ -37,8 +37,8 @@ test('Completed state', async () => { ); - await userEvent.click(screen.getByLabelText('Results loaded')); - await userEvent.click(screen.getByText('Save')); + await userEvent.click(screen.getByLabelText('Loaded')); + await userEvent.click(screen.getByText('Save session')); expect(onSave).toBeCalled(); }); @@ -52,7 +52,7 @@ test('Loading in the background state', async () => { ); await userEvent.click(screen.getByLabelText('Loading results in the background')); - await userEvent.click(screen.getByText('Cancel')); + await userEvent.click(screen.getByText('Cancel session')); expect(onCancel).toBeCalled(); }); diff --git a/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.tsx b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.tsx index 3ed84e79bb6513..ed022e18c34d72 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/search_session_indicator/search_session_indicator.tsx @@ -157,7 +157,7 @@ const searchSessionIndicatorViewStateToProps: { }, popover: { text: i18n.translate('xpack.data.searchSessionIndicator.resultsLoadedText', { - defaultMessage: 'Results loaded', + defaultMessage: 'Loaded', }), primaryAction: SaveButton, secondaryAction: ViewAllSearchSessionsButton, @@ -206,7 +206,7 @@ const searchSessionIndicatorViewStateToProps: { }, popover: { text: i18n.translate('xpack.data.searchSessionIndicator.resultLoadedInTheBackgroundText', { - defaultMessage: 'Results loaded', + defaultMessage: 'Loaded', }), primaryAction: ViewAllSearchSessionsButton, }, From 20a6d867fb6c9fee65597c2b91a2e0a0840de204 Mon Sep 17 00:00:00 2001 From: Liza K Date: Wed, 6 Jan 2021 19:25:20 +0200 Subject: [PATCH 44/58] rename rfc --- ...ssions_client.png => search_sessions_client.png} | Bin ...ssions_server.png => search_sessions_server.png} | Bin ...ckground_sessions.md => 0013_search_sessions.md} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename rfcs/images/{background_sessions_client.png => search_sessions_client.png} (100%) rename rfcs/images/{background_sessions_server.png => search_sessions_server.png} (100%) rename rfcs/text/{0013_background_sessions.md => 0013_search_sessions.md} (100%) diff --git a/rfcs/images/background_sessions_client.png b/rfcs/images/search_sessions_client.png similarity index 100% rename from rfcs/images/background_sessions_client.png rename to rfcs/images/search_sessions_client.png diff --git a/rfcs/images/background_sessions_server.png b/rfcs/images/search_sessions_server.png similarity index 100% rename from rfcs/images/background_sessions_server.png rename to rfcs/images/search_sessions_server.png diff --git a/rfcs/text/0013_background_sessions.md b/rfcs/text/0013_search_sessions.md similarity index 100% rename from rfcs/text/0013_background_sessions.md rename to rfcs/text/0013_search_sessions.md From 530f9be4f794158d0ed031dcf929e1102d4f86c0 Mon Sep 17 00:00:00 2001 From: Liza K Date: Thu, 7 Jan 2021 11:41:44 +0200 Subject: [PATCH 45/58] translations --- .../translations/translations/ja-JP.json | 24 ------------------- .../translations/translations/zh-CN.json | 24 ------------------- 2 files changed, 48 deletions(-) diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index fef61667168a96..798e1b817e4964 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -7138,30 +7138,6 @@ "xpack.dashboardMode.uiSettings.dashboardsOnlyRolesTitle": "ダッシュボード専用ロール", "xpack.data.advancedSettings.searchTimeout": "検索タイムアウト", "xpack.data.advancedSettings.searchTimeoutDesc": "検索セッションの最大タイムアウトを変更するか、0 に設定してタイムアウトを無効にすると、クエリは完了するまで実行されます。", - "xpack.data.backgroundSessionIndicator.cancelButtonText": "キャンセル", - "xpack.data.backgroundSessionIndicator.canceledIconAriaLabel": "キャンセル", - "xpack.data.backgroundSessionIndicator.canceledText": "検索がキャンセルされました", - "xpack.data.backgroundSessionIndicator.canceledTooltipText": "検索がキャンセルされました", - "xpack.data.backgroundSessionIndicator.continueInBackgroundButtonText": "バックグラウンドで続行", - "xpack.data.backgroundSessionIndicator.disabledDueToAutoRefreshMessage": "自動更新が有効な場合は、バックグラウンドに送信できません。", - "xpack.data.backgroundSessionIndicator.loadingInTheBackgroundIconAriaLabel": "バックグラウンドで結果を読み込んでいます", - "xpack.data.backgroundSessionIndicator.loadingInTheBackgroundIconTooltipText": "バックグラウンドで結果を読み込んでいます", - "xpack.data.backgroundSessionIndicator.loadingInTheBackgroundText": "バックグラウンドで読み込んでいます", - "xpack.data.backgroundSessionIndicator.loadingResultsIconAriaLabel": "結果を読み込み中", - "xpack.data.backgroundSessionIndicator.loadingResultsIconTooltipText": "結果を読み込み中", - "xpack.data.backgroundSessionIndicator.loadingResultsText": "読み込み中", - "xpack.data.backgroundSessionIndicator.refreshButtonText": "更新", - "xpack.data.backgroundSessionIndicator.restoredResultsIconAriaLabel": "結果が最新ではありません", - "xpack.data.backgroundSessionIndicator.restoredResultsTooltipText": "結果が最新ではありません", - "xpack.data.backgroundSessionIndicator.restoredText": "結果が最新ではありません", - "xpack.data.backgroundSessionIndicator.resultLoadedInTheBackgroundIconAraText": "結果がバックグラウンドで読み込まれました", - "xpack.data.backgroundSessionIndicator.resultLoadedInTheBackgroundIconTooltipText": "結果がバックグラウンドで読み込まれました", - "xpack.data.backgroundSessionIndicator.resultLoadedInTheBackgroundText": "結果が読み込まれました", - "xpack.data.backgroundSessionIndicator.resultsLoadedIconAriaLabel": "結果が読み込まれました", - "xpack.data.backgroundSessionIndicator.resultsLoadedIconTooltipText": "結果が読み込まれました", - "xpack.data.backgroundSessionIndicator.resultsLoadedText": "結果が読み込まれました", - "xpack.data.backgroundSessionIndicator.saveButtonText": "保存", - "xpack.data.backgroundSessionIndicator.viewBackgroundSessionsLinkText": "バックグラウンドセッションを表示", "xpack.data.kueryAutocomplete.andOperatorDescription": "{bothArguments} が true であることを条件とする", "xpack.data.kueryAutocomplete.andOperatorDescription.bothArgumentsText": "両方の引数", "xpack.data.kueryAutocomplete.equalOperatorDescription": "一部の値に{equals}", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index d4ec4407080f43..254010b4f3f6c9 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -7157,30 +7157,6 @@ "xpack.dashboardMode.uiSettings.dashboardsOnlyRolesTitle": "仅限仪表板的角色", "xpack.data.advancedSettings.searchTimeout": "搜索超时", "xpack.data.advancedSettings.searchTimeoutDesc": "更改搜索会话的最大超时值,或设置为 0 以禁用超时,让查询运行至结束。", - "xpack.data.backgroundSessionIndicator.cancelButtonText": "取消", - "xpack.data.backgroundSessionIndicator.canceledIconAriaLabel": "已取消", - "xpack.data.backgroundSessionIndicator.canceledText": "搜索已取消", - "xpack.data.backgroundSessionIndicator.canceledTooltipText": "搜索已取消", - "xpack.data.backgroundSessionIndicator.continueInBackgroundButtonText": "在后台继续", - "xpack.data.backgroundSessionIndicator.disabledDueToAutoRefreshMessage": "启用自动刷新后,“发送到后台”不可用。", - "xpack.data.backgroundSessionIndicator.loadingInTheBackgroundIconAriaLabel": "正在后台加载结果", - "xpack.data.backgroundSessionIndicator.loadingInTheBackgroundIconTooltipText": "正在后台加载结果", - "xpack.data.backgroundSessionIndicator.loadingInTheBackgroundText": "正在后台加载", - "xpack.data.backgroundSessionIndicator.loadingResultsIconAriaLabel": "正在加载结果", - "xpack.data.backgroundSessionIndicator.loadingResultsIconTooltipText": "正在加载结果", - "xpack.data.backgroundSessionIndicator.loadingResultsText": "正在加载", - "xpack.data.backgroundSessionIndicator.refreshButtonText": "刷新", - "xpack.data.backgroundSessionIndicator.restoredResultsIconAriaLabel": "结果不再是最新", - "xpack.data.backgroundSessionIndicator.restoredResultsTooltipText": "结果不再是最新", - "xpack.data.backgroundSessionIndicator.restoredText": "结果不再是最新", - "xpack.data.backgroundSessionIndicator.resultLoadedInTheBackgroundIconAraText": "结果已后台加载", - "xpack.data.backgroundSessionIndicator.resultLoadedInTheBackgroundIconTooltipText": "结果已后台加载", - "xpack.data.backgroundSessionIndicator.resultLoadedInTheBackgroundText": "结果已加载", - "xpack.data.backgroundSessionIndicator.resultsLoadedIconAriaLabel": "结果已加载", - "xpack.data.backgroundSessionIndicator.resultsLoadedIconTooltipText": "结果已加载", - "xpack.data.backgroundSessionIndicator.resultsLoadedText": "结果已加载", - "xpack.data.backgroundSessionIndicator.saveButtonText": "保存", - "xpack.data.backgroundSessionIndicator.viewBackgroundSessionsLinkText": "查看后台会话", "xpack.data.kueryAutocomplete.andOperatorDescription": "需要{bothArguments}为 true", "xpack.data.kueryAutocomplete.andOperatorDescription.bothArgumentsText": "两个参数都", "xpack.data.kueryAutocomplete.equalOperatorDescription": "{equals}某一值", From ea8d1a5f269b0329cc9c425a9b03e550bfbb2231 Mon Sep 17 00:00:00 2001 From: Liza K Date: Thu, 7 Jan 2021 12:04:16 +0200 Subject: [PATCH 46/58] merge fix --- .../data_enhanced/server/search/session/get_search_status.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts b/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts index f3f759cb7e8f12..51ad8bf361be35 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts @@ -9,12 +9,12 @@ import { ApiResponse } from '@elastic/elasticsearch'; import { ElasticsearchClient } from 'src/core/server'; import { isAsyncSearchStatusResponse, SearchStatus } from './types'; import { AsyncSearchResponse } from '../types'; -import { BackgroundSessionSearchInfo } from '../../../common'; +import { SearchSessionRequestInfo } from '../../../common'; export async function getSearchStatus( client: ElasticsearchClient, asyncId: string -): Promise> { +): Promise> { try { // TODO: Handle strategies other than KQL const response: ApiResponse = await client.asyncSearch.status({ From 1cdd02c24fd986627c3a374176b00aa8499f9cc0 Mon Sep 17 00:00:00 2001 From: Liza K Date: Thu, 7 Jan 2021 12:14:20 +0200 Subject: [PATCH 47/58] merge fix --- .../session/check_running_sessions.test.ts | 10 +++---- .../search/session/check_running_sessions.ts | 24 ++++++++--------- .../search/session/get_session_status.test.ts | 10 +++---- .../search/session/get_session_status.ts | 12 ++++----- .../server/search/session/index.ts | 2 +- .../server/search/session/monitoring_task.ts | 26 +++++++++---------- .../server/search/session/session_service.ts | 6 ++--- 7 files changed, 42 insertions(+), 48 deletions(-) diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts index a964803bf713db..b1851c9891face 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts @@ -5,7 +5,7 @@ */ import { checkRunningSessions } from './check_running_sessions'; -import { BackgroundSessionSavedObjectAttributes, BackgroundSessionStatus } from '../../../common'; +import { SearchSessionStatus, SearchSessionSavedObjectAttributes } from '../../../common'; import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; import type { SavedObjectsClientContract } from 'kibana/server'; import { SearchStatus } from './types'; @@ -140,8 +140,8 @@ describe('getSearchStatus', () => { await checkRunningSessions(savedObjectsClient, mockClient, mockLogger); const [updateInput] = savedObjectsClient.bulkUpdate.mock.calls[0]; - const updatedAttributes = updateInput[0].attributes as BackgroundSessionSavedObjectAttributes; - expect(updatedAttributes.status).toBe(BackgroundSessionStatus.COMPLETE); + const updatedAttributes = updateInput[0].attributes as SearchSessionSavedObjectAttributes; + expect(updatedAttributes.status).toBe(SearchSessionStatus.COMPLETE); expect(updatedAttributes.idMapping['search-hash'].status).toBe(SearchStatus.COMPLETE); expect(updatedAttributes.idMapping['search-hash'].error).toBeUndefined(); }); @@ -173,8 +173,8 @@ describe('getSearchStatus', () => { await checkRunningSessions(savedObjectsClient, mockClient, mockLogger); const [updateInput] = savedObjectsClient.bulkUpdate.mock.calls[0]; - const updatedAttributes = updateInput[0].attributes as BackgroundSessionSavedObjectAttributes; - expect(updatedAttributes.status).toBe(BackgroundSessionStatus.ERROR); + const updatedAttributes = updateInput[0].attributes as SearchSessionSavedObjectAttributes; + expect(updatedAttributes.status).toBe(SearchSessionStatus.ERROR); expect(updatedAttributes.idMapping['search-hash'].status).toBe(SearchStatus.ERROR); expect(updatedAttributes.idMapping['search-hash'].error).toBe( 'Search completed with a 500 status' diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts index 8275a8e10a3bef..5a765ce1eb5b28 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts @@ -10,8 +10,8 @@ import { SavedObjectsFindResult, SavedObjectsClientContract, } from 'kibana/server'; -import { BackgroundSessionStatus, BackgroundSessionSavedObjectAttributes } from '../../../common'; -import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; +import { SearchSessionStatus, SearchSessionSavedObjectAttributes } from '../../../common'; +import { SEARCH_SESSION_TYPE } from '../../saved_objects'; import { getSearchStatus } from './get_search_status'; import { getSessionStatus } from './get_session_status'; import { SearchStatus } from './types'; @@ -22,27 +22,25 @@ export async function checkRunningSessions( logger: Logger ): Promise { try { - const runningBackgroundSearchesResponse = await savedObjectsClient.find( + const runningSearchSessionsResponse = await savedObjectsClient.find( { - type: BACKGROUND_SESSION_TYPE, - search: BackgroundSessionStatus.IN_PROGRESS.toString(), + type: SEARCH_SESSION_TYPE, + search: SearchSessionStatus.IN_PROGRESS.toString(), searchFields: ['status'], namespaces: ['*'], } ); - if (!runningBackgroundSearchesResponse.total) return; + if (!runningSearchSessionsResponse.total) return; - logger.debug(`Found ${runningBackgroundSearchesResponse.total} running sessions`); + logger.debug(`Found ${runningSearchSessionsResponse.total} running sessions`); - const updatedSessions = new Array< - SavedObjectsFindResult - >(); + const updatedSessions = new Array>(); let sessionUpdated = false; await Promise.all( - runningBackgroundSearchesResponse.saved_objects.map(async (session) => { + runningSearchSessionsResponse.saved_objects.map(async (session) => { // Check statuses of all running searches await Promise.all( Object.keys(session.attributes.idMapping).map(async (searchKey: string) => { @@ -63,7 +61,7 @@ export async function checkRunningSessions( // And only then derive the session's status const sessionStatus = getSessionStatus(session.attributes); - if (sessionStatus !== BackgroundSessionStatus.IN_PROGRESS) { + if (sessionStatus !== SearchSessionStatus.IN_PROGRESS) { session.attributes.status = sessionStatus; sessionUpdated = true; } @@ -76,7 +74,7 @@ export async function checkRunningSessions( if (updatedSessions.length) { // If there's an error, we'll try again in the next iteration, so there's no need to check the output. - const updatedResponse = await savedObjectsClient.bulkUpdate( + const updatedResponse = await savedObjectsClient.bulkUpdate( updatedSessions ); logger.debug(`Updated ${updatedResponse.saved_objects.length} background sessions`); diff --git a/x-pack/plugins/data_enhanced/server/search/session/get_session_status.test.ts b/x-pack/plugins/data_enhanced/server/search/session/get_session_status.test.ts index 1d394af732b76d..35bfdeee691e21 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/get_session_status.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/get_session_status.test.ts @@ -6,14 +6,14 @@ import { SearchStatus } from './types'; import { getSessionStatus } from './get_session_status'; -import { BackgroundSessionStatus } from '../../../common'; +import { SearchSessionStatus } from '../../../common'; describe('getSessionStatus', () => { test("returns an in_progress status if there's nothing inside the session", () => { const session: any = { idMapping: {}, }; - expect(getSessionStatus(session)).toBe(BackgroundSessionStatus.IN_PROGRESS); + expect(getSessionStatus(session)).toBe(SearchSessionStatus.IN_PROGRESS); }); test("returns an error status if there's at least one error", () => { @@ -24,7 +24,7 @@ describe('getSessionStatus', () => { c: { status: SearchStatus.COMPLETE }, }, }; - expect(getSessionStatus(session)).toBe(BackgroundSessionStatus.ERROR); + expect(getSessionStatus(session)).toBe(SearchSessionStatus.ERROR); }); test('returns a complete status if all are complete', () => { @@ -35,7 +35,7 @@ describe('getSessionStatus', () => { c: { status: SearchStatus.COMPLETE }, }, }; - expect(getSessionStatus(session)).toBe(BackgroundSessionStatus.COMPLETE); + expect(getSessionStatus(session)).toBe(SearchSessionStatus.COMPLETE); }); test('returns a running status if some are still running', () => { @@ -46,6 +46,6 @@ describe('getSessionStatus', () => { c: { status: SearchStatus.IN_PROGRESS }, }, }; - expect(getSessionStatus(session)).toBe(BackgroundSessionStatus.IN_PROGRESS); + expect(getSessionStatus(session)).toBe(SearchSessionStatus.IN_PROGRESS); }); }); diff --git a/x-pack/plugins/data_enhanced/server/search/session/get_session_status.ts b/x-pack/plugins/data_enhanced/server/search/session/get_session_status.ts index 2c78d15dd1a96a..296f4e489932d3 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/get_session_status.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/get_session_status.ts @@ -4,21 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { BackgroundSessionSavedObjectAttributes, BackgroundSessionStatus } from '../../../common'; +import { SearchSessionSavedObjectAttributes, SearchSessionStatus } from '../../../common'; import { SearchStatus } from './types'; -export function getSessionStatus( - session: BackgroundSessionSavedObjectAttributes -): BackgroundSessionStatus { +export function getSessionStatus(session: SearchSessionSavedObjectAttributes): SearchSessionStatus { const searchStatuses = Object.values(session.idMapping); if (searchStatuses.some((item) => item.status === SearchStatus.ERROR)) { - return BackgroundSessionStatus.ERROR; + return SearchSessionStatus.ERROR; } else if ( searchStatuses.length > 0 && searchStatuses.every((item) => item.status === SearchStatus.COMPLETE) ) { - return BackgroundSessionStatus.COMPLETE; + return SearchSessionStatus.COMPLETE; } else { - return BackgroundSessionStatus.IN_PROGRESS; + return SearchSessionStatus.IN_PROGRESS; } } diff --git a/x-pack/plugins/data_enhanced/server/search/session/index.ts b/x-pack/plugins/data_enhanced/server/search/session/index.ts index e8c52c2d701ef2..8d5e21f3d82768 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/index.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/index.ts @@ -5,4 +5,4 @@ */ export * from './session_service'; -export { registerBackgroundSessionsTask, scheduleBackgroundSessionsTasks } from './monitoring_task'; +export { registerSearchSessionsTask, scheduleSearchSessionsTasks } from './monitoring_task'; diff --git a/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts b/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts index d3b2277e6aba1e..e7330fe493fb52 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts @@ -11,20 +11,18 @@ import { } from '../../../../task_manager/server'; import { checkRunningSessions } from './check_running_sessions'; import { CoreSetup, SavedObjectsClient, Logger } from '../../../../../../src/core/server'; -import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; +import { SEARCH_SESSION_TYPE } from '../../saved_objects'; -export const BACKGROUND_SESSIONS_TASK_TYPE = 'bg_monitor'; -export const BACKGROUND_SESSIONS_TASK_ID = `data_enhanced_${BACKGROUND_SESSIONS_TASK_TYPE}`; +export const SEARCH_SESSIONS_TASK_TYPE = 'bg_monitor'; +export const SEARCH_SESSIONS_TASK_ID = `data_enhanced_${SEARCH_SESSIONS_TASK_TYPE}`; export const MONITOR_INTERVAL = `15s`; -function backgroundSessionRunner(core: CoreSetup, logger: Logger) { +function searchSessionRunner(core: CoreSetup, logger: Logger) { return ({ taskInstance }: RunContext) => { return { async run() { const [coreStart] = await core.getStartServices(); - const internalRepo = coreStart.savedObjects.createInternalRepository([ - BACKGROUND_SESSION_TYPE, - ]); + const internalRepo = coreStart.savedObjects.createInternalRepository([SEARCH_SESSION_TYPE]); const internalSavedObjectsClient = new SavedObjectsClient(internalRepo); await checkRunningSessions( internalSavedObjectsClient, @@ -41,32 +39,32 @@ function backgroundSessionRunner(core: CoreSetup, logger: Logger) { }; } -export function registerBackgroundSessionsTask( +export function registerSearchSessionsTask( core: CoreSetup, taskManager: TaskManagerSetupContract, logger: Logger ) { taskManager.registerTaskDefinitions({ - [BACKGROUND_SESSIONS_TASK_TYPE]: { + [SEARCH_SESSIONS_TASK_TYPE]: { title: 'Background Session Monitor', - createTaskRunner: backgroundSessionRunner(core, logger), + createTaskRunner: searchSessionRunner(core, logger), }, }); } -export async function scheduleBackgroundSessionsTasks( +export async function scheduleSearchSessionsTasks( taskManager: TaskManagerStartContract, logger: Logger ) { try { // delete previous task - await taskManager.remove(BACKGROUND_SESSIONS_TASK_ID); + await taskManager.remove(SEARCH_SESSIONS_TASK_ID); } catch (e) {} // eslint-disable-line no-empty try { await taskManager.ensureScheduled({ - id: BACKGROUND_SESSIONS_TASK_ID, - taskType: BACKGROUND_SESSIONS_TASK_TYPE, + id: SEARCH_SESSIONS_TASK_ID, + taskType: SEARCH_SESSIONS_TASK_TYPE, schedule: { interval: MONITOR_INTERVAL, }, diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts index 2d099d42843071..8c9e0dad4957e8 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts @@ -43,7 +43,7 @@ import { import { SEARCH_SESSION_TYPE } from '../../saved_objects'; import { createRequestHash } from './utils'; import { ConfigSchema } from '../../../config'; -import { registerBackgroundSessionsTask, scheduleBackgroundSessionsTasks } from './monitoring_task'; +import { registerSearchSessionsTask, scheduleSearchSessionsTasks } from './monitoring_task'; import { DEFAULT_EXPIRATION, INMEM_MAX_SESSIONS, @@ -83,7 +83,7 @@ export class SearchSessionService implements ISessionService { constructor(private readonly logger: Logger) {} public setup(core: CoreSetup, deps: SetupDependencies) { - registerBackgroundSessionsTask(core, deps.taskManager, this.logger); + registerSearchSessionsTask(core, deps.taskManager, this.logger); } public async start(core: CoreStart, deps: StartDependencies) { @@ -98,7 +98,7 @@ export class SearchSessionService implements ISessionService { private setupMonitoring = async (core: CoreStart, deps: StartDependencies) => { const config = await deps.config$.pipe(first()).toPromise(); if (config.search.sendToBackground.enabled) { - scheduleBackgroundSessionsTasks(deps.taskManager, this.logger); + scheduleSearchSessionsTasks(deps.taskManager, this.logger); this.logger.debug(`setupMonitoring | Enabling monitoring`); const internalRepo = core.savedObjects.createInternalRepository([SEARCH_SESSION_TYPE]); this.internalSavedObjectsClient = new SavedObjectsClient(internalRepo); From 5e5241883e039195c98a3babe11c9fc70ba14ec0 Mon Sep 17 00:00:00 2001 From: Liza K Date: Thu, 7 Jan 2021 14:14:09 +0200 Subject: [PATCH 48/58] code review --- .../session/check_running_sessions.test.ts | 24 +++++--- .../search/session/check_running_sessions.ts | 27 ++++++--- .../search/session/get_search_status.test.ts | 32 +++++++---- .../search/session/get_search_status.ts | 55 +++++++------------ .../server/search/session/monitoring_task.ts | 8 +-- .../server/search/session/types.ts | 6 -- .../data_enhanced/server/search/types.ts | 2 +- 7 files changed, 81 insertions(+), 73 deletions(-) diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts index b1851c9891face..4334ab3bc29036 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts @@ -77,8 +77,10 @@ describe('getSearchStatus', () => { } as any); mockClient.asyncSearch.status.mockResolvedValue({ - is_partial: true, - is_running: true, + body: { + is_partial: true, + is_running: true, + }, }); await checkRunningSessions(savedObjectsClient, mockClient, mockLogger); @@ -133,12 +135,16 @@ describe('getSearchStatus', () => { } as any); mockClient.asyncSearch.status.mockResolvedValue({ - is_partial: false, - is_running: false, - completion_status: 200, + body: { + is_partial: false, + is_running: false, + completion_status: 200, + }, }); await checkRunningSessions(savedObjectsClient, mockClient, mockLogger); + + expect(mockClient.asyncSearch.status).toBeCalledWith({ id: 'search-id' }); const [updateInput] = savedObjectsClient.bulkUpdate.mock.calls[0]; const updatedAttributes = updateInput[0].attributes as SearchSessionSavedObjectAttributes; expect(updatedAttributes.status).toBe(SearchSessionStatus.COMPLETE); @@ -165,9 +171,11 @@ describe('getSearchStatus', () => { } as any); mockClient.asyncSearch.status.mockResolvedValue({ - is_partial: false, - is_running: false, - completion_status: 500, + body: { + is_partial: false, + is_running: false, + completion_status: 500, + }, }); await checkRunningSessions(savedObjectsClient, mockClient, mockLogger); diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts index 5a765ce1eb5b28..54591f0982e679 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts @@ -44,16 +44,29 @@ export async function checkRunningSessions( // Check statuses of all running searches await Promise.all( Object.keys(session.attributes.idMapping).map(async (searchKey: string) => { + const updateSearchRequest = (currentStatus: any) => { + sessionUpdated = true; + session.attributes.idMapping[searchKey] = { + ...session.attributes.idMapping[searchKey], + ...currentStatus, + ...(currentStatus.status === SearchStatus.ERROR ? {} : { error: undefined }), + }; + }; + const searchInfo = session.attributes.idMapping[searchKey]; if (searchInfo.status === SearchStatus.IN_PROGRESS) { - const currentStatus = await getSearchStatus(client, searchInfo.strategy); + try { + const currentStatus = await getSearchStatus(client, searchInfo.id); - if (currentStatus.status !== SearchStatus.IN_PROGRESS) { - sessionUpdated = true; - session.attributes.idMapping[searchKey] = { - ...session.attributes.idMapping[searchKey], - ...currentStatus, - }; + if (currentStatus.status !== SearchStatus.IN_PROGRESS) { + updateSearchRequest(currentStatus); + } + } catch (e) { + logger.error(e); + updateSearchRequest({ + status: SearchStatus.ERROR, + error: e.message || e.meta.error?.caused_by?.reason, + }); } } }) diff --git a/x-pack/plugins/data_enhanced/server/search/session/get_search_status.test.ts b/x-pack/plugins/data_enhanced/server/search/session/get_search_status.test.ts index 2aaa8d5203aaf0..e66ce613b71d9a 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/get_search_status.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/get_search_status.test.ts @@ -19,18 +19,22 @@ describe('getSearchStatus', () => { test('returns an error status if search is partial and not running', () => { mockClient.asyncSearch.status.mockResolvedValue({ - is_partial: true, - is_running: false, - completion_status: 200, + body: { + is_partial: true, + is_running: false, + completion_status: 200, + }, }); expect(getSearchStatus(mockClient, '123')).resolves.toBe(SearchStatus.ERROR); }); test('returns an error status if completion_status is an error', () => { mockClient.asyncSearch.status.mockResolvedValue({ - is_partial: false, - is_running: false, - completion_status: 500, + body: { + is_partial: false, + is_running: false, + completion_status: 500, + }, }); expect(getSearchStatus(mockClient, '123')).resolves.toBe(SearchStatus.ERROR); }); @@ -53,18 +57,22 @@ describe('getSearchStatus', () => { test('returns a complete status', () => { mockClient.asyncSearch.status.mockResolvedValue({ - is_partial: false, - is_running: false, - completion_status: 200, + body: { + is_partial: false, + is_running: false, + completion_status: 200, + }, }); expect(getSearchStatus(mockClient, '123')).resolves.toBe(SearchStatus.COMPLETE); }); test('returns a running status otherwise', () => { mockClient.asyncSearch.status.mockResolvedValue({ - is_partial: false, - is_running: true, - completion_status: undefined, + body: { + is_partial: false, + is_running: true, + completion_status: undefined, + }, }); expect(getSearchStatus(mockClient, '123')).resolves.toBe(SearchStatus.IN_PROGRESS); }); diff --git a/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts b/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts index 51ad8bf361be35..a8ff5c8fa70339 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts @@ -7,49 +7,34 @@ import { i18n } from '@kbn/i18n'; import { ApiResponse } from '@elastic/elasticsearch'; import { ElasticsearchClient } from 'src/core/server'; -import { isAsyncSearchStatusResponse, SearchStatus } from './types'; -import { AsyncSearchResponse } from '../types'; +import { SearchStatus } from './types'; +import { AsyncSearchStatusResponse } from '../types'; import { SearchSessionRequestInfo } from '../../../common'; export async function getSearchStatus( client: ElasticsearchClient, asyncId: string ): Promise> { - try { - // TODO: Handle strategies other than KQL - const response: ApiResponse = await client.asyncSearch.status({ - id: asyncId, - }); - if (isAsyncSearchStatusResponse(response)) { - if ((response.is_partial && !response.is_running) || response.completion_status >= 400) { - return { - status: SearchStatus.ERROR, - error: i18n.translate('xpack.data.search.statusError', { - defaultMessage: `Search completed with a {errorCode} status`, - values: { errorCode: response.completion_status }, - }), - }; - } else if (!response.is_partial && !response.is_running) { - return { - status: SearchStatus.COMPLETE, - }; - } else { - return { - status: SearchStatus.IN_PROGRESS, - }; - } - } else { - return { - status: SearchStatus.ERROR, - error: 'Unknown response format', - }; - } - } catch (e) { - // eslint-disable-next-line no-console - console.error(e); + // TODO: Handle strategies other than the default one + const apiResponse: ApiResponse = await client.asyncSearch.status({ + id: asyncId, + }); + const response = apiResponse.body; + if ((response.is_partial && !response.is_running) || response.completion_status >= 400) { return { status: SearchStatus.ERROR, - error: e.message, + error: i18n.translate('xpack.data.search.statusError', { + defaultMessage: `Search completed with a {errorCode} status`, + values: { errorCode: response.completion_status }, + }), + }; + } else if (!response.is_partial && !response.is_running) { + return { + status: SearchStatus.COMPLETE, + }; + } else { + return { + status: SearchStatus.IN_PROGRESS, }; } } diff --git a/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts b/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts index e7330fe493fb52..f5e0218f9c0a29 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts @@ -15,7 +15,7 @@ import { SEARCH_SESSION_TYPE } from '../../saved_objects'; export const SEARCH_SESSIONS_TASK_TYPE = 'bg_monitor'; export const SEARCH_SESSIONS_TASK_ID = `data_enhanced_${SEARCH_SESSIONS_TASK_TYPE}`; -export const MONITOR_INTERVAL = `15s`; +export const MONITOR_INTERVAL = 15; // in seconds function searchSessionRunner(core: CoreSetup, logger: Logger) { return ({ taskInstance }: RunContext) => { @@ -31,7 +31,7 @@ function searchSessionRunner(core: CoreSetup, logger: Logger) { ); return { - runAt: new Date(Date.now() + 10 * 1000), + runAt: new Date(Date.now() + MONITOR_INTERVAL * 1000), state: {}, }; }, @@ -46,7 +46,7 @@ export function registerSearchSessionsTask( ) { taskManager.registerTaskDefinitions({ [SEARCH_SESSIONS_TASK_TYPE]: { - title: 'Background Session Monitor', + title: 'Search Sessions Monitor', createTaskRunner: searchSessionRunner(core, logger), }, }); @@ -66,7 +66,7 @@ export async function scheduleSearchSessionsTasks( id: SEARCH_SESSIONS_TASK_ID, taskType: SEARCH_SESSIONS_TASK_TYPE, schedule: { - interval: MONITOR_INTERVAL, + interval: `${MONITOR_INTERVAL}s`, }, state: {}, params: {}, diff --git a/x-pack/plugins/data_enhanced/server/search/session/types.ts b/x-pack/plugins/data_enhanced/server/search/session/types.ts index 4600ccb3a9bc8a..c30e03f70d2dc0 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/types.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/types.ts @@ -4,14 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AsyncSearchStatusResponse } from '../types'; - export enum SearchStatus { IN_PROGRESS = 'in_progress', ERROR = 'error', COMPLETE = 'complete', } - -export function isAsyncSearchStatusResponse(response: any): response is AsyncSearchStatusResponse { - return response.is_partial !== undefined && response.is_running !== undefined; -} diff --git a/x-pack/plugins/data_enhanced/server/search/types.ts b/x-pack/plugins/data_enhanced/server/search/types.ts index b052d162ee427b..4401b7211fb621 100644 --- a/x-pack/plugins/data_enhanced/server/search/types.ts +++ b/x-pack/plugins/data_enhanced/server/search/types.ts @@ -14,7 +14,7 @@ export interface AsyncSearchResponse { is_partial: boolean; is_running: boolean; } -export interface AsyncSearchStatusResponse extends Omit { +export interface AsyncSearchStatusResponse extends Omit { completion_status: number; _shards: ShardsResponse; } From 19dee24495b8fa77007b94ed99547d86ce26f47b Mon Sep 17 00:00:00 2001 From: Liza K Date: Thu, 7 Jan 2021 15:08:39 +0200 Subject: [PATCH 49/58] update so name in features --- .../server/__snapshots__/oss_features.test.ts.snap | 8 ++++---- x-pack/plugins/features/server/oss_features.ts | 4 ++-- .../common/fixtures/es_archiver/apm_8.0.0/mappings.json | 4 ++-- .../es_archives/task_manager_removed_types/mappings.json | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/features/server/__snapshots__/oss_features.test.ts.snap b/x-pack/plugins/features/server/__snapshots__/oss_features.test.ts.snap index b1d7a2d4349681..8432fdac93a9a7 100644 --- a/x-pack/plugins/features/server/__snapshots__/oss_features.test.ts.snap +++ b/x-pack/plugins/features/server/__snapshots__/oss_features.test.ts.snap @@ -73,7 +73,7 @@ Array [ "dashboard", "query", "url", - "background-session", + "search-session", ], "read": Array [ "index-pattern", @@ -207,7 +207,7 @@ Array [ "query", "index-pattern", "url", - "background-session", + "search-session", ], "read": Array [], }, @@ -559,7 +559,7 @@ Array [ "dashboard", "query", "url", - "background-session", + "search-session", ], "read": Array [ "index-pattern", @@ -693,7 +693,7 @@ Array [ "query", "index-pattern", "url", - "background-session", + "search-session", ], "read": Array [], }, diff --git a/x-pack/plugins/features/server/oss_features.ts b/x-pack/plugins/features/server/oss_features.ts index c38fdf8b29d121..daa5d4b5d4219c 100644 --- a/x-pack/plugins/features/server/oss_features.ts +++ b/x-pack/plugins/features/server/oss_features.ts @@ -89,7 +89,7 @@ export const buildOSSFeatures = ({ savedObjectTypes, includeTimelion }: BuildOSS ), includeIn: 'all', savedObject: { - all: ['background-session'], + all: ['search-session'], read: [], }, ui: ['storeSearchSession'], @@ -254,7 +254,7 @@ export const buildOSSFeatures = ({ savedObjectTypes, includeTimelion }: BuildOSS ), includeIn: 'all', savedObject: { - all: ['background-session'], + all: ['search-session'], read: [], }, ui: ['storeSearchSession'], diff --git a/x-pack/test/apm_api_integration/common/fixtures/es_archiver/apm_8.0.0/mappings.json b/x-pack/test/apm_api_integration/common/fixtures/es_archiver/apm_8.0.0/mappings.json index 3ace9d55f8d6e0..13bfec74269b7e 100644 --- a/x-pack/test/apm_api_integration/common/fixtures/es_archiver/apm_8.0.0/mappings.json +++ b/x-pack/test/apm_api_integration/common/fixtures/es_archiver/apm_8.0.0/mappings.json @@ -19,7 +19,7 @@ "application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd", "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", "application_usage_transactional": "3d1b76c39bfb2cc8296b024d73854724", - "background-session": "404e2e2355a045f400c393e751445b42", + "search-session": "404e2e2355a045f400c393e751445b42", "canvas-element": "7390014e1091044523666d97247392fc", "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715", @@ -297,7 +297,7 @@ "dynamic": "false", "type": "object" }, - "background-session": { + "search-session": { "properties": { "appId": { "type": "keyword" diff --git a/x-pack/test/functional/es_archives/task_manager_removed_types/mappings.json b/x-pack/test/functional/es_archives/task_manager_removed_types/mappings.json index c3a10064e905ed..757a1780e279e9 100644 --- a/x-pack/test/functional/es_archives/task_manager_removed_types/mappings.json +++ b/x-pack/test/functional/es_archives/task_manager_removed_types/mappings.json @@ -19,7 +19,7 @@ "application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd", "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", "application_usage_transactional": "3d1b76c39bfb2cc8296b024d73854724", - "background-session": "721df406dbb7e35ac22e4df6c3ad2b2a", + "search-session": "721df406dbb7e35ac22e4df6c3ad2b2a", "canvas-element": "7390014e1091044523666d97247392fc", "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715", From c71139deb572e154a219490546b2fa007075a7c6 Mon Sep 17 00:00:00 2001 From: Liza K Date: Thu, 7 Jan 2021 16:01:44 +0200 Subject: [PATCH 50/58] Move deleteTaskIfItExists to task manager --- x-pack/plugins/alerts/server/alerts_client/alerts_client.ts | 3 +-- .../data_enhanced/server/search/session/monitoring_task.ts | 6 ++---- x-pack/plugins/task_manager/server/index.ts | 1 + .../server/lib/delete_task_if_it_exists.test.ts | 0 .../server/lib/delete_task_if_it_exists.ts | 2 +- 5 files changed, 5 insertions(+), 7 deletions(-) rename x-pack/plugins/{alerts => task_manager}/server/lib/delete_task_if_it_exists.test.ts (100%) rename x-pack/plugins/{alerts => task_manager}/server/lib/delete_task_if_it_exists.ts (88%) diff --git a/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts b/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts index e21fee4ce3d61f..7d33a50c906a21 100644 --- a/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts +++ b/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts @@ -41,9 +41,8 @@ import { InvalidateAPIKeyResult as SecurityPluginInvalidateAPIKeyResult, } from '../../../security/server'; import { EncryptedSavedObjectsClient } from '../../../encrypted_saved_objects/server'; -import { TaskManagerStartContract } from '../../../task_manager/server'; +import { TaskManagerStartContract, deleteTaskIfItExists } from '../../../task_manager/server'; import { taskInstanceToAlertTaskInstance } from '../task_runner/alert_task_instance'; -import { deleteTaskIfItExists } from '../lib/delete_task_if_it_exists'; import { RegistryAlertType, UntypedNormalizedAlertType } from '../alert_type_registry'; import { AlertsAuthorization, WriteOperations, ReadOperations } from '../authorization'; import { IEventLogClient } from '../../../../plugins/event_log/server'; diff --git a/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts b/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts index f5e0218f9c0a29..e3c8d06c0c671b 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts @@ -8,6 +8,7 @@ import { TaskManagerSetupContract, TaskManagerStartContract, RunContext, + deleteTaskIfItExists, } from '../../../../task_manager/server'; import { checkRunningSessions } from './check_running_sessions'; import { CoreSetup, SavedObjectsClient, Logger } from '../../../../../../src/core/server'; @@ -56,10 +57,7 @@ export async function scheduleSearchSessionsTasks( taskManager: TaskManagerStartContract, logger: Logger ) { - try { - // delete previous task - await taskManager.remove(SEARCH_SESSIONS_TASK_ID); - } catch (e) {} // eslint-disable-line no-empty + await deleteTaskIfItExists(taskManager, SEARCH_SESSIONS_TASK_ID); try { await taskManager.ensureScheduled({ diff --git a/x-pack/plugins/task_manager/server/index.ts b/x-pack/plugins/task_manager/server/index.ts index 1696a3ec69c05e..5eb1f1e19f8772 100644 --- a/x-pack/plugins/task_manager/server/index.ts +++ b/x-pack/plugins/task_manager/server/index.ts @@ -20,6 +20,7 @@ export { } from './task'; export { isUnrecoverableError, throwUnrecoverableError } from './task_running'; +export { deleteTaskIfItExists } from './lib/delete_task_if_it_exists'; export { TaskManagerPlugin as TaskManager, diff --git a/x-pack/plugins/alerts/server/lib/delete_task_if_it_exists.test.ts b/x-pack/plugins/task_manager/server/lib/delete_task_if_it_exists.test.ts similarity index 100% rename from x-pack/plugins/alerts/server/lib/delete_task_if_it_exists.test.ts rename to x-pack/plugins/task_manager/server/lib/delete_task_if_it_exists.test.ts diff --git a/x-pack/plugins/alerts/server/lib/delete_task_if_it_exists.ts b/x-pack/plugins/task_manager/server/lib/delete_task_if_it_exists.ts similarity index 88% rename from x-pack/plugins/alerts/server/lib/delete_task_if_it_exists.ts rename to x-pack/plugins/task_manager/server/lib/delete_task_if_it_exists.ts index 53bb1b5cb5d53a..08d93f63c083a1 100644 --- a/x-pack/plugins/alerts/server/lib/delete_task_if_it_exists.ts +++ b/x-pack/plugins/task_manager/server/lib/delete_task_if_it_exists.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { TaskManagerStartContract } from '../../../task_manager/server'; +import { TaskManagerStartContract } from '../'; import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; export async function deleteTaskIfItExists(taskManager: TaskManagerStartContract, taskId: string) { From 19e423fe529d332dc39a4fa9778fdf4d90cf6685 Mon Sep 17 00:00:00 2001 From: restrry Date: Thu, 7 Jan 2021 15:54:50 +0100 Subject: [PATCH 51/58] task_manager to ts project --- x-pack/plugins/task_manager/tsconfig.json | 19 +++++++++++++++++++ x-pack/test/tsconfig.json | 1 + x-pack/tsconfig.json | 2 ++ x-pack/tsconfig.refs.json | 1 + 4 files changed, 23 insertions(+) create mode 100644 x-pack/plugins/task_manager/tsconfig.json diff --git a/x-pack/plugins/task_manager/tsconfig.json b/x-pack/plugins/task_manager/tsconfig.json new file mode 100644 index 00000000000000..a72b678da1f7c1 --- /dev/null +++ b/x-pack/plugins/task_manager/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "server/**/*", + // have to declare *.json explicitly due to https://github.com/microsoft/TypeScript/issues/25636 + "server/**/*.json", + ], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + { "path": "../../../src/plugins/kibana_utils/tsconfig.json" }, + ] +} diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index 5b05628d618a7d..b67171f50859a4 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -32,6 +32,7 @@ { "path": "../plugins/features/tsconfig.json" }, { "path": "../plugins/embeddable_enhanced/tsconfig.json" }, { "path": "../plugins/licensing/tsconfig.json" }, + { "path": "../plugins/task_manager/tsconfig.json" }, { "path": "../plugins/telemetry_collection_xpack/tsconfig.json" }, { "path": "../plugins/ui_actions_enhanced/tsconfig.json" }, ] diff --git a/x-pack/tsconfig.json b/x-pack/tsconfig.json index f6911d12031047..1182732e64673d 100644 --- a/x-pack/tsconfig.json +++ b/x-pack/tsconfig.json @@ -10,6 +10,7 @@ "plugins/embeddable_enhanced/**/*", "plugins/licensing/**/*", "plugins/security_solution/cypress/**/*", + "plugins/task_manager/**/*", "plugins/telemetry_collection_xpack/**/*", "plugins/ui_actions_enhanced/**/*", "test/**/*" @@ -48,6 +49,7 @@ { "path": "./plugins/features/tsconfig.json" }, { "path": "./plugins/embeddable_enhanced/tsconfig.json" }, { "path": "./plugins/licensing/tsconfig.json" }, + { "path": "./plugins/task_manager/tsconfig.json" }, { "path": "./plugins/telemetry_collection_xpack/tsconfig.json" }, { "path": "./plugins/ui_actions_enhanced/tsconfig.json" } ] diff --git a/x-pack/tsconfig.refs.json b/x-pack/tsconfig.refs.json index 0516f414963efd..d5012df00beb05 100644 --- a/x-pack/tsconfig.refs.json +++ b/x-pack/tsconfig.refs.json @@ -6,6 +6,7 @@ { "path": "./plugins/global_search/tsconfig.json" }, { "path": "./plugins/features/tsconfig.json" }, { "path": "./plugins/embeddable_enhanced/tsconfig.json" }, + { "path": "./plugins/task_manager/tsconfig.json" }, { "path": "./plugins/telemetry_collection_xpack/tsconfig.json" }, { "path": "./plugins/ui_actions_enhanced/tsconfig.json" }, ] From 47c3af2268775e11d836c7e28307b755f2b9ea82 Mon Sep 17 00:00:00 2001 From: Liza K Date: Thu, 7 Jan 2021 18:44:34 +0200 Subject: [PATCH 52/58] Move deleteTaskIfItExists to public contract --- .../plugins/alerts/server/alerts_client/alerts_client.ts | 6 +++--- .../data_enhanced/server/search/session/monitoring_task.ts | 3 +-- x-pack/plugins/task_manager/server/index.ts | 1 - .../server/lib/delete_task_if_it_exists.test.ts | 6 +++--- .../task_manager/server/lib/delete_task_if_it_exists.ts | 7 +++++-- x-pack/plugins/task_manager/server/plugin.ts | 6 +++++- 6 files changed, 17 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts b/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts index 7d33a50c906a21..dda0dec3e06b08 100644 --- a/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts +++ b/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts @@ -41,7 +41,7 @@ import { InvalidateAPIKeyResult as SecurityPluginInvalidateAPIKeyResult, } from '../../../security/server'; import { EncryptedSavedObjectsClient } from '../../../encrypted_saved_objects/server'; -import { TaskManagerStartContract, deleteTaskIfItExists } from '../../../task_manager/server'; +import { TaskManagerStartContract } from '../../../task_manager/server'; import { taskInstanceToAlertTaskInstance } from '../task_runner/alert_task_instance'; import { RegistryAlertType, UntypedNormalizedAlertType } from '../alert_type_registry'; import { AlertsAuthorization, WriteOperations, ReadOperations } from '../authorization'; @@ -601,7 +601,7 @@ export class AlertsClient { const removeResult = await this.unsecuredSavedObjectsClient.delete('alert', id); await Promise.all([ - taskIdToRemove ? deleteTaskIfItExists(this.taskManager, taskIdToRemove) : null, + taskIdToRemove ? this.taskManager.deleteTaskIfItExists(taskIdToRemove) : null, apiKeyToInvalidate ? markApiKeyForInvalidation( { apiKey: apiKeyToInvalidate }, @@ -1059,7 +1059,7 @@ export class AlertsClient { await Promise.all([ attributes.scheduledTaskId - ? deleteTaskIfItExists(this.taskManager, attributes.scheduledTaskId) + ? this.taskManager.deleteTaskIfItExists(attributes.scheduledTaskId) : null, apiKeyToInvalidate ? await markApiKeyForInvalidation( diff --git a/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts b/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts index e3c8d06c0c671b..79d9ee594d30fc 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts @@ -8,7 +8,6 @@ import { TaskManagerSetupContract, TaskManagerStartContract, RunContext, - deleteTaskIfItExists, } from '../../../../task_manager/server'; import { checkRunningSessions } from './check_running_sessions'; import { CoreSetup, SavedObjectsClient, Logger } from '../../../../../../src/core/server'; @@ -57,7 +56,7 @@ export async function scheduleSearchSessionsTasks( taskManager: TaskManagerStartContract, logger: Logger ) { - await deleteTaskIfItExists(taskManager, SEARCH_SESSIONS_TASK_ID); + await taskManager.deleteTaskIfItExists(SEARCH_SESSIONS_TASK_ID); try { await taskManager.ensureScheduled({ diff --git a/x-pack/plugins/task_manager/server/index.ts b/x-pack/plugins/task_manager/server/index.ts index 5eb1f1e19f8772..1696a3ec69c05e 100644 --- a/x-pack/plugins/task_manager/server/index.ts +++ b/x-pack/plugins/task_manager/server/index.ts @@ -20,7 +20,6 @@ export { } from './task'; export { isUnrecoverableError, throwUnrecoverableError } from './task_running'; -export { deleteTaskIfItExists } from './lib/delete_task_if_it_exists'; export { TaskManagerPlugin as TaskManager, diff --git a/x-pack/plugins/task_manager/server/lib/delete_task_if_it_exists.test.ts b/x-pack/plugins/task_manager/server/lib/delete_task_if_it_exists.test.ts index 84a1743387c9cd..2896ebcdf4b4ed 100644 --- a/x-pack/plugins/task_manager/server/lib/delete_task_if_it_exists.test.ts +++ b/x-pack/plugins/task_manager/server/lib/delete_task_if_it_exists.test.ts @@ -14,7 +14,7 @@ describe('deleteTaskIfItExists', () => { const tm = taskManagerMock.createStart(); const id = uuid.v4(); - expect(await deleteTaskIfItExists(tm, id)).toBe(undefined); + expect(await deleteTaskIfItExists(tm.remove, id)).toBe(undefined); expect(tm.remove).toHaveBeenCalledWith(id); }); @@ -25,7 +25,7 @@ describe('deleteTaskIfItExists', () => { tm.remove.mockRejectedValue(SavedObjectsErrorHelpers.createGenericNotFoundError('task', id)); - expect(await deleteTaskIfItExists(tm, id)).toBe(undefined); + expect(await deleteTaskIfItExists(tm.remove, id)).toBe(undefined); expect(tm.remove).toHaveBeenCalledWith(id); }); @@ -37,7 +37,7 @@ describe('deleteTaskIfItExists', () => { const error = SavedObjectsErrorHelpers.createInvalidVersionError(uuid.v4()); tm.remove.mockRejectedValue(error); - expect(deleteTaskIfItExists(tm, id)).rejects.toBe(error); + expect(deleteTaskIfItExists(tm.remove, id)).rejects.toBe(error); expect(tm.remove).toHaveBeenCalledWith(id); }); diff --git a/x-pack/plugins/task_manager/server/lib/delete_task_if_it_exists.ts b/x-pack/plugins/task_manager/server/lib/delete_task_if_it_exists.ts index 08d93f63c083a1..a84061b863e78b 100644 --- a/x-pack/plugins/task_manager/server/lib/delete_task_if_it_exists.ts +++ b/x-pack/plugins/task_manager/server/lib/delete_task_if_it_exists.ts @@ -6,9 +6,12 @@ import { TaskManagerStartContract } from '../'; import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; -export async function deleteTaskIfItExists(taskManager: TaskManagerStartContract, taskId: string) { +export async function deleteTaskIfItExists( + removeFn: TaskManagerStartContract['remove'], + taskId: string +) { try { - await taskManager.remove(taskId); + await removeFn(taskId); } catch (err) { if (!SavedObjectsErrorHelpers.isNotFoundError(err)) { throw err; diff --git a/x-pack/plugins/task_manager/server/plugin.ts b/x-pack/plugins/task_manager/server/plugin.ts index 70688cd169d7ea..ddabef39c5a8af 100644 --- a/x-pack/plugins/task_manager/server/plugin.ts +++ b/x-pack/plugins/task_manager/server/plugin.ts @@ -18,6 +18,7 @@ import { TaskDefinition } from './task'; import { TaskPollingLifecycle } from './polling_lifecycle'; import { TaskManagerConfig } from './config'; import { createInitialMiddleware, addMiddlewareToChain, Middleware } from './lib/middleware'; +import { deleteTaskIfItExists } from './lib/delete_task_if_it_exists'; import { setupSavedObjects } from './saved_objects'; import { TaskTypeDictionary } from './task_type_dictionary'; import { FetchResult, SearchOpts, TaskStore } from './task_store'; @@ -35,7 +36,9 @@ export type TaskManagerStartContract = Pick< TaskScheduling, 'schedule' | 'runNow' | 'ensureScheduled' > & - Pick; + Pick & { + deleteTaskIfItExists: (id: string) => void; + }; export class TaskManagerPlugin implements Plugin { @@ -156,6 +159,7 @@ export class TaskManagerPlugin fetch: (opts: SearchOpts): Promise => taskStore.fetch(opts), get: (id: string) => taskStore.get(id), remove: (id: string) => taskStore.remove(id), + deleteTaskIfItExists: (id: string) => deleteTaskIfItExists(taskStore.remove, id), schedule: (...args) => taskScheduling.schedule(...args), ensureScheduled: (...args) => taskScheduling.ensureScheduled(...args), runNow: (...args) => taskScheduling.runNow(...args), From 056a17b0dbc0c3e44498ca1b87c8643f7b97c90e Mon Sep 17 00:00:00 2001 From: Liza K Date: Thu, 7 Jan 2021 18:46:05 +0200 Subject: [PATCH 53/58] mock --- x-pack/plugins/task_manager/server/mocks.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/task_manager/server/mocks.ts b/x-pack/plugins/task_manager/server/mocks.ts index 4a78a0b49001b2..9aea1f9faa8768 100644 --- a/x-pack/plugins/task_manager/server/mocks.ts +++ b/x-pack/plugins/task_manager/server/mocks.ts @@ -22,6 +22,7 @@ const createStartMock = () => { schedule: jest.fn(), runNow: jest.fn(), ensureScheduled: jest.fn(), + deleteTaskIfItExists: jest.fn(), }; return mock; }; From 762bcc7fc420ab682db8b749e3a913d5c1ab3eb4 Mon Sep 17 00:00:00 2001 From: Liza K Date: Thu, 7 Jan 2021 18:55:29 +0200 Subject: [PATCH 54/58] use task store --- .../lib/delete_task_if_it_exists.test.ts | 24 +++++++++---------- .../server/lib/delete_task_if_it_exists.ts | 9 +++---- x-pack/plugins/task_manager/server/plugin.ts | 2 +- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/task_manager/server/lib/delete_task_if_it_exists.test.ts b/x-pack/plugins/task_manager/server/lib/delete_task_if_it_exists.test.ts index 2896ebcdf4b4ed..fbbf22a003ef6c 100644 --- a/x-pack/plugins/task_manager/server/lib/delete_task_if_it_exists.test.ts +++ b/x-pack/plugins/task_manager/server/lib/delete_task_if_it_exists.test.ts @@ -5,40 +5,40 @@ */ import uuid from 'uuid'; -import { taskManagerMock } from '../../../task_manager/server/mocks'; import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; import { deleteTaskIfItExists } from './delete_task_if_it_exists'; +import { taskStoreMock } from '../task_store.mock'; describe('deleteTaskIfItExists', () => { test('removes the task by its ID', async () => { - const tm = taskManagerMock.createStart(); + const ts = taskStoreMock.create({}); const id = uuid.v4(); - expect(await deleteTaskIfItExists(tm.remove, id)).toBe(undefined); + expect(await deleteTaskIfItExists(ts, id)).toBe(undefined); - expect(tm.remove).toHaveBeenCalledWith(id); + expect(ts.remove).toHaveBeenCalledWith(id); }); test('handles 404 errors caused by the task not existing', async () => { - const tm = taskManagerMock.createStart(); + const ts = taskStoreMock.create({}); const id = uuid.v4(); - tm.remove.mockRejectedValue(SavedObjectsErrorHelpers.createGenericNotFoundError('task', id)); + ts.remove.mockRejectedValue(SavedObjectsErrorHelpers.createGenericNotFoundError('task', id)); - expect(await deleteTaskIfItExists(tm.remove, id)).toBe(undefined); + expect(await deleteTaskIfItExists(ts, id)).toBe(undefined); - expect(tm.remove).toHaveBeenCalledWith(id); + expect(ts.remove).toHaveBeenCalledWith(id); }); test('throws if any other errro is caused by task removal', async () => { - const tm = taskManagerMock.createStart(); + const ts = taskStoreMock.create({}); const id = uuid.v4(); const error = SavedObjectsErrorHelpers.createInvalidVersionError(uuid.v4()); - tm.remove.mockRejectedValue(error); + ts.remove.mockRejectedValue(error); - expect(deleteTaskIfItExists(tm.remove, id)).rejects.toBe(error); + expect(deleteTaskIfItExists(ts, id)).rejects.toBe(error); - expect(tm.remove).toHaveBeenCalledWith(id); + expect(ts.remove).toHaveBeenCalledWith(id); }); }); diff --git a/x-pack/plugins/task_manager/server/lib/delete_task_if_it_exists.ts b/x-pack/plugins/task_manager/server/lib/delete_task_if_it_exists.ts index a84061b863e78b..65e6da6d6d4795 100644 --- a/x-pack/plugins/task_manager/server/lib/delete_task_if_it_exists.ts +++ b/x-pack/plugins/task_manager/server/lib/delete_task_if_it_exists.ts @@ -3,15 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { TaskManagerStartContract } from '../'; import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; +import { TaskStore } from '../task_store'; -export async function deleteTaskIfItExists( - removeFn: TaskManagerStartContract['remove'], - taskId: string -) { +export async function deleteTaskIfItExists(taskStore: TaskStore, taskId: string) { try { - await removeFn(taskId); + await taskStore.remove(taskId); } catch (err) { if (!SavedObjectsErrorHelpers.isNotFoundError(err)) { throw err; diff --git a/x-pack/plugins/task_manager/server/plugin.ts b/x-pack/plugins/task_manager/server/plugin.ts index ddabef39c5a8af..14fd7cc8f2a379 100644 --- a/x-pack/plugins/task_manager/server/plugin.ts +++ b/x-pack/plugins/task_manager/server/plugin.ts @@ -159,7 +159,7 @@ export class TaskManagerPlugin fetch: (opts: SearchOpts): Promise => taskStore.fetch(opts), get: (id: string) => taskStore.get(id), remove: (id: string) => taskStore.remove(id), - deleteTaskIfItExists: (id: string) => deleteTaskIfItExists(taskStore.remove, id), + deleteTaskIfItExists: (id: string) => deleteTaskIfItExists(taskStore, id), schedule: (...args) => taskScheduling.schedule(...args), ensureScheduled: (...args) => taskScheduling.ensureScheduled(...args), runNow: (...args) => taskScheduling.runNow(...args), From 740f7d99b9b4f7c7dfd7c48285edf4ef7f9c88ba Mon Sep 17 00:00:00 2001 From: Liza K Date: Thu, 7 Jan 2021 22:52:21 +0200 Subject: [PATCH 55/58] ts --- .../alerts/server/alerts_client/tests/disable.test.ts | 10 +++++----- x-pack/plugins/task_manager/server/plugin.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/disable.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/disable.test.ts index ce0688a5ab2ff4..b148341339f761 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/disable.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/disable.test.ts @@ -199,7 +199,7 @@ describe('disable()', () => { version: '123', } ); - expect(taskManager.remove).toHaveBeenCalledWith('task-123'); + expect(taskManager.deleteTaskIfItExists).toHaveBeenCalledWith('task-123'); expect( (unsecuredSavedObjectsClient.create.mock.calls[0][1] as InvalidatePendingApiKey).apiKeyId ).toBe('123'); @@ -254,7 +254,7 @@ describe('disable()', () => { version: '123', } ); - expect(taskManager.remove).toHaveBeenCalledWith('task-123'); + expect(taskManager.deleteTaskIfItExists).toHaveBeenCalledWith('task-123'); expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); }); @@ -280,7 +280,7 @@ describe('disable()', () => { await alertsClient.disable({ id: '1' }); expect(unsecuredSavedObjectsClient.update).not.toHaveBeenCalled(); - expect(taskManager.remove).not.toHaveBeenCalled(); + expect(taskManager.deleteTaskIfItExists).not.toHaveBeenCalled(); expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); }); @@ -314,7 +314,7 @@ describe('disable()', () => { await alertsClient.disable({ id: '1' }); expect(unsecuredSavedObjectsClient.update).toHaveBeenCalled(); - expect(taskManager.remove).toHaveBeenCalled(); + expect(taskManager.deleteTaskIfItExists).toHaveBeenCalled(); expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); expect(alertsClientParams.logger.error).toHaveBeenCalledWith( 'disable(): Failed to load API key to invalidate on alert 1: Fail' @@ -338,7 +338,7 @@ describe('disable()', () => { }); test('throws when failing to remove task from task manager', async () => { - taskManager.remove.mockRejectedValueOnce(new Error('Failed to remove task')); + taskManager.deleteTaskIfItExists.mockRejectedValueOnce(new Error('Failed to remove task')); await expect(alertsClient.disable({ id: '1' })).rejects.toThrowErrorMatchingInlineSnapshot( `"Failed to remove task"` diff --git a/x-pack/plugins/task_manager/server/plugin.ts b/x-pack/plugins/task_manager/server/plugin.ts index 14fd7cc8f2a379..e1c6ec96e4bed9 100644 --- a/x-pack/plugins/task_manager/server/plugin.ts +++ b/x-pack/plugins/task_manager/server/plugin.ts @@ -37,7 +37,7 @@ export type TaskManagerStartContract = Pick< 'schedule' | 'runNow' | 'ensureScheduled' > & Pick & { - deleteTaskIfItExists: (id: string) => void; + deleteTaskIfItExists: (id: string) => Promise; }; export class TaskManagerPlugin From 9b26ff50c936455650b04281d353f69a21ca7f9f Mon Sep 17 00:00:00 2001 From: Liza K Date: Thu, 7 Jan 2021 22:53:41 +0200 Subject: [PATCH 56/58] code review --- .../server/search/session/check_running_sessions.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts index 54591f0982e679..3a24cc40da7b7f 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts @@ -10,7 +10,11 @@ import { SavedObjectsFindResult, SavedObjectsClientContract, } from 'kibana/server'; -import { SearchSessionStatus, SearchSessionSavedObjectAttributes } from '../../../common'; +import { + SearchSessionStatus, + SearchSessionSavedObjectAttributes, + SearchSessionRequestInfo, +} from '../../../common'; import { SEARCH_SESSION_TYPE } from '../../saved_objects'; import { getSearchStatus } from './get_search_status'; import { getSessionStatus } from './get_session_status'; @@ -44,7 +48,9 @@ export async function checkRunningSessions( // Check statuses of all running searches await Promise.all( Object.keys(session.attributes.idMapping).map(async (searchKey: string) => { - const updateSearchRequest = (currentStatus: any) => { + const updateSearchRequest = ( + currentStatus: Pick + ) => { sessionUpdated = true; session.attributes.idMapping[searchKey] = { ...session.attributes.idMapping[searchKey], From 8415b05bf42bdd439031d02a0e80e58a783da39e Mon Sep 17 00:00:00 2001 From: Liza K Date: Mon, 11 Jan 2021 13:08:06 +0200 Subject: [PATCH 57/58] code review + jest --- .../alerts/server/alerts_client/tests/delete.test.ts | 10 +++++----- .../server/search/session/check_running_sessions.ts | 1 - .../server/search/session/get_search_status.ts | 2 ++ 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/delete.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/delete.test.ts index a7ef008eaa2ee7..cd412999a56022 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/delete.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/delete.test.ts @@ -110,7 +110,7 @@ describe('delete()', () => { const result = await alertsClient.delete({ id: '1' }); expect(result).toEqual({ success: true }); expect(unsecuredSavedObjectsClient.delete).toHaveBeenCalledWith('alert', '1'); - expect(taskManager.remove).toHaveBeenCalledWith('task-123'); + expect(taskManager.deleteTaskIfItExists).toHaveBeenCalledWith('task-123'); expect(unsecuredSavedObjectsClient.create.mock.calls[0][0]).toBe( 'api_key_pending_invalidation' ); @@ -135,7 +135,7 @@ describe('delete()', () => { const result = await alertsClient.delete({ id: '1' }); expect(result).toEqual({ success: true }); expect(unsecuredSavedObjectsClient.delete).toHaveBeenCalledWith('alert', '1'); - expect(taskManager.remove).toHaveBeenCalledWith('task-123'); + expect(taskManager.deleteTaskIfItExists).toHaveBeenCalledWith('task-123'); expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); expect(unsecuredSavedObjectsClient.get).toHaveBeenCalledWith('alert', '1'); expect(alertsClientParams.logger.error).toHaveBeenCalledWith( @@ -153,7 +153,7 @@ describe('delete()', () => { }); await alertsClient.delete({ id: '1' }); - expect(taskManager.remove).not.toHaveBeenCalled(); + expect(taskManager.deleteTaskIfItExists).not.toHaveBeenCalled(); }); test(`doesn't invalidate API key when apiKey is null`, async () => { @@ -217,8 +217,8 @@ describe('delete()', () => { ); }); - test('throws error when taskManager.remove throws an error', async () => { - taskManager.remove.mockRejectedValue(new Error('TM Fail')); + test('throws error when taskManager.deleteTaskIfItExists throws an error', async () => { + taskManager.deleteTaskIfItExists.mockRejectedValue(new Error('TM Fail')); await expect(alertsClient.delete({ id: '1' })).rejects.toThrowErrorMatchingInlineSnapshot( `"TM Fail"` diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts index 3a24cc40da7b7f..71274e15e284d7 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts @@ -55,7 +55,6 @@ export async function checkRunningSessions( session.attributes.idMapping[searchKey] = { ...session.attributes.idMapping[searchKey], ...currentStatus, - ...(currentStatus.status === SearchStatus.ERROR ? {} : { error: undefined }), }; }; diff --git a/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts b/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts index a8ff5c8fa70339..e2b5fc0157b378 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts @@ -31,10 +31,12 @@ export async function getSearchStatus( } else if (!response.is_partial && !response.is_running) { return { status: SearchStatus.COMPLETE, + error: undefined, }; } else { return { status: SearchStatus.IN_PROGRESS, + error: undefined, }; } } From 52a4ba16867c38bf521e586d0c2d648acf502440 Mon Sep 17 00:00:00 2001 From: Liza K Date: Mon, 11 Jan 2021 14:18:01 +0200 Subject: [PATCH 58/58] Alerting code review --- .../alerts/server/alerts_client/alerts_client.ts | 4 ++-- .../alerts/server/alerts_client/tests/delete.test.ts | 10 +++++----- .../alerts/server/alerts_client/tests/disable.test.ts | 10 +++++----- .../server/search/session/monitoring_task.ts | 2 +- ...k_if_it_exists.test.ts => remove_if_exists.test.ts} | 10 +++++----- ...delete_task_if_it_exists.ts => remove_if_exists.ts} | 9 ++++++++- x-pack/plugins/task_manager/server/mocks.ts | 2 +- x-pack/plugins/task_manager/server/plugin.ts | 6 +++--- 8 files changed, 30 insertions(+), 23 deletions(-) rename x-pack/plugins/task_manager/server/lib/{delete_task_if_it_exists.test.ts => remove_if_exists.test.ts} (80%) rename x-pack/plugins/task_manager/server/lib/{delete_task_if_it_exists.ts => remove_if_exists.ts} (70%) diff --git a/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts b/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts index dda0dec3e06b08..a47af44d330c3b 100644 --- a/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts +++ b/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts @@ -601,7 +601,7 @@ export class AlertsClient { const removeResult = await this.unsecuredSavedObjectsClient.delete('alert', id); await Promise.all([ - taskIdToRemove ? this.taskManager.deleteTaskIfItExists(taskIdToRemove) : null, + taskIdToRemove ? this.taskManager.removeIfExists(taskIdToRemove) : null, apiKeyToInvalidate ? markApiKeyForInvalidation( { apiKey: apiKeyToInvalidate }, @@ -1059,7 +1059,7 @@ export class AlertsClient { await Promise.all([ attributes.scheduledTaskId - ? this.taskManager.deleteTaskIfItExists(attributes.scheduledTaskId) + ? this.taskManager.removeIfExists(attributes.scheduledTaskId) : null, apiKeyToInvalidate ? await markApiKeyForInvalidation( diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/delete.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/delete.test.ts index cd412999a56022..8022bc26742aa3 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/delete.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/delete.test.ts @@ -110,7 +110,7 @@ describe('delete()', () => { const result = await alertsClient.delete({ id: '1' }); expect(result).toEqual({ success: true }); expect(unsecuredSavedObjectsClient.delete).toHaveBeenCalledWith('alert', '1'); - expect(taskManager.deleteTaskIfItExists).toHaveBeenCalledWith('task-123'); + expect(taskManager.removeIfExists).toHaveBeenCalledWith('task-123'); expect(unsecuredSavedObjectsClient.create.mock.calls[0][0]).toBe( 'api_key_pending_invalidation' ); @@ -135,7 +135,7 @@ describe('delete()', () => { const result = await alertsClient.delete({ id: '1' }); expect(result).toEqual({ success: true }); expect(unsecuredSavedObjectsClient.delete).toHaveBeenCalledWith('alert', '1'); - expect(taskManager.deleteTaskIfItExists).toHaveBeenCalledWith('task-123'); + expect(taskManager.removeIfExists).toHaveBeenCalledWith('task-123'); expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); expect(unsecuredSavedObjectsClient.get).toHaveBeenCalledWith('alert', '1'); expect(alertsClientParams.logger.error).toHaveBeenCalledWith( @@ -153,7 +153,7 @@ describe('delete()', () => { }); await alertsClient.delete({ id: '1' }); - expect(taskManager.deleteTaskIfItExists).not.toHaveBeenCalled(); + expect(taskManager.removeIfExists).not.toHaveBeenCalled(); }); test(`doesn't invalidate API key when apiKey is null`, async () => { @@ -217,8 +217,8 @@ describe('delete()', () => { ); }); - test('throws error when taskManager.deleteTaskIfItExists throws an error', async () => { - taskManager.deleteTaskIfItExists.mockRejectedValue(new Error('TM Fail')); + test('throws error when taskManager.removeIfExists throws an error', async () => { + taskManager.removeIfExists.mockRejectedValue(new Error('TM Fail')); await expect(alertsClient.delete({ id: '1' })).rejects.toThrowErrorMatchingInlineSnapshot( `"TM Fail"` diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/disable.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/disable.test.ts index b148341339f761..448546941185b0 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/disable.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/disable.test.ts @@ -199,7 +199,7 @@ describe('disable()', () => { version: '123', } ); - expect(taskManager.deleteTaskIfItExists).toHaveBeenCalledWith('task-123'); + expect(taskManager.removeIfExists).toHaveBeenCalledWith('task-123'); expect( (unsecuredSavedObjectsClient.create.mock.calls[0][1] as InvalidatePendingApiKey).apiKeyId ).toBe('123'); @@ -254,7 +254,7 @@ describe('disable()', () => { version: '123', } ); - expect(taskManager.deleteTaskIfItExists).toHaveBeenCalledWith('task-123'); + expect(taskManager.removeIfExists).toHaveBeenCalledWith('task-123'); expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); }); @@ -280,7 +280,7 @@ describe('disable()', () => { await alertsClient.disable({ id: '1' }); expect(unsecuredSavedObjectsClient.update).not.toHaveBeenCalled(); - expect(taskManager.deleteTaskIfItExists).not.toHaveBeenCalled(); + expect(taskManager.removeIfExists).not.toHaveBeenCalled(); expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); }); @@ -314,7 +314,7 @@ describe('disable()', () => { await alertsClient.disable({ id: '1' }); expect(unsecuredSavedObjectsClient.update).toHaveBeenCalled(); - expect(taskManager.deleteTaskIfItExists).toHaveBeenCalled(); + expect(taskManager.removeIfExists).toHaveBeenCalled(); expect(unsecuredSavedObjectsClient.create).not.toHaveBeenCalled(); expect(alertsClientParams.logger.error).toHaveBeenCalledWith( 'disable(): Failed to load API key to invalidate on alert 1: Fail' @@ -338,7 +338,7 @@ describe('disable()', () => { }); test('throws when failing to remove task from task manager', async () => { - taskManager.deleteTaskIfItExists.mockRejectedValueOnce(new Error('Failed to remove task')); + taskManager.removeIfExists.mockRejectedValueOnce(new Error('Failed to remove task')); await expect(alertsClient.disable({ id: '1' })).rejects.toThrowErrorMatchingInlineSnapshot( `"Failed to remove task"` diff --git a/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts b/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts index 79d9ee594d30fc..a7d57c94fa153a 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts @@ -56,7 +56,7 @@ export async function scheduleSearchSessionsTasks( taskManager: TaskManagerStartContract, logger: Logger ) { - await taskManager.deleteTaskIfItExists(SEARCH_SESSIONS_TASK_ID); + await taskManager.removeIfExists(SEARCH_SESSIONS_TASK_ID); try { await taskManager.ensureScheduled({ diff --git a/x-pack/plugins/task_manager/server/lib/delete_task_if_it_exists.test.ts b/x-pack/plugins/task_manager/server/lib/remove_if_exists.test.ts similarity index 80% rename from x-pack/plugins/task_manager/server/lib/delete_task_if_it_exists.test.ts rename to x-pack/plugins/task_manager/server/lib/remove_if_exists.test.ts index fbbf22a003ef6c..17ccb97c322f57 100644 --- a/x-pack/plugins/task_manager/server/lib/delete_task_if_it_exists.test.ts +++ b/x-pack/plugins/task_manager/server/lib/remove_if_exists.test.ts @@ -6,15 +6,15 @@ import uuid from 'uuid'; import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; -import { deleteTaskIfItExists } from './delete_task_if_it_exists'; +import { removeIfExists } from './remove_if_exists'; import { taskStoreMock } from '../task_store.mock'; -describe('deleteTaskIfItExists', () => { +describe('removeIfExists', () => { test('removes the task by its ID', async () => { const ts = taskStoreMock.create({}); const id = uuid.v4(); - expect(await deleteTaskIfItExists(ts, id)).toBe(undefined); + expect(await removeIfExists(ts, id)).toBe(undefined); expect(ts.remove).toHaveBeenCalledWith(id); }); @@ -25,7 +25,7 @@ describe('deleteTaskIfItExists', () => { ts.remove.mockRejectedValue(SavedObjectsErrorHelpers.createGenericNotFoundError('task', id)); - expect(await deleteTaskIfItExists(ts, id)).toBe(undefined); + expect(await removeIfExists(ts, id)).toBe(undefined); expect(ts.remove).toHaveBeenCalledWith(id); }); @@ -37,7 +37,7 @@ describe('deleteTaskIfItExists', () => { const error = SavedObjectsErrorHelpers.createInvalidVersionError(uuid.v4()); ts.remove.mockRejectedValue(error); - expect(deleteTaskIfItExists(ts, id)).rejects.toBe(error); + expect(removeIfExists(ts, id)).rejects.toBe(error); expect(ts.remove).toHaveBeenCalledWith(id); }); diff --git a/x-pack/plugins/task_manager/server/lib/delete_task_if_it_exists.ts b/x-pack/plugins/task_manager/server/lib/remove_if_exists.ts similarity index 70% rename from x-pack/plugins/task_manager/server/lib/delete_task_if_it_exists.ts rename to x-pack/plugins/task_manager/server/lib/remove_if_exists.ts index 65e6da6d6d4795..77ab20683c3c94 100644 --- a/x-pack/plugins/task_manager/server/lib/delete_task_if_it_exists.ts +++ b/x-pack/plugins/task_manager/server/lib/remove_if_exists.ts @@ -6,7 +6,14 @@ import { SavedObjectsErrorHelpers } from '../../../../../src/core/server'; import { TaskStore } from '../task_store'; -export async function deleteTaskIfItExists(taskStore: TaskStore, taskId: string) { +/** + * Removes a task from the store, ignoring a not found error + * Other errors are re-thrown + * + * @param taskStore + * @param taskId + */ +export async function removeIfExists(taskStore: TaskStore, taskId: string) { try { await taskStore.remove(taskId); } catch (err) { diff --git a/x-pack/plugins/task_manager/server/mocks.ts b/x-pack/plugins/task_manager/server/mocks.ts index 9aea1f9faa8768..45c077e64fff65 100644 --- a/x-pack/plugins/task_manager/server/mocks.ts +++ b/x-pack/plugins/task_manager/server/mocks.ts @@ -22,7 +22,7 @@ const createStartMock = () => { schedule: jest.fn(), runNow: jest.fn(), ensureScheduled: jest.fn(), - deleteTaskIfItExists: jest.fn(), + removeIfExists: jest.fn(), }; return mock; }; diff --git a/x-pack/plugins/task_manager/server/plugin.ts b/x-pack/plugins/task_manager/server/plugin.ts index e1c6ec96e4bed9..260d12565d4b41 100644 --- a/x-pack/plugins/task_manager/server/plugin.ts +++ b/x-pack/plugins/task_manager/server/plugin.ts @@ -18,7 +18,7 @@ import { TaskDefinition } from './task'; import { TaskPollingLifecycle } from './polling_lifecycle'; import { TaskManagerConfig } from './config'; import { createInitialMiddleware, addMiddlewareToChain, Middleware } from './lib/middleware'; -import { deleteTaskIfItExists } from './lib/delete_task_if_it_exists'; +import { removeIfExists } from './lib/remove_if_exists'; import { setupSavedObjects } from './saved_objects'; import { TaskTypeDictionary } from './task_type_dictionary'; import { FetchResult, SearchOpts, TaskStore } from './task_store'; @@ -37,7 +37,7 @@ export type TaskManagerStartContract = Pick< 'schedule' | 'runNow' | 'ensureScheduled' > & Pick & { - deleteTaskIfItExists: (id: string) => Promise; + removeIfExists: TaskStore['remove']; }; export class TaskManagerPlugin @@ -159,7 +159,7 @@ export class TaskManagerPlugin fetch: (opts: SearchOpts): Promise => taskStore.fetch(opts), get: (id: string) => taskStore.get(id), remove: (id: string) => taskStore.remove(id), - deleteTaskIfItExists: (id: string) => deleteTaskIfItExists(taskStore, id), + removeIfExists: (id: string) => removeIfExists(taskStore, id), schedule: (...args) => taskScheduling.schedule(...args), ensureScheduled: (...args) => taskScheduling.ensureScheduled(...args), runNow: (...args) => taskScheduling.runNow(...args),