From 26c47bdf5ec20b3de79cd96b9ef11267b54d7e04 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Thu, 15 Oct 2020 14:55:13 -0700 Subject: [PATCH 01/27] [Search] Add request context and asScoped pattern --- .../search_examples/server/my_strategy.ts | 10 +- .../server/routes/server_search_route.ts | 7 +- .../data/common/search/aggs/agg_config.ts | 3 +- .../data/common/search/es_search/types.ts | 11 -- .../search_source/search_source.test.ts | 4 +- .../search/search_source/search_source.ts | 21 ++-- src/plugins/data/common/search/types.ts | 28 +++-- src/plugins/data/public/index.ts | 1 - src/plugins/data/public/search/index.ts | 1 - .../data/public/search/search_interceptor.ts | 2 +- .../data/public/search/search_service.ts | 21 +--- src/plugins/data/server/index.ts | 1 + .../es_search/es_search_strategy.test.ts | 24 ++--- .../search/es_search/es_search_strategy.ts | 5 +- src/plugins/data/server/search/index.ts | 6 +- src/plugins/data/server/search/mocks.ts | 5 +- .../data/server/search/routes/search.test.ts | 53 ++++------ .../data/server/search/routes/search.ts | 21 +--- .../data/server/search/search_service.ts | 96 +++++++++-------- src/plugins/data/server/search/types.ts | 29 +++-- .../server/routes/validate_es.ts | 14 +-- .../abstract_search_strategy.test.js | 21 +--- .../strategies/abstract_search_strategy.ts | 8 +- .../server/search/eql_search_strategy.test.ts | 49 ++++----- .../server/search/eql_search_strategy.ts | 9 +- .../server/search/es_search_strategy.test.ts | 46 +++----- .../server/search/es_search_strategy.ts | 100 ++++++++---------- .../search_strategy/index_fields/index.ts | 7 +- .../security_solution/index.ts | 10 +- .../server/search_strategy/timeline/index.ts | 10 +- 30 files changed, 272 insertions(+), 351 deletions(-) diff --git a/examples/search_examples/server/my_strategy.ts b/examples/search_examples/server/my_strategy.ts index 26e7056cdd787..957216c7b9713 100644 --- a/examples/search_examples/server/my_strategy.ts +++ b/examples/search_examples/server/my_strategy.ts @@ -24,18 +24,18 @@ import { IMyStrategyResponse, IMyStrategyRequest } from '../common'; export const mySearchStrategyProvider = ( data: PluginStart ): ISearchStrategy => { - const es = data.search.getSearchStrategy('es'); + const es = data.search.getSearchStrategy(); return { - search: (request, options, context) => - es.search(request, options, context).pipe( + search: (deps, request, options) => + es.search(deps, request, options).pipe( map((esSearchRes) => ({ ...esSearchRes, cool: request.get_cool ? 'YES' : 'NOPE', })) ), - cancel: async (context, id) => { + cancel: async (deps, id) => { if (es.cancel) { - es.cancel(context, id); + es.cancel(deps, id); } }, }; diff --git a/examples/search_examples/server/routes/server_search_route.ts b/examples/search_examples/server/routes/server_search_route.ts index 21ae38b99f3d2..dae423aeecc8c 100644 --- a/examples/search_examples/server/routes/server_search_route.ts +++ b/examples/search_examples/server/routes/server_search_route.ts @@ -39,8 +39,8 @@ export function registerServerSearchRoute(router: IRouter, data: DataPluginStart // Run a synchronous search server side, by enforcing a high keepalive and waiting for completion. // If you wish to run the search with polling (in basic+), you'd have to poll on the search API. // Please reach out to the @app-arch-team if you need this to be implemented. - const res = await data.search - .search( + const res = await context + .search!.search( { params: { index, @@ -57,8 +57,7 @@ export function registerServerSearchRoute(router: IRouter, data: DataPluginStart keepAlive: '5m', }, } as IEsSearchRequest, - {}, - context + {} ) .toPromise(); diff --git a/src/plugins/data/common/search/aggs/agg_config.ts b/src/plugins/data/common/search/aggs/agg_config.ts index 910c79f5dd0d7..8ca27755e3dda 100644 --- a/src/plugins/data/common/search/aggs/agg_config.ts +++ b/src/plugins/data/common/search/aggs/agg_config.ts @@ -21,13 +21,12 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import { Assign, Ensure } from '@kbn/utility-types'; -import { ISearchSource } from 'src/plugins/data/public'; +import { ISearchOptions, ISearchSource } from 'src/plugins/data/public'; import { ExpressionAstFunction, ExpressionAstArgument, SerializedFieldFormat, } from 'src/plugins/expressions/common'; -import { ISearchOptions } from '../es_search'; import { IAggType } from './agg_type'; import { writeParams } from './agg_params'; diff --git a/src/plugins/data/common/search/es_search/types.ts b/src/plugins/data/common/search/es_search/types.ts index b1c3e5cdd3960..7dbbd01d2cdad 100644 --- a/src/plugins/data/common/search/es_search/types.ts +++ b/src/plugins/data/common/search/es_search/types.ts @@ -22,17 +22,6 @@ import { IKibanaSearchRequest, IKibanaSearchResponse } from '../types'; export const ES_SEARCH_STRATEGY = 'es'; -export interface ISearchOptions { - /** - * An `AbortSignal` that allows the caller of `search` to abort a search request. - */ - abortSignal?: AbortSignal; - /** - * Use this option to force using a specific server side search strategy. Leave empty to use the default strategy. - */ - strategy?: string; -} - export type ISearchRequestParams> = { trackTotalHits?: boolean; } & Search; diff --git a/src/plugins/data/common/search/search_source/search_source.test.ts b/src/plugins/data/common/search/search_source/search_source.test.ts index 00e06663e998e..98d66310c040e 100644 --- a/src/plugins/data/common/search/search_source/search_source.test.ts +++ b/src/plugins/data/common/search/search_source/search_source.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, of } from 'rxjs'; import { IndexPattern } from '../../index_patterns'; import { GetConfigFn } from '../../types'; import { fetchSoon } from './legacy'; @@ -53,7 +53,7 @@ describe('SearchSource', () => { let searchSourceDependencies: SearchSourceDependencies; beforeEach(() => { - mockSearchMethod = jest.fn().mockResolvedValue({ rawResponse: '' }); + mockSearchMethod = jest.fn().mockReturnValue(of({ rawResponse: '' })); searchSourceDependencies = { getConfig: jest.fn(), diff --git a/src/plugins/data/common/search/search_source/search_source.ts b/src/plugins/data/common/search/search_source/search_source.ts index 1a6b770cf2ca8..418e316860eb9 100644 --- a/src/plugins/data/common/search/search_source/search_source.ts +++ b/src/plugins/data/common/search/search_source/search_source.ts @@ -75,8 +75,7 @@ import { normalizeSortRequest } from './normalize_sort_request'; import { filterDocvalueFields } from './filter_docvalue_fields'; import { fieldWildcardFilter } from '../../../../kibana_utils/common'; import { IIndexPattern } from '../../index_patterns'; -import { IEsSearchRequest, IEsSearchResponse, ISearchOptions } from '../..'; -import { IKibanaSearchRequest, IKibanaSearchResponse } from '../types'; +import { ISearchGeneric, ISearchOptions } from '../..'; import { ISearchSource, SearchSourceOptions, SearchSourceFields } from './types'; import { FetchHandlers, RequestFailure, getSearchParamsFromRequest, SearchRequest } from './fetch'; @@ -102,15 +101,7 @@ export const searchSourceRequiredUiSettings = [ ]; export interface SearchSourceDependencies extends FetchHandlers { - // Types are nearly identical to ISearchGeneric, except we are making - // search options required here and returning a promise instead of observable. - search: < - SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest, - SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse - >( - request: SearchStrategyRequest, - options: ISearchOptions - ) => Promise; + search: ISearchGeneric; } /** @public **/ @@ -144,7 +135,7 @@ export class SearchSource { } /** - * sets value to a single search source feild + * sets value to a single search source field * @param field: field name * @param value: value for the field */ @@ -319,9 +310,9 @@ export class SearchSource { getConfig, }); - return search({ params, indexType: searchRequest.indexType }, options).then(({ rawResponse }) => - onResponse(searchRequest, rawResponse) - ); + return search({ params, indexType: searchRequest.indexType }, options) + .toPromise() + .then(({ rawResponse }) => onResponse(searchRequest, rawResponse)); } /** diff --git a/src/plugins/data/common/search/types.ts b/src/plugins/data/common/search/types.ts index c3943af5c6ff7..fa93f64da2699 100644 --- a/src/plugins/data/common/search/types.ts +++ b/src/plugins/data/common/search/types.ts @@ -18,12 +18,7 @@ */ import { Observable } from 'rxjs'; -import { IEsSearchRequest, IEsSearchResponse, ISearchOptions } from '../../common/search'; - -export type ISearch = ( - request: IKibanaSearchRequest, - options?: ISearchOptions -) => Observable; +import { IEsSearchRequest, IEsSearchResponse } from './es_search'; export type ISearchGeneric = < SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest, @@ -33,6 +28,13 @@ export type ISearchGeneric = < options?: ISearchOptions ) => Observable; +export type ISearchCancelGeneric = (id: string, options?: ISearchOptions) => Promise; + +export interface ISearchClient { + search: ISearchGeneric; + cancel: ISearchCancelGeneric; +} + export interface IKibanaSearchResponse { /** * Some responses may contain a unique id to identify the request this response came from. @@ -61,6 +63,9 @@ export interface IKibanaSearchResponse { */ isPartial?: boolean; + /** + * The raw response returned by the internal search method (usually the raw ES response) + */ rawResponse: RawResponse; } @@ -72,3 +77,14 @@ export interface IKibanaSearchRequest { params?: Params; } + +export interface ISearchOptions { + /** + * An `AbortSignal` that allows the caller of `search` to abort a search request. + */ + abortSignal?: AbortSignal; + /** + * Use this option to force using a specific server side search strategy. Leave empty to use the default strategy. + */ + strategy?: string; +} diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index c041511745be2..7c5dabed8a0cc 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -358,7 +358,6 @@ export { IKibanaSearchRequest, IKibanaSearchResponse, injectSearchSourceReferences, - ISearch, ISearchSetup, ISearchStart, ISearchStartSearchSource, diff --git a/src/plugins/data/public/search/index.ts b/src/plugins/data/public/search/index.ts index 86804a819cb0e..8ab92aed18b0a 100644 --- a/src/plugins/data/public/search/index.ts +++ b/src/plugins/data/public/search/index.ts @@ -31,7 +31,6 @@ export { IKibanaSearchRequest, IKibanaSearchResponse, injectReferences as injectSearchSourceReferences, - ISearch, ISearchGeneric, ISearchSource, parseSearchSourceJSON, diff --git a/src/plugins/data/public/search/search_interceptor.ts b/src/plugins/data/public/search/search_interceptor.ts index 2e42635a7f811..2ebf98ad0fdbe 100644 --- a/src/plugins/data/public/search/search_interceptor.ts +++ b/src/plugins/data/public/search/search_interceptor.ts @@ -191,7 +191,7 @@ export class SearchInterceptor { * * @param request * @options - * @returns `Observalbe` emitting the search response or an error. + * @returns `Observable` emitting the search response or an error. */ public search( request: IKibanaSearchRequest, diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index 734e88e085661..c124527f3da2d 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -22,16 +22,7 @@ import { BehaviorSubject } from 'rxjs'; import { ISearchSetup, ISearchStart, SearchEnhancements } from './types'; import { handleResponse } from './fetch'; -import { - IEsSearchRequest, - IEsSearchResponse, - IKibanaSearchRequest, - IKibanaSearchResponse, - ISearchGeneric, - ISearchOptions, - SearchSourceService, - SearchSourceDependencies, -} from '../../common/search'; +import { ISearchGeneric, SearchSourceService, SearchSourceDependencies } from '../../common/search'; import { getCallMsearch } from './legacy'; import { AggsService, AggsStartDependencies } from './aggs'; import { IndexPatternsContract } from '../index_patterns/index_patterns'; @@ -120,15 +111,7 @@ export class SearchService implements Plugin { const searchSourceDependencies: SearchSourceDependencies = { getConfig: uiSettings.get.bind(uiSettings), - search: < - SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest, - SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse - >( - request: SearchStrategyRequest, - options: ISearchOptions - ) => { - return search(request, options).toPromise(); - }, + search, onResponse: handleResponse, legacy: { callMsearch: getCallMsearch({ http }), diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index 11dcbb01bf4a6..8c4fb6cb0a0bc 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -213,6 +213,7 @@ export { ISearchStrategy, ISearchSetup, ISearchStart, + SearchStrategyDependencies, toSnakeCase, getAsyncOptions, getDefaultSearchParams, diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.test.ts b/src/plugins/data/server/search/es_search/es_search_strategy.test.ts index 2dbcc3196aa75..ddac28d9d54f5 100644 --- a/src/plugins/data/server/search/es_search/es_search_strategy.test.ts +++ b/src/plugins/data/server/search/es_search/es_search_strategy.test.ts @@ -17,9 +17,9 @@ * under the License. */ -import { RequestHandlerContext } from '../../../../../core/server'; import { pluginInitializerContextConfigMock } from '../../../../../core/server/mocks'; import { esSearchStrategyProvider } from './es_search_strategy'; +import { SearchStrategyDependencies } from '../types'; describe('ES search strategy', () => { const mockLogger: any = { @@ -36,16 +36,12 @@ describe('ES search strategy', () => { }, }); - const mockContext = ({ - core: { - uiSettings: { - client: { - get: () => {}, - }, - }, - elasticsearch: { client: { asCurrentUser: { search: mockApiCaller } } }, + const mockDeps = ({ + uiSettingsClient: { + get: () => {}, }, - } as unknown) as RequestHandlerContext; + esClient: { asCurrentUser: { search: mockApiCaller } }, + } as unknown) as SearchStrategyDependencies; const mockConfig$ = pluginInitializerContextConfigMock({}).legacy.globalConfig$; @@ -63,7 +59,7 @@ describe('ES search strategy', () => { const params = { index: 'logstash-*' }; await esSearchStrategyProvider(mockConfig$, mockLogger) - .search({ params }, {}, mockContext) + .search(mockDeps, { params }, {}) .subscribe(() => { expect(mockApiCaller).toBeCalled(); expect(mockApiCaller.mock.calls[0][0]).toEqual({ @@ -79,7 +75,7 @@ describe('ES search strategy', () => { const params = { index: 'logstash-*', ignore_unavailable: false, timeout: '1000ms' }; await esSearchStrategyProvider(mockConfig$, mockLogger) - .search({ params }, {}, mockContext) + .search(mockDeps, { params }, {}) .subscribe(() => { expect(mockApiCaller).toBeCalled(); expect(mockApiCaller.mock.calls[0][0]).toEqual({ @@ -93,11 +89,11 @@ describe('ES search strategy', () => { it('has all response parameters', async (done) => await esSearchStrategyProvider(mockConfig$, mockLogger) .search( + mockDeps, { params: { index: 'logstash-*' }, }, - {}, - mockContext + {} ) .subscribe((data) => { expect(data.isRunning).toBe(false); diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.ts b/src/plugins/data/server/search/es_search/es_search_strategy.ts index 92cc941e14853..cb66cafb66144 100644 --- a/src/plugins/data/server/search/es_search/es_search_strategy.ts +++ b/src/plugins/data/server/search/es_search/es_search_strategy.ts @@ -38,12 +38,11 @@ export const esSearchStrategyProvider = ( usage?: SearchUsage ): ISearchStrategy => { return { - search: (request, options, context) => + search: ({ esClient, uiSettingsClient }, request, options) => from( new Promise(async (resolve, reject) => { logger.debug(`search ${request.params?.index}`); const config = await config$.pipe(first()).toPromise(); - const uiSettingsClient = await context.core.uiSettings.client; // Only default index pattern type is supported here. // See data_enhanced for other type support. @@ -64,7 +63,7 @@ export const esSearchStrategyProvider = ( try { const promise = shimAbortSignal( - context.core.elasticsearch.client.asCurrentUser.search(params), + esClient.asCurrentUser.search(params), options?.abortSignal ); const { body: rawResponse } = (await promise) as ApiResponse>; diff --git a/src/plugins/data/server/search/index.ts b/src/plugins/data/server/search/index.ts index b671ed806510b..1be641401b29c 100644 --- a/src/plugins/data/server/search/index.ts +++ b/src/plugins/data/server/search/index.ts @@ -17,12 +17,8 @@ * under the License. */ -export { ISearchStrategy, ISearchSetup, ISearchStart, SearchEnhancements } from './types'; - +export * from './types'; export * from './es_search'; - export { usageProvider, SearchUsage } from './collectors'; - export * from './aggs'; - export { shimHitsTotal } from './routes'; diff --git a/src/plugins/data/server/search/mocks.ts b/src/plugins/data/server/search/mocks.ts index 0d4ba0cba24a3..4914726c85ef8 100644 --- a/src/plugins/data/server/search/mocks.ts +++ b/src/plugins/data/server/search/mocks.ts @@ -33,7 +33,10 @@ export function createSearchStartMock(): jest.Mocked { return { aggs: searchAggsStartMock(), getSearchStrategy: jest.fn(), - search: jest.fn(), + asScoped: jest.fn().mockReturnValue({ + search: jest.fn(), + cancel: jest.fn(), + }), searchSource: searchSourceMock.createStartContract(), }; } diff --git a/src/plugins/data/server/search/routes/search.test.ts b/src/plugins/data/server/search/routes/search.test.ts index 834e5de5c3121..1ad4a4d9dae29 100644 --- a/src/plugins/data/server/search/routes/search.test.ts +++ b/src/plugins/data/server/search/routes/search.test.ts @@ -17,34 +17,18 @@ * under the License. */ -import { Observable, from } from 'rxjs'; - -import { - CoreSetup, - RequestHandlerContext, - SharedGlobalConfig, - StartServicesAccessor, -} from 'src/core/server'; -import { - coreMock, - httpServerMock, - pluginInitializerContextConfigMock, -} from '../../../../../../src/core/server/mocks'; +import { from } from 'rxjs'; + +import { CoreSetup, RequestHandlerContext } from 'src/core/server'; +import { coreMock, httpServerMock } from '../../../../../../src/core/server/mocks'; import { registerSearchRoute } from './search'; import { DataPluginStart } from '../../plugin'; -import { dataPluginMock } from '../../mocks'; describe('Search service', () => { - let mockDataStart: MockedKeys; let mockCoreSetup: MockedKeys>; - let getStartServices: jest.Mocked>; - let globalConfig$: Observable; beforeEach(() => { - mockDataStart = dataPluginMock.createStartContract(); - mockCoreSetup = coreMock.createSetup({ pluginStartContract: mockDataStart }); - getStartServices = mockCoreSetup.getStartServices; - globalConfig$ = pluginInitializerContextConfigMock({}).legacy.globalConfig$; + mockCoreSetup = coreMock.createSetup(); }); it('handler calls context.search.search with the given request and strategy', async () => { @@ -67,8 +51,12 @@ describe('Search service', () => { }, }; - mockDataStart.search.search.mockReturnValue(from(Promise.resolve(response))); - const mockContext = {}; + const mockContext = { + search: { + search: jest.fn().mockReturnValue(from(Promise.resolve(response))), + }, + }; + const mockBody = { id: undefined, params: {} }; const mockParams = { strategy: 'foo' }; const mockRequest = httpServerMock.createKibanaRequest({ @@ -77,14 +65,14 @@ describe('Search service', () => { }); const mockResponse = httpServerMock.createResponseFactory(); - registerSearchRoute(mockCoreSetup.http.createRouter(), { getStartServices, globalConfig$ }); + registerSearchRoute(mockCoreSetup.http.createRouter()); const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value; const handler = mockRouter.post.mock.calls[0][1]; await handler((mockContext as unknown) as RequestHandlerContext, mockRequest, mockResponse); - expect(mockDataStart.search.search).toBeCalled(); - expect(mockDataStart.search.search.mock.calls[0][0]).toStrictEqual(mockBody); + expect(mockContext.search.search).toBeCalled(); + expect(mockContext.search.search.mock.calls[0][0]).toStrictEqual(mockBody); expect(mockResponse.ok).toBeCalled(); expect(mockResponse.ok.mock.calls[0][0]).toEqual({ body: response, @@ -101,9 +89,12 @@ describe('Search service', () => { }) ); - mockDataStart.search.search.mockReturnValue(rejectedValue); + const mockContext = { + search: { + search: jest.fn().mockReturnValue(rejectedValue), + }, + }; - const mockContext = {}; const mockBody = { id: undefined, params: {} }; const mockParams = { strategy: 'foo' }; const mockRequest = httpServerMock.createKibanaRequest({ @@ -112,14 +103,14 @@ describe('Search service', () => { }); const mockResponse = httpServerMock.createResponseFactory(); - registerSearchRoute(mockCoreSetup.http.createRouter(), { getStartServices, globalConfig$ }); + registerSearchRoute(mockCoreSetup.http.createRouter()); const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value; const handler = mockRouter.post.mock.calls[0][1]; await handler((mockContext as unknown) as RequestHandlerContext, mockRequest, mockResponse); - expect(mockDataStart.search.search).toBeCalled(); - expect(mockDataStart.search.search.mock.calls[0][0]).toStrictEqual(mockBody); + expect(mockContext.search.search).toBeCalled(); + expect(mockContext.search.search.mock.calls[0][0]).toStrictEqual(mockBody); expect(mockResponse.customError).toBeCalled(); const error: any = mockResponse.customError.mock.calls[0][0]; expect(error.body.message).toBe('oh no'); diff --git a/src/plugins/data/server/search/routes/search.ts b/src/plugins/data/server/search/routes/search.ts index 1e8433d9685e3..9fb8b0d004f08 100644 --- a/src/plugins/data/server/search/routes/search.ts +++ b/src/plugins/data/server/search/routes/search.ts @@ -20,13 +20,9 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from 'src/core/server'; import { getRequestAbortedSignal } from '../../lib'; -import { SearchRouteDependencies } from '../search_service'; import { shimHitsTotal } from './shim_hits_total'; -export function registerSearchRoute( - router: IRouter, - { getStartServices }: SearchRouteDependencies -): void { +export function registerSearchRoute(router: IRouter): void { router.post( { path: '/internal/search/{strategy}/{id?}', @@ -46,17 +42,14 @@ export function registerSearchRoute( const { strategy, id } = request.params; const abortSignal = getRequestAbortedSignal(request.events.aborted$); - const [, , selfStart] = await getStartServices(); - try { - const response = await selfStart.search - .search( + const response = await context + .search!.search( { ...searchRequest, id }, { abortSignal, strategy, - }, - context + } ) .toPromise(); @@ -97,12 +90,8 @@ export function registerSearchRoute( async (context, request, res) => { const { strategy, id } = request.params; - const [, , selfStart] = await getStartServices(); - const searchStrategy = selfStart.search.getSearchStrategy(strategy); - if (!searchStrategy.cancel) return res.ok(); - try { - await searchStrategy.cancel(context, id); + await context.search!.cancel(id, { strategy }); return res.ok(); } catch (err) { return res.customError({ diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 0130d3aacc91f..c7ef611e90cb2 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -26,12 +26,17 @@ import { Logger, Plugin, PluginInitializerContext, - RequestHandlerContext, SharedGlobalConfig, StartServicesAccessor, } from 'src/core/server'; import { first } from 'rxjs/operators'; -import { ISearchSetup, ISearchStart, ISearchStrategy, SearchEnhancements } from './types'; +import { + ISearchSetup, + ISearchStart, + ISearchStrategy, + SearchEnhancements, + SearchStrategyDependencies, +} from './types'; import { AggsService, AggsSetupDependencies } from './aggs'; @@ -53,6 +58,7 @@ import { SearchSourceService, searchSourceRequiredUiSettings, ISearchOptions, + ISearchClient, } from '../../common/search'; import { getShardDelayBucketAgg, @@ -61,6 +67,12 @@ import { import { aggShardDelay } from '../../common/search/aggs/buckets/shard_delay_fn'; import { ConfigSchema } from '../../config'; +declare module 'src/core/server' { + interface RequestHandlerContext { + search?: ISearchClient; + } +} + type StrategyMap = Record>; /** @internal */ @@ -103,9 +115,17 @@ export class SearchService implements Plugin { getStartServices: core.getStartServices, globalConfig$: this.initializerContext.config.legacy.globalConfig$, }; - registerSearchRoute(router, routeDependencies); + registerSearchRoute(router); registerMsearchRoute(router, routeDependencies); + core.http.registerRouteHandlerContext('search', (context) => { + return this.getSearchClient({ + savedObjectsClient: context.core.savedObjects.client, + esClient: context.core.elasticsearch.client, + uiSettingsClient: context.core.uiSettings.client, + }); + }); + this.registerSearchStrategy( ES_SEARCH_STRATEGY, esSearchStrategyProvider( @@ -144,14 +164,25 @@ export class SearchService implements Plugin { usage, }; } + public start( { elasticsearch, savedObjects, uiSettings }: CoreStart, { fieldFormats, indexPatterns }: SearchServiceStartDependencies ): ISearchStart { + const getSearchClientAsScoped = (request: KibanaRequest) => { + const savedObjectsClient = savedObjects.getScopedClient(request); + const deps = { + savedObjectsClient, + esClient: elasticsearch.client.asScoped(request), + uiSettingsClient: uiSettings.asScopedToClient(savedObjectsClient), + }; + return this.getSearchClient(deps); + }; + return { aggs: this.aggsService.start({ fieldFormats, uiSettings }), getSearchStrategy: this.getSearchStrategy, - search: this.search.bind(this), + asScoped: getSearchClientAsScoped, searchSource: { asScoped: async (request: KibanaRequest) => { const esClient = elasticsearch.client.asScoped(request); @@ -169,39 +200,7 @@ export class SearchService implements Plugin { const searchSourceDependencies: SearchSourceDependencies = { getConfig: (key: string): T => uiSettingsCache[key], - search: < - SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest, - SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse - >( - searchStrategyRequest: SearchStrategyRequest, - options: ISearchOptions - ) => { - /** - * Unless we want all SearchSource users to provide both a KibanaRequest - * (needed for index patterns) AND the RequestHandlerContext (needed for - * low-level search), we need to fake the context as it can be derived - * from the request object anyway. This will pose problems for folks who - * are registering custom search strategies as they are only getting a - * subset of the entire context. Ideally low-level search should be - * refactored to only require the needed dependencies: esClient & uiSettings. - */ - const fakeRequestHandlerContext = { - core: { - elasticsearch: { - client: esClient, - }, - uiSettings: { - client: uiSettingsClient, - }, - }, - } as RequestHandlerContext; - - return this.search( - searchStrategyRequest, - options, - fakeRequestHandlerContext - ).toPromise(); - }, + search: getSearchClientAsScoped(request).search, // onResponse isn't used on the server, so we just return the original value onResponse: (req, res) => res, legacy: { @@ -239,22 +238,35 @@ export class SearchService implements Plugin { SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest, SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse >( + deps: SearchStrategyDependencies, searchRequest: SearchStrategyRequest, - options: ISearchOptions, - context: RequestHandlerContext + options?: ISearchOptions ) => { const strategy = this.getSearchStrategy( - options.strategy || this.defaultSearchStrategyName + options?.strategy ); - return strategy.search(searchRequest, options, context); + return strategy.search(deps, searchRequest, options); + }; + + private cancel = (deps: SearchStrategyDependencies, id: string, options?: ISearchOptions) => { + const strategy = this.getSearchStrategy(options?.strategy); + + return strategy.cancel ? strategy.cancel(deps, id) : Promise.resolve(); + }; + + private getSearchClient = (deps: SearchStrategyDependencies): ISearchClient => { + return { + search: (searchRequest, options?) => this.search(deps, searchRequest, options), + cancel: (id: string, options?: ISearchOptions) => this.cancel(deps, id, options), + }; }; private getSearchStrategy = < SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest, SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse >( - name: string + name: string = this.defaultSearchStrategyName ): ISearchStrategy => { this.logger.debug(`Get strategy ${name}`); const strategy = this.searchStrategies[name]; diff --git a/src/plugins/data/server/search/types.ts b/src/plugins/data/server/search/types.ts index 9ba06d88dc4b3..622d6856b20f6 100644 --- a/src/plugins/data/server/search/types.ts +++ b/src/plugins/data/server/search/types.ts @@ -18,12 +18,18 @@ */ import { Observable } from 'rxjs'; -import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; +import { + IScopedClusterClient, + IUiSettingsClient, + SavedObjectsClientContract, + KibanaRequest, +} from 'src/core/server'; import { ISearchOptions, ISearchStartSearchSource, IKibanaSearchRequest, IKibanaSearchResponse, + ISearchClient, } from '../../common/search'; import { AggsSetup, AggsStart } from './aggs'; import { SearchUsage } from './collectors'; @@ -33,6 +39,12 @@ export interface SearchEnhancements { defaultStrategy: string; } +export interface SearchStrategyDependencies { + savedObjectsClient: SavedObjectsClientContract; + esClient: IScopedClusterClient; + uiSettingsClient: IUiSettingsClient; +} + export interface ISearchSetup { aggs: AggsSetup; /** @@ -67,11 +79,11 @@ export interface ISearchStrategy< SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse > { search: ( + deps: SearchStrategyDependencies, request: SearchStrategyRequest, - options: ISearchOptions, - context: RequestHandlerContext + options?: ISearchOptions ) => Observable; - cancel?: (context: RequestHandlerContext, id: string) => Promise; + cancel?: (deps: SearchStrategyDependencies, id: string) => Promise; } export interface ISearchStart< @@ -80,13 +92,14 @@ export interface ISearchStart< > { aggs: AggsStart; /** - * Get other registered search strategies. For example, if a new strategy needs to use the - * already-registered ES search strategy, it can use this function to accomplish that. + * Get other registered search strategies by name (or, by default, the Elasticsearch strategy). + * For example, if a new strategy needs to use the already-registered ES search strategy, it can + * use this function to accomplish that. */ getSearchStrategy: ( - name: string + name?: string // Name of the search strategy (defaults to the Elasticsearch strategy) ) => ISearchStrategy; - search: ISearchStrategy['search']; + asScoped: (request: KibanaRequest) => ISearchClient; searchSource: { asScoped: (request: KibanaRequest) => Promise; }; diff --git a/src/plugins/vis_type_timelion/server/routes/validate_es.ts b/src/plugins/vis_type_timelion/server/routes/validate_es.ts index 242be515e52bc..73f169e4bd859 100644 --- a/src/plugins/vis_type_timelion/server/routes/validate_es.ts +++ b/src/plugins/vis_type_timelion/server/routes/validate_es.ts @@ -20,7 +20,6 @@ import _ from 'lodash'; import { IRouter, CoreSetup } from 'kibana/server'; import { ES_SEARCH_STRATEGY } from '../../../data/server'; -import { TimelionPluginStartDeps } from '../plugin'; export function validateEsRoute(router: IRouter, core: CoreSetup) { router.get( @@ -30,7 +29,6 @@ export function validateEsRoute(router: IRouter, core: CoreSetup) { }, async function (context, request, response) { const uiSettings = await context.core.uiSettings.client.getAll(); - const deps = (await core.getStartServices())[1] as TimelionPluginStartDeps; const timefield = uiSettings['timelion:es.timefield']; @@ -58,14 +56,10 @@ export function validateEsRoute(router: IRouter, core: CoreSetup) { let resp; try { resp = ( - await deps.data.search - .search( - body, - { - strategy: ES_SEARCH_STRATEGY, - }, - context - ) + await context + .search!.search(body, { + strategy: ES_SEARCH_STRATEGY, + }) .toPromise() ).rawResponse; } catch (errResp) { diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js index ceae784cf74a6..ece0ba4c2da2d 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js @@ -60,22 +60,8 @@ describe('AbstractSearchStrategy', () => { const responses = await abstractSearchStrategy.search( { - requestContext: {}, - framework: { - core: { - getStartServices: jest.fn().mockReturnValue( - Promise.resolve([ - {}, - { - data: { - search: { - search: searchFn, - }, - }, - }, - ]) - ), - }, + requestContext: { + search: { search: searchFn }, }, }, searches @@ -92,8 +78,7 @@ describe('AbstractSearchStrategy', () => { }, { strategy: 'es', - }, - {} + } ); }); }); diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts index 7b62ad310a354..c670825093608 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts @@ -56,12 +56,11 @@ export class AbstractSearchStrategy { } async search(req: ReqFacade, bodies: any[], options = {}) { - const [, deps] = await req.framework.core.getStartServices(); const requests: any[] = []; bodies.forEach((body) => { requests.push( - deps.data.search - .search( + req.requestContext + .search!.search( { params: { ...body, @@ -72,8 +71,7 @@ export class AbstractSearchStrategy { { ...options, strategy: this.searchStrategyName, - }, - req.requestContext + } ) .toPromise() ); diff --git a/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.test.ts b/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.test.ts index 5b634fe4cf26c..72705f52b2937 100644 --- a/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.test.ts @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Logger, RequestHandlerContext } from 'src/core/server'; +import { Logger } from 'src/core/server'; import { EqlSearchStrategyRequest } from '../../common/search/types'; import { eqlSearchStrategyProvider } from './eql_search_strategy'; +import { SearchStrategyDependencies } from '../../../../../src/plugins/data/server'; const getMockEqlResponse = () => ({ body: { @@ -46,32 +47,26 @@ describe('EQL search strategy', () => { describe('search()', () => { let mockEqlSearch: jest.Mock; let mockEqlGet: jest.Mock; - let mockContext: RequestHandlerContext; + let mockDeps: SearchStrategyDependencies; let params: Required['params']; let options: Required['options']; beforeEach(() => { mockEqlSearch = jest.fn().mockResolvedValueOnce(getMockEqlResponse()); mockEqlGet = jest.fn().mockResolvedValueOnce(getMockEqlResponse()); - mockContext = ({ - core: { - uiSettings: { - client: { - get: jest.fn(), - }, - }, - elasticsearch: { - client: { - asCurrentUser: { - eql: { - get: mockEqlGet, - search: mockEqlSearch, - }, - }, + mockDeps = ({ + uiSettingsClient: { + get: jest.fn(), + }, + esClient: { + asCurrentUser: { + eql: { + get: mockEqlGet, + search: mockEqlSearch, }, }, }, - } as unknown) as RequestHandlerContext; + } as unknown) as SearchStrategyDependencies; params = { index: 'logstash-*', body: { query: 'process where 1 == 1' }, @@ -82,7 +77,7 @@ describe('EQL search strategy', () => { describe('async functionality', () => { it('performs an eql client search with params when no ID is provided', async () => { const eqlSearch = await eqlSearchStrategyProvider(mockLogger); - await eqlSearch.search({ options, params }, {}, mockContext).toPromise(); + await eqlSearch.search(mockDeps, { options, params }, {}).toPromise(); const [[request, requestOptions]] = mockEqlSearch.mock.calls; expect(request.index).toEqual('logstash-*'); @@ -92,7 +87,7 @@ describe('EQL search strategy', () => { it('retrieves the current request if an id is provided', async () => { const eqlSearch = await eqlSearchStrategyProvider(mockLogger); - await eqlSearch.search({ id: 'my-search-id' }, {}, mockContext).toPromise(); + await eqlSearch.search(mockDeps, { id: 'my-search-id' }, {}).toPromise(); const [[requestParams]] = mockEqlGet.mock.calls; expect(mockEqlSearch).not.toHaveBeenCalled(); @@ -103,7 +98,7 @@ describe('EQL search strategy', () => { describe('arguments', () => { it('sends along async search options', async () => { const eqlSearch = await eqlSearchStrategyProvider(mockLogger); - await eqlSearch.search({ options, params }, {}, mockContext).toPromise(); + await eqlSearch.search(mockDeps, { options, params }, {}).toPromise(); const [[request]] = mockEqlSearch.mock.calls; expect(request).toEqual( @@ -116,7 +111,7 @@ describe('EQL search strategy', () => { it('sends along default search parameters', async () => { const eqlSearch = await eqlSearchStrategyProvider(mockLogger); - await eqlSearch.search({ options, params }, {}, mockContext).toPromise(); + await eqlSearch.search(mockDeps, { options, params }, {}).toPromise(); const [[request]] = mockEqlSearch.mock.calls; expect(request).toEqual( @@ -131,6 +126,7 @@ describe('EQL search strategy', () => { const eqlSearch = await eqlSearchStrategyProvider(mockLogger); await eqlSearch .search( + mockDeps, { options, params: { @@ -139,8 +135,7 @@ describe('EQL search strategy', () => { keep_on_completion: false, }, }, - {}, - mockContext + {} ) .toPromise(); const [[request]] = mockEqlSearch.mock.calls; @@ -158,12 +153,12 @@ describe('EQL search strategy', () => { const eqlSearch = await eqlSearchStrategyProvider(mockLogger); await eqlSearch .search( + mockDeps, { options: { ...options, maxRetries: 2, ignore: [300] }, params, }, - {}, - mockContext + {} ) .toPromise(); const [[, requestOptions]] = mockEqlSearch.mock.calls; @@ -179,7 +174,7 @@ describe('EQL search strategy', () => { it('passes transport options for an existing request', async () => { const eqlSearch = await eqlSearchStrategyProvider(mockLogger); await eqlSearch - .search({ id: 'my-search-id', options: { ignore: [400] } }, {}, mockContext) + .search(mockDeps, { id: 'my-search-id', options: { ignore: [400] } }, {}) .toPromise(); const [[, requestOptions]] = mockEqlGet.mock.calls; diff --git a/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.ts index a7ca999699e23..377b90c9cf7a9 100644 --- a/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.ts @@ -21,19 +21,18 @@ export const eqlSearchStrategyProvider = ( logger: Logger ): ISearchStrategy => { return { - cancel: async (context, id) => { + cancel: async ({ esClient }, id) => { logger.debug(`_eql/delete ${id}`); - await context.core.elasticsearch.client.asCurrentUser.eql.delete({ + await esClient.asCurrentUser.eql.delete({ id, }); }, - search: (request, options, context) => + search: ({ esClient, uiSettingsClient }, request, options) => from( new Promise(async (resolve) => { logger.debug(`_eql/search ${JSON.stringify(request.params) || request.id}`); let promise: TransportRequestPromise; - const eqlClient = context.core.elasticsearch.client.asCurrentUser.eql; - const uiSettingsClient = await context.core.uiSettings.client; + const eqlClient = esClient.asCurrentUser.eql; const asyncOptions = getAsyncOptions(); const searchOptions = toSnakeCase({ ...request.options }); diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts index bab304b6afc9f..4f6b490972f06 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RequestHandlerContext } from '../../../../../src/core/server'; import { enhancedEsSearchStrategyProvider } from './es_search_strategy'; import { BehaviorSubject } from 'rxjs'; +import { SearchStrategyDependencies } from '../../../../../src/plugins/data/server/search'; const mockAsyncResponse = { body: { @@ -40,26 +40,20 @@ describe('ES search strategy', () => { const mockLogger: any = { debug: () => {}, }; - const mockContext = { - core: { - uiSettings: { - client: { - get: jest.fn(), - }, - }, - elasticsearch: { - client: { - asCurrentUser: { - asyncSearch: { - get: mockGetCaller, - submit: mockSubmitCaller, - }, - transport: { request: mockApiCaller }, - }, + const mockDeps = ({ + uiSettingsClient: { + get: jest.fn(), + }, + esClient: { + asCurrentUser: { + asyncSearch: { + get: mockGetCaller, + submit: mockSubmitCaller, }, + transport: { request: mockApiCaller }, }, }, - }; + } as unknown) as SearchStrategyDependencies; const mockConfig$ = new BehaviorSubject({ elasticsearch: { shardTimeout: { @@ -86,9 +80,7 @@ describe('ES search strategy', () => { const params = { index: 'logstash-*', body: { query: {} } }; const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); - await esSearch - .search({ params }, {}, (mockContext as unknown) as RequestHandlerContext) - .toPromise(); + await esSearch.search(mockDeps, { params }, {}).toPromise(); expect(mockSubmitCaller).toBeCalled(); const request = mockSubmitCaller.mock.calls[0][0]; @@ -102,9 +94,7 @@ describe('ES search strategy', () => { const params = { index: 'logstash-*', body: { query: {} } }; const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); - await esSearch - .search({ id: 'foo', params }, {}, (mockContext as unknown) as RequestHandlerContext) - .toPromise(); + await esSearch.search(mockDeps, { id: 'foo', params }, {}).toPromise(); expect(mockGetCaller).toBeCalled(); const request = mockGetCaller.mock.calls[0][0]; @@ -121,12 +111,12 @@ describe('ES search strategy', () => { await esSearch .search( + mockDeps, { indexType: 'rollup', params, }, - {}, - (mockContext as unknown) as RequestHandlerContext + {} ) .toPromise(); @@ -142,9 +132,7 @@ describe('ES search strategy', () => { const params = { index: 'foo-*', body: {} }; const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); - await esSearch - .search({ params }, {}, (mockContext as unknown) as RequestHandlerContext) - .toPromise(); + await esSearch.search(mockDeps, { params }, {}).toPromise(); expect(mockSubmitCaller).toBeCalled(); const request = mockSubmitCaller.mock.calls[0][0]; diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts index 9b89fb9fab3cb..58de427a14b07 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts @@ -9,7 +9,7 @@ import { first } from 'rxjs/operators'; import { SearchResponse } from 'elasticsearch'; import { Observable } from 'rxjs'; import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport'; -import { SharedGlobalConfig, RequestHandlerContext, Logger } from '../../../../../src/core/server'; +import { SharedGlobalConfig, Logger } from '../../../../../src/core/server'; import { getTotalLoaded, ISearchStrategy, @@ -20,13 +20,14 @@ import { shimHitsTotal, getAsyncOptions, shimAbortSignal, + SearchStrategyDependencies, } from '../../../../../src/plugins/data/server'; import { IEnhancedEsSearchRequest } from '../../common'; import { ISearchOptions, IEsSearchResponse, isCompleteResponse, -} from '../../../../../src/plugins/data/common/search'; +} from '../../../../../src/plugins/data/common'; function isEnhancedEsSearchResponse(response: any): response is IEsSearchResponse { return response.hasOwnProperty('isPartial') && response.hasOwnProperty('isRunning'); @@ -36,55 +37,48 @@ export const enhancedEsSearchStrategyProvider = ( config$: Observable, logger: Logger, usage?: SearchUsage -): ISearchStrategy => { - const search = ( - request: IEnhancedEsSearchRequest, - options: ISearchOptions, - context: RequestHandlerContext - ) => - from( - new Promise(async (resolve, reject) => { - logger.debug(`search ${JSON.stringify(request.params) || request.id}`); - - const isAsync = request.indexType !== 'rollup'; - - try { - const response = isAsync - ? await asyncSearch(request, options, context) - : await rollupSearch(request, options, context); - - if ( - usage && - isAsync && - isEnhancedEsSearchResponse(response) && - isCompleteResponse(response) - ) { - usage.trackSuccess(response.rawResponse.took); +): ISearchStrategy => { + return { + search: (deps, request, options) => + from( + new Promise(async (resolve, reject) => { + logger.debug(`search ${JSON.stringify(request.params) || request.id}`); + + const isAsync = request.indexType !== 'rollup'; + + try { + const response = isAsync + ? await asyncSearch(deps, request, options) + : await rollupSearch(deps, request, options); + + if ( + usage && + isAsync && + isEnhancedEsSearchResponse(response) && + isCompleteResponse(response) + ) { + usage.trackSuccess(response.rawResponse.took); + } + + resolve(response); + } catch (e) { + if (usage) usage.trackError(); + reject(e); } - - resolve(response); - } catch (e) { - if (usage) usage.trackError(); - reject(e); - } - }) - ); - - const cancel = async (context: RequestHandlerContext, id: string) => { - logger.debug(`cancel ${id}`); - await context.core.elasticsearch.client.asCurrentUser.asyncSearch.delete({ - id, - }); + }) + ), + cancel: async ({ esClient }, id) => { + logger.debug(`cancel ${id}`); + await esClient.asCurrentUser.asyncSearch.delete({ id }); + }, }; async function asyncSearch( + { esClient, uiSettingsClient }: SearchStrategyDependencies, request: IEnhancedEsSearchRequest, - options: ISearchOptions, - context: RequestHandlerContext + options?: ISearchOptions ): Promise { let promise: TransportRequestPromise; - const esClient = context.core.elasticsearch.client.asCurrentUser; - const uiSettingsClient = await context.core.uiSettings.client; const asyncOptions = getAsyncOptions(); // If we have an ID, then just poll for that ID, otherwise send the entire request body @@ -96,9 +90,9 @@ export const enhancedEsSearchStrategyProvider = ( ...request.params, }); - promise = esClient.asyncSearch.submit(submitOptions); + promise = esClient.asCurrentUser.asyncSearch.submit(submitOptions); } else { - promise = esClient.asyncSearch.get({ + promise = esClient.asCurrentUser.asyncSearch.get({ id: request.id, ...toSnakeCase(asyncOptions), }); @@ -115,13 +109,11 @@ export const enhancedEsSearchStrategyProvider = ( }; } - const rollupSearch = async function ( + async function rollupSearch( + { esClient, uiSettingsClient }: SearchStrategyDependencies, request: IEnhancedEsSearchRequest, - options: ISearchOptions, - context: RequestHandlerContext + options?: ISearchOptions ): Promise { - const esClient = context.core.elasticsearch.client.asCurrentUser; - const uiSettingsClient = await context.core.uiSettings.client; const config = await config$.pipe(first()).toPromise(); const { body, index, ...params } = request.params!; const method = 'POST'; @@ -132,7 +124,7 @@ export const enhancedEsSearchStrategyProvider = ( ...params, }); - const promise = esClient.transport.request({ + const promise = esClient.asCurrentUser.transport.request({ method, path, body, @@ -146,7 +138,5 @@ export const enhancedEsSearchStrategyProvider = ( rawResponse: response, ...getTotalLoaded(response._shards), }; - }; - - return { search, cancel }; + } }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/index_fields/index.ts b/x-pack/plugins/security_solution/server/search_strategy/index_fields/index.ts index bc461f3885a70..a0955f5d44f2a 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/index_fields/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/index_fields/index.ts @@ -26,13 +26,10 @@ export const securitySolutionIndexFieldsProvider = (): ISearchStrategy< const beatFields: BeatFields = require('../../utils/beat_schema/fields').fieldsBeat; return { - search: (request, options, context) => + search: ({ esClient }, request, options) => from( new Promise(async (resolve) => { - const { elasticsearch } = context.core; - const indexPatternsFetcher = new IndexPatternsFetcher( - elasticsearch.legacy.client.callAsCurrentUser - ); + const indexPatternsFetcher = new IndexPatternsFetcher(esClient.asCurrentUser); const dedupeIndices = dedupeIndexName(request.indices); const responsesIndexFields = await Promise.all( diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts index 962865880df5f..2d6937f40b5d1 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts @@ -17,10 +17,10 @@ import { SecuritySolutionFactory } from './factory/types'; export const securitySolutionSearchStrategyProvider = ( data: PluginStart ): ISearchStrategy, StrategyResponseType> => { - const es = data.search.getSearchStrategy('es'); + const es = data.search.getSearchStrategy(); return { - search: (request, options, context) => { + search: (deps, request, options) => { if (request.factoryQueryType == null) { throw new Error('factoryQueryType is required'); } @@ -28,12 +28,12 @@ export const securitySolutionSearchStrategyProvider = queryFactory.parse(request, esSearchRes))); }, - cancel: async (context, id) => { + cancel: async (deps, id) => { if (es.cancel) { - es.cancel(context, id); + es.cancel(deps, id); } }, }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/index.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/index.ts index 165f0f586ebdb..39f65054d4f71 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/timeline/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/timeline/index.ts @@ -17,10 +17,10 @@ import { SecuritySolutionTimelineFactory } from './factory/types'; export const securitySolutionTimelineSearchStrategyProvider = ( data: PluginStart ): ISearchStrategy, TimelineStrategyResponseType> => { - const es = data.search.getSearchStrategy('es'); + const es = data.search.getSearchStrategy(); return { - search: (request, options, context) => { + search: (deps, request, options) => { if (request.factoryQueryType == null) { throw new Error('factoryQueryType is required'); } @@ -29,12 +29,12 @@ export const securitySolutionTimelineSearchStrategyProvider = queryFactory.parse(request, esSearchRes))); }, - cancel: async (context, id) => { + cancel: async (deps, id) => { if (es.cancel) { - es.cancel(context, id); + es.cancel(deps, id); } }, }; From 36b8fcbd1c49226ee099a899755a860a2d7ceb74 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Thu, 15 Oct 2020 15:11:48 -0700 Subject: [PATCH 02/27] Update docs --- ...ugins-data-public.ikibanasearchresponse.md | 2 +- ...ublic.ikibanasearchresponse.rawresponse.md | 2 + ...bana-plugin-plugins-data-public.isearch.md | 11 ---- .../kibana-plugin-plugins-data-public.md | 1 - ...ns-data-public.searchinterceptor.search.md | 2 +- ...plugin-plugins-data-public.searchsource.md | 2 +- ...gins-data-public.searchsource.serialize.md | 4 +- ...ugins-data-public.searchsource.setfield.md | 2 +- ...gins-data-server.isearchstart.asscoped.md} | 6 +- ...a-server.isearchstart.getsearchstrategy.md | 4 +- ...plugin-plugins-data-server.isearchstart.md | 4 +- ...gins-data-server.isearchstrategy.cancel.md | 2 +- ...gin-plugins-data-server.isearchstrategy.md | 4 +- ...gins-data-server.isearchstrategy.search.md | 2 +- .../kibana-plugin-plugins-data-server.md | 1 + ...plugin-plugins-data-server.plugin.start.md | 8 +-- ...ver.searchstrategydependencies.esclient.md | 11 ++++ ...-data-server.searchstrategydependencies.md | 20 +++++++ ...strategydependencies.savedobjectsclient.md | 11 ++++ ...chstrategydependencies.uisettingsclient.md | 11 ++++ src/plugins/data/public/public.api.md | 40 ++++++------- src/plugins/data/server/server.api.md | 59 ++++++++++++------- 22 files changed, 131 insertions(+), 78 deletions(-) delete mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearch.md rename docs/development/plugins/data/server/{kibana-plugin-plugins-data-server.isearchstart.search.md => kibana-plugin-plugins-data-server.isearchstart.asscoped.md} (54%) create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchstrategydependencies.esclient.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchstrategydependencies.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchstrategydependencies.savedobjectsclient.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchstrategydependencies.uisettingsclient.md diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchresponse.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchresponse.md index 159dc8f4ada18..1d3e0c08dfc18 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchresponse.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchresponse.md @@ -18,6 +18,6 @@ export interface IKibanaSearchResponse | [isPartial](./kibana-plugin-plugins-data-public.ikibanasearchresponse.ispartial.md) | boolean | Indicates whether the results returned are complete or partial | | [isRunning](./kibana-plugin-plugins-data-public.ikibanasearchresponse.isrunning.md) | boolean | Indicates whether search is still in flight | | [loaded](./kibana-plugin-plugins-data-public.ikibanasearchresponse.loaded.md) | number | If relevant to the search strategy, return a loaded number that represents how progress is indicated. | -| [rawResponse](./kibana-plugin-plugins-data-public.ikibanasearchresponse.rawresponse.md) | RawResponse | | +| [rawResponse](./kibana-plugin-plugins-data-public.ikibanasearchresponse.rawresponse.md) | RawResponse | The raw response returned by the internal search method (usually the raw ES response) | | [total](./kibana-plugin-plugins-data-public.ikibanasearchresponse.total.md) | number | If relevant to the search strategy, return a total number that represents how progress is indicated. | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchresponse.rawresponse.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchresponse.rawresponse.md index 865c7d795801b..5857911259e12 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchresponse.rawresponse.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ikibanasearchresponse.rawresponse.md @@ -4,6 +4,8 @@ ## IKibanaSearchResponse.rawResponse property +The raw response returned by the internal search method (usually the raw ES response) + Signature: ```typescript diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearch.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearch.md deleted file mode 100644 index 79f667a70571a..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearch.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISearch](./kibana-plugin-plugins-data-public.isearch.md) - -## ISearch type - -Signature: - -```typescript -export declare type ISearch = (request: IKibanaSearchRequest, options?: ISearchOptions) => Observable; -``` 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 6a3c437305cc8..cb0008e426edf 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 @@ -158,7 +158,6 @@ | [IndexPatternsContract](./kibana-plugin-plugins-data-public.indexpatternscontract.md) | | | [IndexPatternSelectProps](./kibana-plugin-plugins-data-public.indexpatternselectprops.md) | | | [InputTimeRange](./kibana-plugin-plugins-data-public.inputtimerange.md) | | -| [ISearch](./kibana-plugin-plugins-data-public.isearch.md) | | | [ISearchGeneric](./kibana-plugin-plugins-data-public.isearchgeneric.md) | | | [ISearchSource](./kibana-plugin-plugins-data-public.isearchsource.md) | search source interface | | [MatchAllFilter](./kibana-plugin-plugins-data-public.matchallfilter.md) | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.search.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.search.md index 672ff5065c456..61f8eeb973f4c 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.search.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchinterceptor.search.md @@ -23,5 +23,5 @@ search(request: IKibanaSearchRequest, options?: ISearchOptions): Observable` -`Observalbe` emitting the search response or an error. +`Observable` emitting the search response or an error. diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md index 87346f81b13e2..548fa66e6e518 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md @@ -42,7 +42,7 @@ export declare class SearchSource | [getSerializedFields()](./kibana-plugin-plugins-data-public.searchsource.getserializedfields.md) | | serializes search source fields (which can later be passed to [ISearchStartSearchSource](./kibana-plugin-plugins-data-public.isearchstartsearchsource.md)) | | [onRequestStart(handler)](./kibana-plugin-plugins-data-public.searchsource.onrequeststart.md) | | Add a handler that will be notified whenever requests start | | [serialize()](./kibana-plugin-plugins-data-public.searchsource.serialize.md) | | Serializes the instance to a JSON string and a set of referenced objects. Use this method to get a representation of the search source which can be stored in a saved object.The references returned by this function can be mixed with other references in the same object, however make sure there are no name-collisions. The references will be named kibanaSavedObjectMeta.searchSourceJSON.index and kibanaSavedObjectMeta.searchSourceJSON.filter[<number>].meta.index.Using createSearchSource, the instance can be re-created. | -| [setField(field, value)](./kibana-plugin-plugins-data-public.searchsource.setfield.md) | | sets value to a single search source feild | +| [setField(field, value)](./kibana-plugin-plugins-data-public.searchsource.setfield.md) | | sets value to a single search source field | | [setFields(newFields)](./kibana-plugin-plugins-data-public.searchsource.setfields.md) | | Internal, do not use. Overrides all search source fields with the new field array. | | [setParent(parent, options)](./kibana-plugin-plugins-data-public.searchsource.setparent.md) | | Set a searchSource that this source should inherit from | | [setPreferredSearchStrategyId(searchStrategyId)](./kibana-plugin-plugins-data-public.searchsource.setpreferredsearchstrategyid.md) | | internal, dont use | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.serialize.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.serialize.md index 496e1ae9677d8..3bc2a20541777 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.serialize.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.serialize.md @@ -15,13 +15,13 @@ Using `createSearchSource`, the instance can be re-created. ```typescript serialize(): { searchSourceJSON: string; - references: import("../../../../../core/types").SavedObjectReference[]; + references: import("src/core/server").SavedObjectReference[]; }; ``` Returns: `{ searchSourceJSON: string; - references: import("../../../../../core/types").SavedObjectReference[]; + references: import("src/core/server").SavedObjectReference[]; }` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.setfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.setfield.md index 22619940f1589..e96a35d8deee9 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.setfield.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.setfield.md @@ -4,7 +4,7 @@ ## SearchSource.setField() method -sets value to a single search source feild +sets value to a single search source field Signature: diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.search.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.asscoped.md similarity index 54% rename from docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.search.md rename to docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.asscoped.md index 98ea175aaaea7..f97cc22a53001 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.search.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.asscoped.md @@ -1,11 +1,11 @@ -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchStart](./kibana-plugin-plugins-data-server.isearchstart.md) > [search](./kibana-plugin-plugins-data-server.isearchstart.search.md) +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchStart](./kibana-plugin-plugins-data-server.isearchstart.md) > [asScoped](./kibana-plugin-plugins-data-server.isearchstart.asscoped.md) -## ISearchStart.search property +## ISearchStart.asScoped property Signature: ```typescript -search: ISearchStrategy['search']; +asScoped: (request: KibanaRequest) => ISearchClient; ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md index 398ea21641942..9820e281c3f93 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md @@ -4,10 +4,10 @@ ## ISearchStart.getSearchStrategy property -Get other registered search strategies. For example, if a new strategy needs to use the already-registered ES search strategy, it can use this function to accomplish that. +Get other registered search strategies by name (or, by default, the Elasticsearch strategy). For example, if a new strategy needs to use the already-registered ES search strategy, it can use this function to accomplish that. Signature: ```typescript -getSearchStrategy: (name: string) => ISearchStrategy; +getSearchStrategy: (name?: string) => ISearchStrategy; ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md index b99c5f0f10a9e..771b529f23824 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md @@ -15,7 +15,7 @@ export interface ISearchStartAggsStart | | -| [getSearchStrategy](./kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md) | (name: string) => ISearchStrategy<SearchStrategyRequest, SearchStrategyResponse> | Get other registered search strategies. For example, if a new strategy needs to use the already-registered ES search strategy, it can use this function to accomplish that. | -| [search](./kibana-plugin-plugins-data-server.isearchstart.search.md) | ISearchStrategy['search'] | | +| [asScoped](./kibana-plugin-plugins-data-server.isearchstart.asscoped.md) | (request: KibanaRequest) => ISearchClient | | +| [getSearchStrategy](./kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md) | (name?: string) => ISearchStrategy<SearchStrategyRequest, SearchStrategyResponse> | Get other registered search strategies by name (or, by default, the Elasticsearch strategy). For example, if a new strategy needs to use the already-registered ES search strategy, it can use this function to accomplish that. | | [searchSource](./kibana-plugin-plugins-data-server.isearchstart.searchsource.md) | {
asScoped: (request: KibanaRequest) => Promise<ISearchStartSearchSource>;
} | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.cancel.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.cancel.md index 34903697090ea..5267290b3b0ee 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.cancel.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.cancel.md @@ -7,5 +7,5 @@ Signature: ```typescript -cancel?: (context: RequestHandlerContext, id: string) => Promise; +cancel?: (deps: SearchStrategyDependencies, id: string) => Promise; ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.md index 6dd95da2be3c1..39cc51049175e 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.md @@ -16,6 +16,6 @@ export interface ISearchStrategy(context: RequestHandlerContext, id: string) => Promise<void> | | -| [search](./kibana-plugin-plugins-data-server.isearchstrategy.search.md) | (request: SearchStrategyRequest, options: ISearchOptions, context: RequestHandlerContext) => Observable<SearchStrategyResponse> | | +| [cancel](./kibana-plugin-plugins-data-server.isearchstrategy.cancel.md) | (deps: SearchStrategyDependencies, id: string) => Promise<void> | | +| [search](./kibana-plugin-plugins-data-server.isearchstrategy.search.md) | (deps: SearchStrategyDependencies, request: SearchStrategyRequest, options?: ISearchOptions) => Observable<SearchStrategyResponse> | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.search.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.search.md index 84b90ae23f916..d7911cbfdcbac 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.search.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.search.md @@ -7,5 +7,5 @@ Signature: ```typescript -search: (request: SearchStrategyRequest, options: ISearchOptions, context: RequestHandlerContext) => Observable; +search: (deps: SearchStrategyDependencies, request: SearchStrategyRequest, options?: ISearchOptions) => Observable; ``` 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 f1eecd6e49b02..fa3d35e8fe7e4 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 @@ -58,6 +58,7 @@ | [PluginSetup](./kibana-plugin-plugins-data-server.pluginsetup.md) | | | [PluginStart](./kibana-plugin-plugins-data-server.pluginstart.md) | | | [RefreshInterval](./kibana-plugin-plugins-data-server.refreshinterval.md) | | +| [SearchStrategyDependencies](./kibana-plugin-plugins-data-server.searchstrategydependencies.md) | | | [SearchUsage](./kibana-plugin-plugins-data-server.searchusage.md) | | | [TabbedAggColumn](./kibana-plugin-plugins-data-server.tabbedaggcolumn.md) | \* | | [TabbedTable](./kibana-plugin-plugins-data-server.tabbedtable.md) | \* | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md index 215eac9829451..a1e42c44fa861 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md @@ -9,10 +9,10 @@ ```typescript start(core: CoreStart): { fieldFormats: { - fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; + fieldFormatServiceFactory: (uiSettings: import("src/core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick) => Promise; }; search: ISearchStart>; }; @@ -28,10 +28,10 @@ start(core: CoreStart): { `{ fieldFormats: { - fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; + fieldFormatServiceFactory: (uiSettings: import("src/core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick) => Promise; }; search: ISearchStart>; }` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchstrategydependencies.esclient.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchstrategydependencies.esclient.md new file mode 100644 index 0000000000000..d205021e10954 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchstrategydependencies.esclient.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [SearchStrategyDependencies](./kibana-plugin-plugins-data-server.searchstrategydependencies.md) > [esClient](./kibana-plugin-plugins-data-server.searchstrategydependencies.esclient.md) + +## SearchStrategyDependencies.esClient property + +Signature: + +```typescript +esClient: IScopedClusterClient; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchstrategydependencies.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchstrategydependencies.md new file mode 100644 index 0000000000000..be95fb04a2c4f --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchstrategydependencies.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [SearchStrategyDependencies](./kibana-plugin-plugins-data-server.searchstrategydependencies.md) + +## SearchStrategyDependencies interface + +Signature: + +```typescript +export interface SearchStrategyDependencies +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [esClient](./kibana-plugin-plugins-data-server.searchstrategydependencies.esclient.md) | IScopedClusterClient | | +| [savedObjectsClient](./kibana-plugin-plugins-data-server.searchstrategydependencies.savedobjectsclient.md) | SavedObjectsClientContract | | +| [uiSettingsClient](./kibana-plugin-plugins-data-server.searchstrategydependencies.uisettingsclient.md) | IUiSettingsClient | | + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchstrategydependencies.savedobjectsclient.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchstrategydependencies.savedobjectsclient.md new file mode 100644 index 0000000000000..f159a863312a4 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchstrategydependencies.savedobjectsclient.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [SearchStrategyDependencies](./kibana-plugin-plugins-data-server.searchstrategydependencies.md) > [savedObjectsClient](./kibana-plugin-plugins-data-server.searchstrategydependencies.savedobjectsclient.md) + +## SearchStrategyDependencies.savedObjectsClient property + +Signature: + +```typescript +savedObjectsClient: SavedObjectsClientContract; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchstrategydependencies.uisettingsclient.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchstrategydependencies.uisettingsclient.md new file mode 100644 index 0000000000000..38a33e41c396f --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchstrategydependencies.uisettingsclient.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [SearchStrategyDependencies](./kibana-plugin-plugins-data-server.searchstrategydependencies.md) > [uiSettingsClient](./kibana-plugin-plugins-data-server.searchstrategydependencies.uisettingsclient.md) + +## SearchStrategyDependencies.uiSettingsClient property + +Signature: + +```typescript +uiSettingsClient: IUiSettingsClient; +``` diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 2ed3e440040de..230b0f0bc0ed9 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -136,7 +136,7 @@ export class AggConfig { // (undocumented) makeLabel(percentageMode?: boolean): any; static nextId(list: IAggConfig[]): number; - onSearchRequestStart(searchSource: ISearchSource_2, options?: ISearchOptions): Promise | Promise; + onSearchRequestStart(searchSource: ISearchSource_2, options?: ISearchOptions_2): Promise | Promise; // (undocumented) params: any; // Warning: (ae-incompatible-release-tags) The symbol "parent" is marked as @public, but its signature references "IAggConfigs" which is marked as @internal @@ -1044,7 +1044,6 @@ export interface IKibanaSearchResponse { isPartial?: boolean; isRunning?: boolean; loaded?: number; - // (undocumented) rawResponse: RawResponse; total?: number; } @@ -1374,11 +1373,6 @@ export type InputTimeRange = TimeRange | { // @public (undocumented) export const isCompleteResponse: (response?: IKibanaSearchResponse | undefined) => boolean | undefined; -// Warning: (ae-missing-release-tag) "ISearch" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export type ISearch = (request: IKibanaSearchRequest, options?: ISearchOptions) => Observable; - // Warning: (ae-missing-release-tag) "ISearchGeneric" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -2066,7 +2060,7 @@ export class SearchSource { onRequestStart(handler: (searchSource: SearchSource, options?: ISearchOptions) => Promise): void; serialize(): { searchSourceJSON: string; - references: import("../../../../../core/types").SavedObjectReference[]; + references: import("src/core/server").SavedObjectReference[]; }; setField(field: K, value: SearchSourceFields[K]): this; setFields(newFields: SearchSourceFields): this; @@ -2300,21 +2294,21 @@ export const UI_SETTINGS: { // src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:388:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:388:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:388:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:388:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:390:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:391:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:402:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:403:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:407:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:408:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:411:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:415:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:387:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:387:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:387:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:387:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:389:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:390:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:402:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:406:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:407:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:410:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:411:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:414: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 // (No @packageDocumentation comment for this package) diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 0828460830f2c..942d4df96083a 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -21,8 +21,10 @@ import { ErrorToastOptions } from 'src/core/public/notifications'; import { ExpressionAstFunction } from 'src/plugins/expressions/common'; import { ExpressionsServerSetup } from 'src/plugins/expressions/server'; import { ISavedObjectsRepository } from 'kibana/server'; +import { IScopedClusterClient } from 'src/core/server'; import { ISearchOptions as ISearchOptions_2 } from 'src/plugins/data/public'; import { ISearchSource } from 'src/plugins/data/public'; +import { IUiSettingsClient } from 'src/core/server'; import { KibanaRequest } from 'src/core/server'; import { LegacyAPICaller } from 'kibana/server'; import { Logger } from 'kibana/server'; @@ -38,7 +40,6 @@ import { Plugin as Plugin_3 } from 'kibana/server'; import { PluginInitializerContext as PluginInitializerContext_2 } from 'src/core/server'; import { RecursiveReadonly } from '@kbn/utility-types'; import { RequestAdapter } from 'src/plugins/inspector/common'; -import { RequestHandlerContext } from 'src/core/server'; import { RequestStatistics } from 'src/plugins/inspector/common'; import { SavedObject } from 'src/core/server'; import { SavedObjectsClientContract } from 'src/core/server'; @@ -366,7 +367,7 @@ export const getAsyncOptions: () => { // Warning: (ae-missing-release-tag) "getDefaultSearchParams" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export function getDefaultSearchParams(uiSettingsClient: IUiSettingsClient): Promise<{ +export function getDefaultSearchParams(uiSettingsClient: IUiSettingsClient_2): Promise<{ maxConcurrentShardRequests: number | undefined; ignoreThrottled: boolean; ignoreUnavailable: boolean; @@ -712,9 +713,11 @@ export interface ISearchStart ISearchStrategy; + // Warning: (ae-forgotten-export) The symbol "ISearchClient" needs to be exported by the entry point index.d.ts + // // (undocumented) - search: ISearchStrategy['search']; + asScoped: (request: KibanaRequest) => ISearchClient; + getSearchStrategy: (name?: string) => ISearchStrategy; // (undocumented) searchSource: { asScoped: (request: KibanaRequest) => Promise; @@ -726,9 +729,9 @@ export interface ISearchStart { // (undocumented) - cancel?: (context: RequestHandlerContext, id: string) => Promise; + cancel?: (deps: SearchStrategyDependencies, id: string) => Promise; // (undocumented) - search: (request: SearchStrategyRequest, options: ISearchOptions, context: RequestHandlerContext) => Observable; + search: (deps: SearchStrategyDependencies, request: SearchStrategyRequest, options?: ISearchOptions) => Observable; } // @public (undocumented) @@ -877,10 +880,10 @@ export class Plugin implements Plugin_2 Promise; + fieldFormatServiceFactory: (uiSettings: import("src/core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick) => Promise; }; search: ISearchStart>; }; @@ -978,6 +981,18 @@ export const search: { tabifyGetColumns: typeof tabifyGetColumns; }; +// Warning: (ae-missing-release-tag) "SearchStrategyDependencies" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface SearchStrategyDependencies { + // (undocumented) + esClient: IScopedClusterClient; + // (undocumented) + savedObjectsClient: SavedObjectsClientContract; + // (undocumented) + uiSettingsClient: IUiSettingsClient; +} + // Warning: (ae-missing-release-tag) "SearchUsage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1126,22 +1141,22 @@ export function usageProvider(core: CoreSetup_2): SearchUsage; // src/plugins/data/server/index.ts:101:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:127:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:127:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:228:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:228:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:228:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:228:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:230:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:231:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:240:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:241:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:242:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:246:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:247:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:251:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:254:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:229:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:229:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:229:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:229:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:231:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:232:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:241:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:242:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:243:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:247:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:248:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:252:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:255:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index_patterns/index_patterns_service.ts:50:14 - (ae-forgotten-export) The symbol "IndexPatternsService" needs to be exported by the entry point index.d.ts // src/plugins/data/server/plugin.ts:88:66 - (ae-forgotten-export) The symbol "DataEnhancements" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/search/types.ts:91: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:104:5 - (ae-forgotten-export) The symbol "ISearchStartSearchSource" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) From c7296f0b667f8d25ae961d2d255f1189ca011cf3 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Mon, 19 Oct 2020 14:27:20 -0700 Subject: [PATCH 03/27] Unify interface for getting search client --- .../data/server/search/search_service.ts | 49 +++++++++---------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index c7ef611e90cb2..2b4eee1db18e5 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -118,12 +118,9 @@ export class SearchService implements Plugin { registerSearchRoute(router); registerMsearchRoute(router, routeDependencies); - core.http.registerRouteHandlerContext('search', (context) => { - return this.getSearchClient({ - savedObjectsClient: context.core.savedObjects.client, - esClient: context.core.elasticsearch.client, - uiSettingsClient: context.core.uiSettings.client, - }); + core.http.registerRouteHandlerContext('search', async (context, request) => { + const [coreStart] = await core.getStartServices(); + return this.asScopedProvider(coreStart)(request); }); this.registerSearchStrategy( @@ -166,23 +163,15 @@ export class SearchService implements Plugin { } public start( - { elasticsearch, savedObjects, uiSettings }: CoreStart, + core: CoreStart, { fieldFormats, indexPatterns }: SearchServiceStartDependencies ): ISearchStart { - const getSearchClientAsScoped = (request: KibanaRequest) => { - const savedObjectsClient = savedObjects.getScopedClient(request); - const deps = { - savedObjectsClient, - esClient: elasticsearch.client.asScoped(request), - uiSettingsClient: uiSettings.asScopedToClient(savedObjectsClient), - }; - return this.getSearchClient(deps); - }; - + const { elasticsearch, savedObjects, uiSettings } = core; + const asScoped = this.asScopedProvider(core); return { aggs: this.aggsService.start({ fieldFormats, uiSettings }), getSearchStrategy: this.getSearchStrategy, - asScoped: getSearchClientAsScoped, + asScoped, searchSource: { asScoped: async (request: KibanaRequest) => { const esClient = elasticsearch.client.asScoped(request); @@ -200,7 +189,7 @@ export class SearchService implements Plugin { const searchSourceDependencies: SearchSourceDependencies = { getConfig: (key: string): T => uiSettingsCache[key], - search: getSearchClientAsScoped(request).search, + search: asScoped(request).search, // onResponse isn't used on the server, so we just return the original value onResponse: (req, res) => res, legacy: { @@ -255,13 +244,6 @@ export class SearchService implements Plugin { return strategy.cancel ? strategy.cancel(deps, id) : Promise.resolve(); }; - private getSearchClient = (deps: SearchStrategyDependencies): ISearchClient => { - return { - search: (searchRequest, options?) => this.search(deps, searchRequest, options), - cancel: (id: string, options?: ISearchOptions) => this.cancel(deps, id, options), - }; - }; - private getSearchStrategy = < SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest, SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse @@ -275,4 +257,19 @@ export class SearchService implements Plugin { } return strategy; }; + + private asScopedProvider = ({ elasticsearch, savedObjects, uiSettings }: CoreStart) => { + return (request: KibanaRequest): ISearchClient => { + const savedObjectsClient = savedObjects.getScopedClient(request); + const deps = { + savedObjectsClient, + esClient: elasticsearch.client.asScoped(request), + uiSettingsClient: uiSettings.asScopedToClient(savedObjectsClient), + }; + return { + search: (searchRequest, options) => this.search(deps, searchRequest, options), + cancel: (id, options) => this.cancel(deps, id, options), + }; + }; + }; } From d77c009baf7e56a8bd2eae7bdb02c9ff194c409e Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Mon, 19 Oct 2020 19:01:17 -0700 Subject: [PATCH 04/27] [WIP] [data.search] Server-side background session service --- .../data/common/search/session/index.ts | 1 + .../data/common/search/session/types.ts | 5 + .../data/common/search/session/utils.ts | 24 +++ src/plugins/data/common/search/types.ts | 5 + .../data/public/search/search_interceptor.ts | 18 ++- .../data/public/search/session_service.ts | 21 ++- .../saved_objects/background_session.ts | 44 +++++ .../data/server/saved_objects/index.ts | 5 + .../data/server/search/routes/search.ts | 12 +- .../data/server/search/routes/session.ts | 85 ++++++++++ .../data/server/search/search_service.ts | 35 +++- .../data/server/search/session/index.ts | 20 +++ .../server/search/session/session_service.ts | 152 ++++++++++++++++++ .../public/application/angular/discover.js | 5 + .../application/angular/discover_legacy.html | 1 + .../public/search/search_interceptor.ts | 9 +- .../server/search/es_search_strategy.ts | 1 + 17 files changed, 426 insertions(+), 17 deletions(-) create mode 100644 src/plugins/data/common/search/session/utils.ts create mode 100644 src/plugins/data/server/saved_objects/background_session.ts create mode 100644 src/plugins/data/server/search/routes/session.ts create mode 100644 src/plugins/data/server/search/session/index.ts create mode 100644 src/plugins/data/server/search/session/session_service.ts diff --git a/src/plugins/data/common/search/session/index.ts b/src/plugins/data/common/search/session/index.ts index d8f7b5091eb8f..8e8897c7d7517 100644 --- a/src/plugins/data/common/search/session/index.ts +++ b/src/plugins/data/common/search/session/index.ts @@ -18,3 +18,4 @@ */ export * from './types'; +export * from './utils'; diff --git a/src/plugins/data/common/search/session/types.ts b/src/plugins/data/common/search/session/types.ts index 80ab74f1aa14d..53b266ea04a04 100644 --- a/src/plugins/data/common/search/session/types.ts +++ b/src/plugins/data/common/search/session/types.ts @@ -38,4 +38,9 @@ export interface ISessionService { * Clears the active session. */ clear: () => void; + + /** + * Whether the active session is already saved (i.e. sent to background) + */ + isStored: () => boolean; } diff --git a/src/plugins/data/common/search/session/utils.ts b/src/plugins/data/common/search/session/utils.ts new file mode 100644 index 0000000000000..56927d09a64d8 --- /dev/null +++ b/src/plugins/data/common/search/session/utils.ts @@ -0,0 +1,24 @@ +/* + * 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'; + +export function createRequestHash(keys: Record) { + return createHash(`sha256`).update(JSON.stringify(keys)).digest('hex'); +} diff --git a/src/plugins/data/common/search/types.ts b/src/plugins/data/common/search/types.ts index 7451edf5e2fa3..9bcdb1861e64b 100644 --- a/src/plugins/data/common/search/types.ts +++ b/src/plugins/data/common/search/types.ts @@ -92,4 +92,9 @@ export interface ISearchOptions { * A session ID, grouping multiple search requests into a single session. */ sessionId?: string; + + /** + * Whether the session is already saved (i.e. sent to background) + */ + isStored?: boolean; } diff --git a/src/plugins/data/public/search/search_interceptor.ts b/src/plugins/data/public/search/search_interceptor.ts index 42ab2656ea064..99ebf425a65c4 100644 --- a/src/plugins/data/public/search/search_interceptor.ts +++ b/src/plugins/data/public/search/search_interceptor.ts @@ -113,18 +113,24 @@ export class SearchInterceptor { */ protected runSearch( request: IKibanaSearchRequest, - signal: AbortSignal, - strategy?: string + options?: ISearchOptions ): Observable { const { id, ...searchRequest } = request; - const path = trimEnd(`/internal/search/${strategy || ES_SEARCH_STRATEGY}/${id || ''}`, '/'); - const body = JSON.stringify(searchRequest); + const path = trimEnd( + `/internal/search/${options?.strategy ?? ES_SEARCH_STRATEGY}/${id ?? ''}`, + '/' + ); + const body = JSON.stringify({ + sessionId: options?.sessionId, + isStored: options?.isStored, + ...searchRequest, + }); return from( this.deps.http.fetch({ method: 'POST', path, body, - signal, + signal: options?.abortSignal, }) ); } @@ -220,7 +226,7 @@ export class SearchInterceptor { abortSignal: options?.abortSignal, }); this.pendingCount$.next(this.pendingCount$.getValue() + 1); - return this.runSearch(request, combinedSignal, options?.strategy).pipe( + return this.runSearch(request, { ...options, abortSignal: combinedSignal }).pipe( catchError((e: Error) => { return throwError(this.handleSearchError(e, request, timeoutSignal, options)); }), diff --git a/src/plugins/data/public/search/session_service.ts b/src/plugins/data/public/search/session_service.ts index 31524434af302..40c838fe852cf 100644 --- a/src/plugins/data/public/search/session_service.ts +++ b/src/plugins/data/public/search/session_service.ts @@ -19,7 +19,7 @@ import uuid from 'uuid'; import { Subject, Subscription } from 'rxjs'; -import { PluginInitializerContext, StartServicesAccessor } from 'kibana/public'; +import { HttpStart, PluginInitializerContext, StartServicesAccessor } from 'kibana/public'; import { ISessionService } from '../../common/search'; import { ConfigSchema } from '../../config'; @@ -28,6 +28,8 @@ export class SessionService implements ISessionService { private session$: Subject = new Subject(); private appChangeSubscription$?: Subscription; private curApp?: string; + private http: HttpStart; + private _isStored: boolean = false; constructor( initializerContext: PluginInitializerContext, @@ -37,6 +39,8 @@ export class SessionService implements ISessionService { Make sure that apps don't leave sessions open. */ getStartServices().then(([coreStart]) => { + this.http = coreStart.http; + this.appChangeSubscription$ = coreStart.application.currentAppId$.subscribe((appName) => { if (this.sessionId) { const message = `Application '${this.curApp}' had an open session while navigating`; @@ -67,14 +71,29 @@ export class SessionService implements ISessionService { return this.session$.asObservable(); } + public isStored() { + return this._isStored; + } + public start() { + this._isStored = false; this.sessionId = uuid.v4(); this.session$.next(this.sessionId); return this.sessionId; } public clear() { + this._isStored = false; this.sessionId = undefined; this.session$.next(this.sessionId); } + + public async save() { + await this.http.post(`/internal/session`, { + body: JSON.stringify({ + sessionId: this.sessionId, + }), + }); + this._isStored = true; + } } diff --git a/src/plugins/data/server/saved_objects/background_session.ts b/src/plugins/data/server/saved_objects/background_session.ts new file mode 100644 index 0000000000000..bb12525134111 --- /dev/null +++ b/src/plugins/data/server/saved_objects/background_session.ts @@ -0,0 +1,44 @@ +/* + * 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 { SavedObjectsType } from 'kibana/server'; + +export const BACKGROUND_SESSION_TYPE = 'background-session'; + +export const backgroundSessionMapping: SavedObjectsType = { + name: BACKGROUND_SESSION_TYPE, + namespaceType: 'single', + hidden: true, + mappings: { + properties: { + expires: { + type: 'date', + }, + idMapping: { + type: 'object', + enabled: false, + }, + }, + }, +}; + +export interface BackgroundSessionSavedObjectAttributes { + expires: string; + idMapping: Record; +} diff --git a/src/plugins/data/server/saved_objects/index.ts b/src/plugins/data/server/saved_objects/index.ts index 077f9380823d0..fd7942615b4c4 100644 --- a/src/plugins/data/server/saved_objects/index.ts +++ b/src/plugins/data/server/saved_objects/index.ts @@ -20,3 +20,8 @@ export { querySavedObjectType } from './query'; export { indexPatternSavedObjectType } from './index_patterns'; export { kqlTelemetry } from './kql_telemetry'; export { searchTelemetry } from './search_telemetry'; +export { + BACKGROUND_SESSION_TYPE, + backgroundSessionMapping, + BackgroundSessionSavedObjectAttributes, +} from './background_session'; diff --git a/src/plugins/data/server/search/routes/search.ts b/src/plugins/data/server/search/routes/search.ts index 9fb8b0d004f08..ec36527d628d3 100644 --- a/src/plugins/data/server/search/routes/search.ts +++ b/src/plugins/data/server/search/routes/search.ts @@ -34,11 +34,17 @@ export function registerSearchRoute(router: IRouter): void { query: schema.object({}, { unknowns: 'allow' }), - body: schema.object({}, { unknowns: 'allow' }), + body: schema.object( + { + sessionId: schema.maybe(schema.string()), + isStored: schema.maybe(schema.boolean()), + }, + { unknowns: 'allow' } + ), }, }, async (context, request, res) => { - const searchRequest = request.body; + const { sessionId, isStored, ...searchRequest } = request.body; const { strategy, id } = request.params; const abortSignal = getRequestAbortedSignal(request.events.aborted$); @@ -49,6 +55,8 @@ export function registerSearchRoute(router: IRouter): void { { abortSignal, strategy, + sessionId, + isStored, } ) .toPromise(); diff --git a/src/plugins/data/server/search/routes/session.ts b/src/plugins/data/server/search/routes/session.ts new file mode 100644 index 0000000000000..ef0da7d32fcb5 --- /dev/null +++ b/src/plugins/data/server/search/routes/session.ts @@ -0,0 +1,85 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import { IRouter } from 'src/core/server'; + +export function registerSessionRoutes(router: IRouter): void { + router.post( + { + path: '/internal/session', + validate: { + body: schema.object({ + sessionId: schema.string(), + }), + }, + }, + async (context, request, res) => { + const { sessionId } = request.body; + try { + const response = await context.search!.session.save(sessionId); + + return res.ok({ + body: response, + }); + } catch (err) { + return res.customError({ + statusCode: err.statusCode || 500, + body: { + message: err.message, + attributes: { + error: err.body?.error || err.message, + }, + }, + }); + } + } + ); + + router.get( + { + path: '/internal/session/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + async (context, request, res) => { + const { id } = request.params; + try { + const response = await context.search!.session.get(id); + + return res.ok({ + body: response, + }); + } catch (err) { + return res.customError({ + statusCode: err.statusCode || 500, + body: { + message: err.message, + attributes: { + error: err.body?.error || err.message, + }, + }, + }); + } + } + ); +} diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 2b4eee1db18e5..efbf18d2b7c5f 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -29,7 +29,7 @@ import { SharedGlobalConfig, StartServicesAccessor, } from 'src/core/server'; -import { first } from 'rxjs/operators'; +import { first, tap } from 'rxjs/operators'; import { ISearchSetup, ISearchStart, @@ -48,7 +48,7 @@ import { DataPluginStart } from '../plugin'; import { UsageCollectionSetup } from '../../../usage_collection/server'; import { registerUsageCollector } from './collectors/register'; import { usageProvider } from './collectors/usage'; -import { searchTelemetry } from '../saved_objects'; +import { BACKGROUND_SESSION_TYPE, searchTelemetry } from '../saved_objects'; import { IKibanaSearchRequest, IKibanaSearchResponse, @@ -66,6 +66,9 @@ import { } from '../../common/search/aggs/buckets/shard_delay'; 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'; declare module 'src/core/server' { interface RequestHandlerContext { @@ -98,6 +101,7 @@ export class SearchService implements Plugin { private readonly searchSourceService = new SearchSourceService(); private defaultSearchStrategyName: string = ES_SEARCH_STRATEGY; private searchStrategies: StrategyMap = {}; + private sessionService: BackgroundSessionService = new BackgroundSessionService(); constructor( private initializerContext: PluginInitializerContext, @@ -117,12 +121,17 @@ export class SearchService implements Plugin { }; registerSearchRoute(router); registerMsearchRoute(router, routeDependencies); + registerSessionRoutes(router, routeDependencies); core.http.registerRouteHandlerContext('search', async (context, request) => { const [coreStart] = await core.getStartServices(); - return this.asScopedProvider(coreStart)(request); + const search = this.asScopedProvider(coreStart)(request); + const session = this.sessionService.asScopedProvider(coreStart)(request); + return { ...search, session }; }); + core.savedObjects.registerType(backgroundSessionMapping); + this.registerSearchStrategy( ES_SEARCH_STRATEGY, esSearchStrategyProvider( @@ -210,6 +219,7 @@ export class SearchService implements Plugin { public stop() { this.aggsService.stop(); + this.sessionService.stop(); } private registerSearchStrategy = < @@ -235,7 +245,20 @@ export class SearchService implements Plugin { options?.strategy ); - return strategy.search(deps, searchRequest, options); + let isFirstResponse = true; + return strategy.search(deps, searchRequest, options).pipe( + tap((response) => { + if (isFirstResponse) { + isFirstResponse = false; + this.sessionService.trackId( + { savedObjectsClient: deps.savedObjectsClient }, + searchRequest, + response.id, + options + ); + } + }) + ); }; private cancel = (deps: SearchStrategyDependencies, id: string, options?: ISearchOptions) => { @@ -260,7 +283,9 @@ export class SearchService implements Plugin { private asScopedProvider = ({ elasticsearch, savedObjects, uiSettings }: CoreStart) => { return (request: KibanaRequest): ISearchClient => { - const savedObjectsClient = savedObjects.getScopedClient(request); + const savedObjectsClient = savedObjects.getScopedClient(request, { + includedHiddenTypes: [BACKGROUND_SESSION_TYPE], + }); const deps = { savedObjectsClient, esClient: elasticsearch.client.asScoped(request), diff --git a/src/plugins/data/server/search/session/index.ts b/src/plugins/data/server/search/session/index.ts new file mode 100644 index 0000000000000..11b5b16a02b56 --- /dev/null +++ b/src/plugins/data/server/search/session/index.ts @@ -0,0 +1,20 @@ +/* + * 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 { BackgroundSessionService, ISearchSessionClient } from './session_service'; diff --git a/src/plugins/data/server/search/session/session_service.ts b/src/plugins/data/server/search/session/session_service.ts new file mode 100644 index 0000000000000..d80b4ca79f6a4 --- /dev/null +++ b/src/plugins/data/server/search/session/session_service.ts @@ -0,0 +1,152 @@ +/* + * 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 { CoreStart, KibanaRequest, SavedObjectsClientContract } from 'kibana/server'; +import { createRequestHash, IKibanaSearchRequest, ISearchOptions } from '../../../common/search'; +import { + BACKGROUND_SESSION_TYPE, + BackgroundSessionSavedObjectAttributes, +} from '../../saved_objects'; + +const DEFAULT_EXPIRATION = 7 * 24 * 60 * 60 * 1000; + +export interface BackgroundSessionDependencies { + savedObjectsClient: SavedObjectsClientContract; +} + +export type ISearchSessionClient = Pick; + +export class BackgroundSessionService { + /** + * Map of sessionId to { [requestHash]: searchId } + * @private + */ + private sessionSearchMap = new Map>(); + + constructor() {} + + public setup = () => {}; + + public start = () => { + return { + save: this.save, + trackId: this.trackId, + }; + }; + + public stop = () => { + this.clear(); + }; + + public save = ( + { savedObjectsClient }: BackgroundSessionDependencies, + sessionId: string, + expires: Date = new Date(Date.now() + DEFAULT_EXPIRATION) + ) => { + // Get the mapping of request hash/search ID for this session + const searchMap = this.sessionSearchMap.get(sessionId) ?? new Map(); + + // Clear out the entries for this session ID so they don't get saved next time + this.sessionSearchMap.delete(sessionId); + + const attributes = { + expires: expires.toISOString(), + idMapping: Object.fromEntries(searchMap.entries()), + }; + return savedObjectsClient.create( + BACKGROUND_SESSION_TYPE, + attributes, + { id: sessionId } + ); + }; + + // TODO: Send 404 if the given ID doesn't belong to this user + public get = ({ savedObjectsClient }: BackgroundSessionDependencies, sessionId: string) => { + return savedObjectsClient.get( + BACKGROUND_SESSION_TYPE, + sessionId + ); + }; + + // TODO: Send 404 if the given ID doesn't belong to this user + public delete = ({ savedObjectsClient }: BackgroundSessionDependencies, sessionId: string) => { + return savedObjectsClient.delete(BACKGROUND_SESSION_TYPE, sessionId); + }; + + /** + * Tracks the given search request / search ID in memory. Should only be called directly by the + * search service. + * @param savedObjectsClient + * @param searchRequest + * @param searchId + * @param sessionId + * @param isStored + * @internal + */ + public trackId = ( + { savedObjectsClient }: BackgroundSessionDependencies, + searchRequest: IKibanaSearchRequest, + searchId?: string, + { sessionId, isStored = false }: ISearchOptions = {} + ) => { + 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. + if (isStored) { + const attributes = { + idMapping: { [requestHash]: searchId }, + }; + return savedObjectsClient.update( + BACKGROUND_SESSION_TYPE, + sessionId, + attributes + ); + } else { + const map = this.sessionSearchMap.get(sessionId) ?? new Map(); + map.set(requestHash, searchId); + return Promise.resolve(this.sessionSearchMap.set(sessionId, map)); + } + }; + + // TODO: When should we call this? Should we call `deleteId` or something instead? + public clear = () => { + this.sessionSearchMap.clear(); + }; + + public asScopedProvider = ({ savedObjects }: CoreStart) => { + return (request: KibanaRequest) => { + const savedObjectsClient = savedObjects.getScopedClient(request, { + includedHiddenTypes: [BACKGROUND_SESSION_TYPE], + }); + return { + save: (sessionId: string, expires?: Date) => { + return this.save({ savedObjectsClient }, sessionId, expires); + }, + get: (sessionId: string) => { + return this.get({ savedObjectsClient }, sessionId); + }, + delete: (sessionId: string) => { + return this.delete({ savedObjectsClient }, sessionId); + }, + }; + }; + }; +} diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index 612cedb7780bd..cc59eafba8565 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -806,6 +806,7 @@ function discoverController($element, $route, $scope, $timeout, $window, Promise return $scope.searchSource.fetch({ abortSignal: abortController.signal, sessionId, + isStored: data.search.session.isStored(), }); }) .then(onResults) @@ -820,6 +821,10 @@ function discoverController($element, $route, $scope, $timeout, $window, Promise }); }; + $scope.saveSession = async () => { + await data.search.session.save(); + }; + $scope.handleRefresh = function (_payload, isUpdate) { if (isUpdate === false) { refetch$.next(); diff --git a/src/plugins/discover/public/application/angular/discover_legacy.html b/src/plugins/discover/public/application/angular/discover_legacy.html index 8582f71c0cb88..38e96b2cc28a2 100644 --- a/src/plugins/discover/public/application/angular/discover_legacy.html +++ b/src/plugins/discover/public/application/angular/discover_legacy.html @@ -1,4 +1,5 @@ + { // If the response indicates of an error, stop polling and complete the observable if (isErrorResponse(response)) { @@ -86,7 +86,10 @@ export class EnhancedSearchInterceptor extends SearchInterceptor { return timer(pollInterval).pipe( // Send future requests using just the ID from the response mergeMap(() => { - return this.runSearch({ ...request, id }, combinedSignal, strategy); + return this.runSearch( + { ...request, id }, + { ...options, strategy, abortSignal: combinedSignal } + ); }) ); }), diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts index 58de427a14b07..3bac34166ccd1 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts @@ -85,6 +85,7 @@ export const enhancedEsSearchStrategyProvider = ( if (!request.id) { const submitOptions = toSnakeCase({ batchedReduceSize: 64, // Only report partial results every 64 shards; this should be reduced when we actually display partial results + keepOnCompletion: true, // Return an ID even when the search returns in the first response ...(await getDefaultSearchParams(uiSettingsClient)), ...asyncOptions, ...request.params, From 9dadfb22c0ffea82372933d5f5ef48c2335c3d05 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Tue, 20 Oct 2020 10:08:33 -0700 Subject: [PATCH 05/27] Update examples/search_examples/server/my_strategy.ts Co-authored-by: Anton Dosov --- examples/search_examples/server/my_strategy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/search_examples/server/my_strategy.ts b/examples/search_examples/server/my_strategy.ts index 957216c7b9713..b537e5b579833 100644 --- a/examples/search_examples/server/my_strategy.ts +++ b/examples/search_examples/server/my_strategy.ts @@ -35,7 +35,7 @@ export const mySearchStrategyProvider = ( ), cancel: async (deps, id) => { if (es.cancel) { - es.cancel(deps, id); + await es.cancel(deps, id); } }, }; From 2b3d2a0415930ecbad9f996bddc08a1d94ffb60c Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Mon, 26 Oct 2020 12:38:52 -0700 Subject: [PATCH 06/27] Review feedback --- .../search_examples/server/my_strategy.ts | 8 ++++---- .../search/search_source/search_source.ts | 5 +++-- .../es_search/es_search_strategy.test.ts | 8 ++++---- .../search/es_search/es_search_strategy.ts | 4 ++-- .../data/server/search/search_service.ts | 18 ++++++++--------- src/plugins/data/server/search/types.ts | 6 +++--- .../public/search/search_interceptor.ts | 2 +- .../server/search/eql_search_strategy.test.ts | 20 +++++++++---------- .../server/search/eql_search_strategy.ts | 6 +++--- .../server/search/es_search_strategy.test.ts | 10 +++++----- .../server/search/es_search_strategy.ts | 20 +++++++++---------- .../search_strategy/index_fields/index.ts | 2 +- .../security_solution/index.ts | 8 ++++---- .../server/search_strategy/timeline/index.ts | 8 ++++---- 14 files changed, 63 insertions(+), 62 deletions(-) diff --git a/examples/search_examples/server/my_strategy.ts b/examples/search_examples/server/my_strategy.ts index 957216c7b9713..cb06f931bd119 100644 --- a/examples/search_examples/server/my_strategy.ts +++ b/examples/search_examples/server/my_strategy.ts @@ -26,16 +26,16 @@ export const mySearchStrategyProvider = ( ): ISearchStrategy => { const es = data.search.getSearchStrategy(); return { - search: (deps, request, options) => - es.search(deps, request, options).pipe( + search: (request, options, deps) => + es.search(request, options, deps).pipe( map((esSearchRes) => ({ ...esSearchRes, cool: request.get_cool ? 'YES' : 'NOPE', })) ), - cancel: async (deps, id) => { + cancel: async (id, options, deps) => { if (es.cancel) { - es.cancel(deps, id); + return es.cancel(id, options, deps); } }, }; diff --git a/src/plugins/data/common/search/search_source/search_source.ts b/src/plugins/data/common/search/search_source/search_source.ts index 418e316860eb9..f52b8434f0c8f 100644 --- a/src/plugins/data/common/search/search_source/search_source.ts +++ b/src/plugins/data/common/search/search_source/search_source.ts @@ -71,6 +71,7 @@ import { setWith } from '@elastic/safer-lodash-set'; import { uniqueId, uniq, extend, pick, difference, omit, isObject, keys, isFunction } from 'lodash'; +import { map } from 'rxjs/operators'; import { normalizeSortRequest } from './normalize_sort_request'; import { filterDocvalueFields } from './filter_docvalue_fields'; import { fieldWildcardFilter } from '../../../../kibana_utils/common'; @@ -311,8 +312,8 @@ export class SearchSource { }); return search({ params, indexType: searchRequest.indexType }, options) - .toPromise() - .then(({ rawResponse }) => onResponse(searchRequest, rawResponse)); + .pipe(map(({ rawResponse }) => onResponse(searchRequest, rawResponse))) + .toPromise(); } /** diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.test.ts b/src/plugins/data/server/search/es_search/es_search_strategy.test.ts index ddac28d9d54f5..4556bee94603f 100644 --- a/src/plugins/data/server/search/es_search/es_search_strategy.test.ts +++ b/src/plugins/data/server/search/es_search/es_search_strategy.test.ts @@ -59,7 +59,7 @@ describe('ES search strategy', () => { const params = { index: 'logstash-*' }; await esSearchStrategyProvider(mockConfig$, mockLogger) - .search(mockDeps, { params }, {}) + .search({ params }, {}, mockDeps) .subscribe(() => { expect(mockApiCaller).toBeCalled(); expect(mockApiCaller.mock.calls[0][0]).toEqual({ @@ -75,7 +75,7 @@ describe('ES search strategy', () => { const params = { index: 'logstash-*', ignore_unavailable: false, timeout: '1000ms' }; await esSearchStrategyProvider(mockConfig$, mockLogger) - .search(mockDeps, { params }, {}) + .search({ params }, {}, mockDeps) .subscribe(() => { expect(mockApiCaller).toBeCalled(); expect(mockApiCaller.mock.calls[0][0]).toEqual({ @@ -89,11 +89,11 @@ describe('ES search strategy', () => { it('has all response parameters', async (done) => await esSearchStrategyProvider(mockConfig$, mockLogger) .search( - mockDeps, { params: { index: 'logstash-*' }, }, - {} + {}, + mockDeps ) .subscribe((data) => { expect(data.isRunning).toBe(false); diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.ts b/src/plugins/data/server/search/es_search/es_search_strategy.ts index cb66cafb66144..d20f90ae1cb0b 100644 --- a/src/plugins/data/server/search/es_search/es_search_strategy.ts +++ b/src/plugins/data/server/search/es_search/es_search_strategy.ts @@ -38,7 +38,7 @@ export const esSearchStrategyProvider = ( usage?: SearchUsage ): ISearchStrategy => { return { - search: ({ esClient, uiSettingsClient }, request, options) => + search: (request, options, { esClient, uiSettingsClient }) => from( new Promise(async (resolve, reject) => { logger.debug(`search ${request.params?.index}`); @@ -64,7 +64,7 @@ export const esSearchStrategyProvider = ( try { const promise = shimAbortSignal( esClient.asCurrentUser.search(params), - options?.abortSignal + options.abortSignal ); const { body: rawResponse } = (await promise) as ApiResponse>; diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 2b4eee1db18e5..b6d9d7f3e4e6a 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -227,21 +227,21 @@ export class SearchService implements Plugin { SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest, SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse >( - deps: SearchStrategyDependencies, searchRequest: SearchStrategyRequest, - options?: ISearchOptions + options: ISearchOptions, + deps: SearchStrategyDependencies ) => { const strategy = this.getSearchStrategy( - options?.strategy + options.strategy ); - return strategy.search(deps, searchRequest, options); + return strategy.search(searchRequest, options, deps); }; - private cancel = (deps: SearchStrategyDependencies, id: string, options?: ISearchOptions) => { - const strategy = this.getSearchStrategy(options?.strategy); + private cancel = (id: string, options: ISearchOptions, deps: SearchStrategyDependencies) => { + const strategy = this.getSearchStrategy(options.strategy); - return strategy.cancel ? strategy.cancel(deps, id) : Promise.resolve(); + return strategy.cancel ? strategy.cancel(id, options, deps) : Promise.resolve(); }; private getSearchStrategy = < @@ -267,8 +267,8 @@ export class SearchService implements Plugin { uiSettingsClient: uiSettings.asScopedToClient(savedObjectsClient), }; return { - search: (searchRequest, options) => this.search(deps, searchRequest, options), - cancel: (id, options) => this.cancel(deps, id, options), + search: (searchRequest, options = {}) => this.search(searchRequest, options, deps), + cancel: (id, options = {}) => this.cancel(id, options, deps), }; }; }; diff --git a/src/plugins/data/server/search/types.ts b/src/plugins/data/server/search/types.ts index 622d6856b20f6..ebce02014c2a4 100644 --- a/src/plugins/data/server/search/types.ts +++ b/src/plugins/data/server/search/types.ts @@ -79,11 +79,11 @@ export interface ISearchStrategy< SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse > { search: ( - deps: SearchStrategyDependencies, request: SearchStrategyRequest, - options?: ISearchOptions + options: ISearchOptions, + deps: SearchStrategyDependencies ) => Observable; - cancel?: (deps: SearchStrategyDependencies, id: string) => Promise; + cancel?: (id: string, options: ISearchOptions, deps: SearchStrategyDependencies) => Promise; } export interface ISearchStart< 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 aee32a7c62759..91304e3191ae2 100644 --- a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts +++ b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts @@ -65,7 +65,7 @@ export class EnhancedSearchInterceptor extends SearchInterceptor { timeout: this.searchTimeout, }); const aborted$ = from(toPromise(combinedSignal)); - const strategy = options?.strategy || ENHANCED_ES_SEARCH_STRATEGY; + const strategy = options?.strategy ?? ENHANCED_ES_SEARCH_STRATEGY; this.pendingCount$.next(this.pendingCount$.getValue() + 1); diff --git a/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.test.ts b/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.test.ts index 91bc4ac524b9e..efa481ec15c40 100644 --- a/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.test.ts @@ -77,7 +77,7 @@ describe('EQL search strategy', () => { describe('async functionality', () => { it('performs an eql client search with params when no ID is provided', async () => { const eqlSearch = await eqlSearchStrategyProvider(mockLogger); - await eqlSearch.search(mockDeps, { options, params }, {}).toPromise(); + await eqlSearch.search({ options, params }, {}, mockDeps).toPromise(); const [[request, requestOptions]] = mockEqlSearch.mock.calls; expect(request.index).toEqual('logstash-*'); @@ -87,7 +87,7 @@ describe('EQL search strategy', () => { it('retrieves the current request if an id is provided', async () => { const eqlSearch = await eqlSearchStrategyProvider(mockLogger); - await eqlSearch.search(mockDeps, { id: 'my-search-id' }, {}).toPromise(); + await eqlSearch.search({ id: 'my-search-id' }, {}, mockDeps).toPromise(); const [[requestParams]] = mockEqlGet.mock.calls; expect(mockEqlSearch).not.toHaveBeenCalled(); @@ -98,7 +98,7 @@ describe('EQL search strategy', () => { expect.assertions(1); mockEqlSearch.mockReset().mockRejectedValueOnce(new Error('client error')); const eqlSearch = await eqlSearchStrategyProvider(mockLogger); - eqlSearch.search({ options, params }, {}, mockContext).subscribe( + eqlSearch.search({ options, params }, {}, mockDeps).subscribe( () => {}, (err) => { expect(err).toEqual(new Error('client error')); @@ -110,7 +110,7 @@ describe('EQL search strategy', () => { describe('arguments', () => { it('sends along async search options', async () => { const eqlSearch = await eqlSearchStrategyProvider(mockLogger); - await eqlSearch.search(mockDeps, { options, params }, {}).toPromise(); + await eqlSearch.search({ options, params }, {}, mockDeps).toPromise(); const [[request]] = mockEqlSearch.mock.calls; expect(request).toEqual( @@ -123,7 +123,7 @@ describe('EQL search strategy', () => { it('sends along default search parameters', async () => { const eqlSearch = await eqlSearchStrategyProvider(mockLogger); - await eqlSearch.search(mockDeps, { options, params }, {}).toPromise(); + await eqlSearch.search({ options, params }, {}, mockDeps).toPromise(); const [[request]] = mockEqlSearch.mock.calls; expect(request).toEqual( @@ -138,7 +138,6 @@ describe('EQL search strategy', () => { const eqlSearch = await eqlSearchStrategyProvider(mockLogger); await eqlSearch .search( - mockDeps, { options, params: { @@ -147,7 +146,8 @@ describe('EQL search strategy', () => { keep_on_completion: false, }, }, - {} + {}, + mockDeps ) .toPromise(); const [[request]] = mockEqlSearch.mock.calls; @@ -165,12 +165,12 @@ describe('EQL search strategy', () => { const eqlSearch = await eqlSearchStrategyProvider(mockLogger); await eqlSearch .search( - mockDeps, { options: { ...options, maxRetries: 2, ignore: [300] }, params, }, - {} + {}, + mockDeps ) .toPromise(); const [[, requestOptions]] = mockEqlSearch.mock.calls; @@ -186,7 +186,7 @@ describe('EQL search strategy', () => { it('passes transport options for an existing request', async () => { const eqlSearch = await eqlSearchStrategyProvider(mockLogger); await eqlSearch - .search(mockDeps, { id: 'my-search-id', options: { ignore: [400] } }, {}) + .search({ id: 'my-search-id', options: { ignore: [400] } }, {}, mockDeps) .toPromise(); const [[, requestOptions]] = mockEqlGet.mock.calls; diff --git a/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.ts index d2c3c22dca383..ad154014866ea 100644 --- a/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.ts @@ -21,13 +21,13 @@ export const eqlSearchStrategyProvider = ( logger: Logger ): ISearchStrategy => { return { - cancel: async ({ esClient }, id) => { + cancel: async (id, options, { esClient }) => { logger.debug(`_eql/delete ${id}`); await esClient.asCurrentUser.eql.delete({ id, }); }, - search: ({ esClient, uiSettingsClient }, request, options) => + search: (request, options, { esClient, uiSettingsClient }) => from( new Promise(async (resolve, reject) => { logger.debug(`_eql/search ${JSON.stringify(request.params) || request.id}`); @@ -62,7 +62,7 @@ export const eqlSearchStrategyProvider = ( ); } - const rawResponse = await shimAbortSignal(promise, options?.abortSignal); + const rawResponse = await shimAbortSignal(promise, options.abortSignal); const { id, is_partial: isPartial, is_running: isRunning } = rawResponse.body; resolve({ diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts index 4f6b490972f06..b9b6e25067f2f 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts @@ -80,7 +80,7 @@ describe('ES search strategy', () => { const params = { index: 'logstash-*', body: { query: {} } }; const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); - await esSearch.search(mockDeps, { params }, {}).toPromise(); + await esSearch.search({ params }, {}, mockDeps).toPromise(); expect(mockSubmitCaller).toBeCalled(); const request = mockSubmitCaller.mock.calls[0][0]; @@ -94,7 +94,7 @@ describe('ES search strategy', () => { const params = { index: 'logstash-*', body: { query: {} } }; const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); - await esSearch.search(mockDeps, { id: 'foo', params }, {}).toPromise(); + await esSearch.search({ id: 'foo', params }, {}, mockDeps).toPromise(); expect(mockGetCaller).toBeCalled(); const request = mockGetCaller.mock.calls[0][0]; @@ -111,12 +111,12 @@ describe('ES search strategy', () => { await esSearch .search( - mockDeps, { indexType: 'rollup', params, }, - {} + {}, + mockDeps ) .toPromise(); @@ -132,7 +132,7 @@ describe('ES search strategy', () => { const params = { index: 'foo-*', body: {} }; const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); - await esSearch.search(mockDeps, { params }, {}).toPromise(); + await esSearch.search({ params }, {}, mockDeps).toPromise(); expect(mockSubmitCaller).toBeCalled(); const request = mockSubmitCaller.mock.calls[0][0]; diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts index 58de427a14b07..144a063148953 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts @@ -39,7 +39,7 @@ export const enhancedEsSearchStrategyProvider = ( usage?: SearchUsage ): ISearchStrategy => { return { - search: (deps, request, options) => + search: (request, options, deps) => from( new Promise(async (resolve, reject) => { logger.debug(`search ${JSON.stringify(request.params) || request.id}`); @@ -48,8 +48,8 @@ export const enhancedEsSearchStrategyProvider = ( try { const response = isAsync - ? await asyncSearch(deps, request, options) - : await rollupSearch(deps, request, options); + ? await asyncSearch(request, options, deps) + : await rollupSearch(request, options, deps); if ( usage && @@ -67,16 +67,16 @@ export const enhancedEsSearchStrategyProvider = ( } }) ), - cancel: async ({ esClient }, id) => { + cancel: async (id, options, { esClient }) => { logger.debug(`cancel ${id}`); await esClient.asCurrentUser.asyncSearch.delete({ id }); }, }; async function asyncSearch( - { esClient, uiSettingsClient }: SearchStrategyDependencies, request: IEnhancedEsSearchRequest, - options?: ISearchOptions + options: ISearchOptions, + { esClient, uiSettingsClient }: SearchStrategyDependencies ): Promise { let promise: TransportRequestPromise; const asyncOptions = getAsyncOptions(); @@ -98,7 +98,7 @@ export const enhancedEsSearchStrategyProvider = ( }); } - const esResponse = await shimAbortSignal(promise, options?.abortSignal); + const esResponse = await shimAbortSignal(promise, options.abortSignal); const { id, response, is_partial: isPartial, is_running: isRunning } = esResponse.body; return { id, @@ -110,9 +110,9 @@ export const enhancedEsSearchStrategyProvider = ( } async function rollupSearch( - { esClient, uiSettingsClient }: SearchStrategyDependencies, request: IEnhancedEsSearchRequest, - options?: ISearchOptions + options: ISearchOptions, + { esClient, uiSettingsClient }: SearchStrategyDependencies ): Promise { const config = await config$.pipe(first()).toPromise(); const { body, index, ...params } = request.params!; @@ -131,7 +131,7 @@ export const enhancedEsSearchStrategyProvider = ( querystring, }); - const esResponse = await shimAbortSignal(promise, options?.abortSignal); + const esResponse = await shimAbortSignal(promise, options.abortSignal); const response = esResponse.body as SearchResponse; return { diff --git a/x-pack/plugins/security_solution/server/search_strategy/index_fields/index.ts b/x-pack/plugins/security_solution/server/search_strategy/index_fields/index.ts index a0955f5d44f2a..81ee4dc7c9ad2 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/index_fields/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/index_fields/index.ts @@ -26,7 +26,7 @@ export const securitySolutionIndexFieldsProvider = (): ISearchStrategy< const beatFields: BeatFields = require('../../utils/beat_schema/fields').fieldsBeat; return { - search: ({ esClient }, request, options) => + search: (request, options, { esClient }) => from( new Promise(async (resolve) => { const indexPatternsFetcher = new IndexPatternsFetcher(esClient.asCurrentUser); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts index 2d6937f40b5d1..9128238631208 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts @@ -20,7 +20,7 @@ export const securitySolutionSearchStrategyProvider = { + search: (request, options, deps) => { if (request.factoryQueryType == null) { throw new Error('factoryQueryType is required'); } @@ -28,12 +28,12 @@ export const securitySolutionSearchStrategyProvider = queryFactory.parse(request, esSearchRes))); }, - cancel: async (deps, id) => { + cancel: async (id, options, deps) => { if (es.cancel) { - es.cancel(deps, id); + return es.cancel(id, options, deps); } }, }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/index.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/index.ts index 39f65054d4f71..6967db17813a8 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/timeline/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/timeline/index.ts @@ -20,7 +20,7 @@ export const securitySolutionTimelineSearchStrategyProvider = { + search: (request, options, deps) => { if (request.factoryQueryType == null) { throw new Error('factoryQueryType is required'); } @@ -29,12 +29,12 @@ export const securitySolutionTimelineSearchStrategyProvider = queryFactory.parse(request, esSearchRes))); }, - cancel: async (deps, id) => { + cancel: async (id, options, deps) => { if (es.cancel) { - es.cancel(deps, id); + return es.cancel(id, options, deps); } }, }; From 6c9aec5ef110aff5115fa32ec9e9e79d34b6bf00 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Wed, 28 Oct 2020 12:27:42 -0700 Subject: [PATCH 07/27] Fix checks --- examples/search_examples/server/my_strategy.ts | 4 ++-- .../server/series_functions/es/index.js | 14 ++++---------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/examples/search_examples/server/my_strategy.ts b/examples/search_examples/server/my_strategy.ts index f210fa9a32329..d5b19f0619bbc 100644 --- a/examples/search_examples/server/my_strategy.ts +++ b/examples/search_examples/server/my_strategy.ts @@ -33,9 +33,9 @@ export const mySearchStrategyProvider = ( cool: request.get_cool ? 'YES' : 'NOPE', })) ), - cancel: (id, options, deps) => { + cancel: async (id, options, deps) => { if (es.cancel) { - return es.cancel(id, options, deps); + await es.cancel(id, options, deps); } }, }; diff --git a/src/plugins/vis_type_timelion/server/series_functions/es/index.js b/src/plugins/vis_type_timelion/server/series_functions/es/index.js index fc3250f0d4726..c04f044b79717 100644 --- a/src/plugins/vis_type_timelion/server/series_functions/es/index.js +++ b/src/plugins/vis_type_timelion/server/series_functions/es/index.js @@ -130,16 +130,10 @@ export default new Datasource('es', { const body = buildRequest(config, tlConfig, scriptedFields, esShardTimeout); - const deps = (await tlConfig.getStartServices())[1]; - - const resp = await deps.data.search - .search( - body, - { - strategy: ES_SEARCH_STRATEGY, - }, - tlConfig.context - ) + const resp = await tlConfig.context.search + .search(body, { + strategy: ES_SEARCH_STRATEGY, + }) .toPromise(); if (!resp.rawResponse._shards.total) { From 260e46c3825e348c5b19d718a0d07b0f95999108 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Wed, 28 Oct 2020 16:59:46 -0700 Subject: [PATCH 08/27] Add tapFirst and additional props for session --- src/plugins/data/common/utils/index.ts | 1 + .../data/common/utils/tap_first.test.ts | 30 +++++++++ src/plugins/data/common/utils/tap_first.ts | 31 +++++++++ .../saved_objects/background_session.ts | 12 ++++ .../data/server/search/search_service.ts | 22 +++---- .../server/search/session/session_service.ts | 66 ++++++++++++++----- 6 files changed, 130 insertions(+), 32 deletions(-) create mode 100644 src/plugins/data/common/utils/tap_first.test.ts create mode 100644 src/plugins/data/common/utils/tap_first.ts diff --git a/src/plugins/data/common/utils/index.ts b/src/plugins/data/common/utils/index.ts index 33989f3ad50a7..5babe58b58191 100644 --- a/src/plugins/data/common/utils/index.ts +++ b/src/plugins/data/common/utils/index.ts @@ -20,3 +20,4 @@ /** @internal */ export { shortenDottedString } from './shorten_dotted_string'; export { AbortError, toPromise, getCombinedSignal } from './abort_utils'; +export { tapFirst } from './tap_first'; diff --git a/src/plugins/data/common/utils/tap_first.test.ts b/src/plugins/data/common/utils/tap_first.test.ts new file mode 100644 index 0000000000000..033ae59f8c715 --- /dev/null +++ b/src/plugins/data/common/utils/tap_first.test.ts @@ -0,0 +1,30 @@ +/* + * 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 { of } from 'rxjs'; +import { tapFirst } from './tap_first'; + +describe('tapFirst', () => { + it('should tap the first and only the first', () => { + const fn = jest.fn(); + of(1, 2, 3).pipe(tapFirst(fn)).subscribe(); + expect(fn).toBeCalledTimes(1); + expect(fn).lastCalledWith(1); + }); +}); diff --git a/src/plugins/data/common/utils/tap_first.ts b/src/plugins/data/common/utils/tap_first.ts new file mode 100644 index 0000000000000..2c783a3ef87f0 --- /dev/null +++ b/src/plugins/data/common/utils/tap_first.ts @@ -0,0 +1,31 @@ +/* + * 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 { pipe } from 'rxjs'; +import { tap } from 'rxjs/operators'; + +export function tapFirst(next: (x: T) => void) { + let isFirst = true; + return pipe( + tap((x: T) => { + if (isFirst) next(x); + isFirst = false; + }) + ); +} diff --git a/src/plugins/data/server/saved_objects/background_session.ts b/src/plugins/data/server/saved_objects/background_session.ts index bb12525134111..e4c793c9d47d2 100644 --- a/src/plugins/data/server/saved_objects/background_session.ts +++ b/src/plugins/data/server/saved_objects/background_session.ts @@ -27,6 +27,15 @@ export const backgroundSessionMapping: SavedObjectsType = { hidden: true, mappings: { properties: { + name: { + type: 'keyword', + }, + url: { + type: 'keyword', + }, + userId: { + type: 'keyword', + }, expires: { type: 'date', }, @@ -39,6 +48,9 @@ export const backgroundSessionMapping: SavedObjectsType = { }; export interface BackgroundSessionSavedObjectAttributes { + name: string; + userId: string; + url: string; expires: string; idMapping: Record; } diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index cbab704b31b55..698b2038d13af 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -69,11 +69,11 @@ import { ConfigSchema } from '../../config'; import { BackgroundSessionService, ISearchSessionClient } from './session'; import { registerSessionRoutes } from './routes/session'; import { backgroundSessionMapping } from '../saved_objects'; -import { tapFirst } from '../lib/tap_once'; +import { tapFirst } from '../../common/utils'; declare module 'src/core/server' { interface RequestHandlerContext { - search?: ISearchClient; + search?: ISearchClient & { session: ISearchSessionClient }; } } @@ -122,7 +122,7 @@ export class SearchService implements Plugin { }; registerSearchRoute(router); registerMsearchRoute(router, routeDependencies); - registerSessionRoutes(router, routeDependencies); + registerSessionRoutes(router); core.http.registerRouteHandlerContext('search', async (context, request) => { const [coreStart] = await core.getStartServices(); @@ -246,18 +246,12 @@ export class SearchService implements Plugin { options.strategy ); - let isFirstResponse = true; return strategy.search(searchRequest, options, deps).pipe( - tap((response) => { - if (isFirstResponse) { - isFirstResponse = false; - this.sessionService.trackId( - { savedObjectsClient: deps.savedObjectsClient }, - searchRequest, - response.id, - options - ); - } + tapFirst((response) => { + if (!options.sessionId || !response.id) return; + this.sessionService.trackId(searchRequest, response.id, options, { + savedObjectsClient: deps.savedObjectsClient, + }); }) ); }; diff --git a/src/plugins/data/server/search/session/session_service.ts b/src/plugins/data/server/search/session/session_service.ts index d80b4ca79f6a4..6ffabfb757642 100644 --- a/src/plugins/data/server/search/session/session_service.ts +++ b/src/plugins/data/server/search/session/session_service.ts @@ -30,7 +30,9 @@ export interface BackgroundSessionDependencies { savedObjectsClient: SavedObjectsClientContract; } -export type ISearchSessionClient = Pick; +export type ISearchSessionClient = ReturnType< + ReturnType +>; export class BackgroundSessionService { /** @@ -39,25 +41,41 @@ export class BackgroundSessionService { */ private sessionSearchMap = new Map>(); + private timeout?: NodeJS.Timeout; + constructor() {} public setup = () => {}; - public start = () => { + public start = (core: CoreStart) => { return { save: this.save, trackId: this.trackId, + asScoped: this.asScopedProvider(core), }; + + const savedObjectsClient = core.savedObjects.createInternalRepository([ + BACKGROUND_SESSION_TYPE, + ]); + + (async () => { + this.timeout = await this.updateBackgroundSessions({ savedObjectsClient }); + })(); }; public stop = () => { this.clear(); + if (this.timeout) clearTimeout(this.timeout); }; + // TODO: Generate the `userId` from the realm type/realm name/username public save = ( - { savedObjectsClient }: BackgroundSessionDependencies, sessionId: string, - expires: Date = new Date(Date.now() + DEFAULT_EXPIRATION) + name: string, + url: string, + userId: string, + expires: Date = new Date(Date.now() + DEFAULT_EXPIRATION), + { savedObjectsClient }: BackgroundSessionDependencies ) => { // Get the mapping of request hash/search ID for this session const searchMap = this.sessionSearchMap.get(sessionId) ?? new Map(); @@ -65,10 +83,8 @@ export class BackgroundSessionService { // Clear out the entries for this session ID so they don't get saved next time this.sessionSearchMap.delete(sessionId); - const attributes = { - expires: expires.toISOString(), - idMapping: Object.fromEntries(searchMap.entries()), - }; + const idMapping = Object.fromEntries(searchMap.entries()); + const attributes = { name, url, userId, expires: expires.toISOString(), idMapping }; return savedObjectsClient.create( BACKGROUND_SESSION_TYPE, attributes, @@ -77,7 +93,7 @@ export class BackgroundSessionService { }; // TODO: Send 404 if the given ID doesn't belong to this user - public get = ({ savedObjectsClient }: BackgroundSessionDependencies, sessionId: string) => { + public get = (sessionId: string, { savedObjectsClient }: BackgroundSessionDependencies) => { return savedObjectsClient.get( BACKGROUND_SESSION_TYPE, sessionId @@ -85,7 +101,7 @@ export class BackgroundSessionService { }; // TODO: Send 404 if the given ID doesn't belong to this user - public delete = ({ savedObjectsClient }: BackgroundSessionDependencies, sessionId: string) => { + public delete = (sessionId: string, { savedObjectsClient }: BackgroundSessionDependencies) => { return savedObjectsClient.delete(BACKGROUND_SESSION_TYPE, sessionId); }; @@ -100,12 +116,13 @@ export class BackgroundSessionService { * @internal */ public trackId = ( - { savedObjectsClient }: BackgroundSessionDependencies, searchRequest: IKibanaSearchRequest, - searchId?: string, - { sessionId, isStored = false }: ISearchOptions = {} + searchId: string, + { sessionId, isStored = false }: ISearchOptions, + { savedObjectsClient }: BackgroundSessionDependencies ) => { - if (!sessionId || !searchId) return; + if (!sessionId) return; + console.log(`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. @@ -137,16 +154,29 @@ export class BackgroundSessionService { includedHiddenTypes: [BACKGROUND_SESSION_TYPE], }); return { - save: (sessionId: string, expires?: Date) => { - return this.save({ savedObjectsClient }, sessionId, expires); + save: (sessionId: string, name: string, url: string, userId: string, expires?: Date) => { + return this.save(sessionId, name, url, userId, expires, { savedObjectsClient }); }, get: (sessionId: string) => { - return this.get({ savedObjectsClient }, sessionId); + return this.get(sessionId, { savedObjectsClient }); }, delete: (sessionId: string) => { - return this.delete({ savedObjectsClient }, sessionId); + return this.delete(sessionId, { savedObjectsClient }); }, }; }; }; + + private updateBackgroundSessions = async ({ + savedObjectsClient, + }: BackgroundSessionDependencies): Promise => { + for (const sessionId in this.sessionSearchMap.keys()) { + const searchMap = this.sessionSearchMap.get(sessionId); + const { attributes } = await this.get(sessionId, { savedObjectsClient }); + const idMapping = Object.fromEntries(searchMap.entries()); + console.log(`updateBackgroundSessions ${sessionId}`); + await savedObjectsClient.update(BACKGROUND_SESSION_TYPE, sessionId, { idMapping }); + } + return setTimeout(() => this.updateBackgroundSessions({ savedObjectsClient }), 10000); + }; } From 1ae55efa63b53e7cf18239ab509bda3e1c79423d Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Thu, 29 Oct 2020 09:32:05 -0700 Subject: [PATCH 09/27] Fix CI --- ...n-plugins-data-server.isearchstrategy.cancel.md | 2 +- ...a-plugin-plugins-data-server.isearchstrategy.md | 4 ++-- ...n-plugins-data-server.isearchstrategy.search.md | 2 +- src/plugins/data/server/server.api.md | 4 ++-- .../server/series_functions/es/es.test.js | 9 +-------- .../server/series_functions/fixtures/tl_config.js | 14 +++++--------- 6 files changed, 12 insertions(+), 23 deletions(-) diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.cancel.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.cancel.md index 5267290b3b0ee..709d9bb7be9e5 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.cancel.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.cancel.md @@ -7,5 +7,5 @@ Signature: ```typescript -cancel?: (deps: SearchStrategyDependencies, id: string) => Promise; +cancel?: (id: string, options: ISearchOptions, deps: SearchStrategyDependencies) => Promise; ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.md index 39cc51049175e..c9f4c886735a7 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.md @@ -16,6 +16,6 @@ export interface ISearchStrategy(deps: SearchStrategyDependencies, id: string) => Promise<void> | | -| [search](./kibana-plugin-plugins-data-server.isearchstrategy.search.md) | (deps: SearchStrategyDependencies, request: SearchStrategyRequest, options?: ISearchOptions) => Observable<SearchStrategyResponse> | | +| [cancel](./kibana-plugin-plugins-data-server.isearchstrategy.cancel.md) | (id: string, options: ISearchOptions, deps: SearchStrategyDependencies) => Promise<void> | | +| [search](./kibana-plugin-plugins-data-server.isearchstrategy.search.md) | (request: SearchStrategyRequest, options: ISearchOptions, deps: SearchStrategyDependencies) => Observable<SearchStrategyResponse> | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.search.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.search.md index d7911cbfdcbac..266995f2ec82c 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.search.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstrategy.search.md @@ -7,5 +7,5 @@ Signature: ```typescript -search: (deps: SearchStrategyDependencies, request: SearchStrategyRequest, options?: ISearchOptions) => Observable; +search: (request: SearchStrategyRequest, options: ISearchOptions, deps: SearchStrategyDependencies) => Observable; ``` diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 952bdf28235c5..b10f2d2891067 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -741,9 +741,9 @@ export interface ISearchStart { // (undocumented) - cancel?: (deps: SearchStrategyDependencies, id: string) => Promise; + cancel?: (id: string, options: ISearchOptions, deps: SearchStrategyDependencies) => Promise; // (undocumented) - search: (deps: SearchStrategyDependencies, request: SearchStrategyRequest, options?: ISearchOptions) => Observable; + search: (request: SearchStrategyRequest, options: ISearchOptions, deps: SearchStrategyDependencies) => Observable; } // @public (undocumented) diff --git a/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js b/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js index 8be3cf5171c65..95cc9388574a0 100644 --- a/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js +++ b/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js @@ -33,14 +33,7 @@ import { UI_SETTINGS } from '../../../../data/server'; function stubRequestAndServer(response, indexPatternSavedObjects = []) { return { - getStartServices: sinon - .stub() - .returns( - Promise.resolve([ - {}, - { data: { search: { search: () => from(Promise.resolve(response)) } } }, - ]) - ), + context: { search: { search: () => from(Promise.resolve(response)) } }, savedObjectsClient: { find: function () { return Promise.resolve({ diff --git a/src/plugins/vis_type_timelion/server/series_functions/fixtures/tl_config.js b/src/plugins/vis_type_timelion/server/series_functions/fixtures/tl_config.js index 38d70278fbf00..2f51cf38c0180 100644 --- a/src/plugins/vis_type_timelion/server/series_functions/fixtures/tl_config.js +++ b/src/plugins/vis_type_timelion/server/series_functions/fixtures/tl_config.js @@ -18,7 +18,7 @@ */ import moment from 'moment'; -import sinon from 'sinon'; +import { of } from 'rxjs'; import timelionDefaults from '../../lib/get_namespaced_settings'; import esResponse from './es_response'; @@ -30,14 +30,6 @@ export default function () { if (!functions[name]) throw new Error('No such function: ' + name); return functions[name]; }, - getStartServices: sinon - .stub() - .returns( - Promise.resolve([ - {}, - { data: { search: { search: () => Promise.resolve({ rawResponse: esResponse }) } } }, - ]) - ), esShardTimeout: moment.duration(30000), allowedGraphiteUrls: ['https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite'], @@ -54,5 +46,9 @@ export default function () { tlConfig.setTargetSeries(); + tlConfig.context = { + search: { search: () => of({ rawResponse: esResponse }) }, + }; + return tlConfig; } From 4634fb47d486c24487477769d6d3198081505997 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Thu, 29 Oct 2020 15:36:23 -0700 Subject: [PATCH 10/27] Fix security search --- .../server/search_strategy/security_solution/index.ts | 2 +- .../security_solution/server/search_strategy/timeline/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts index 9128238631208..4abec07b3b493 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts @@ -17,7 +17,7 @@ import { SecuritySolutionFactory } from './factory/types'; export const securitySolutionSearchStrategyProvider = ( data: PluginStart ): ISearchStrategy, StrategyResponseType> => { - const es = data.search.getSearchStrategy(); + const es = data.search.getSearchStrategy('es'); return { search: (request, options, deps) => { diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/index.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/index.ts index 6967db17813a8..0b73eed61765f 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/timeline/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/timeline/index.ts @@ -17,7 +17,7 @@ import { SecuritySolutionTimelineFactory } from './factory/types'; export const securitySolutionTimelineSearchStrategyProvider = ( data: PluginStart ): ISearchStrategy, TimelineStrategyResponseType> => { - const es = data.search.getSearchStrategy(); + const es = data.search.getSearchStrategy('es'); return { search: (request, options, deps) => { From 5f7c9abdbdea13345d60b9b327702885b64aa9e0 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Mon, 2 Nov 2020 10:08:48 -0700 Subject: [PATCH 11/27] Fix test --- .../vis_type_timelion/server/series_functions/es/es.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js b/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js index e1ce94d0694fe..2fa18b259de52 100644 --- a/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js +++ b/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js @@ -32,7 +32,6 @@ import { UI_SETTINGS } from '../../../../data/server'; describe('es', () => { let tlConfig; - let dataSearchStub; function stubRequestAndServer(response, indexPatternSavedObjects = []) { return { @@ -73,7 +72,7 @@ describe('es', () => { await invoke(es, [5], tlConfig); - expect(dataSearchStub.data.search.search.mock.calls[0][1]).toHaveProperty('sessionId', 1); + expect(tlConfig.context.search.search.mock.calls[0][1]).toHaveProperty('sessionId', 1); }); test('returns a seriesList', () => { From f1b515d57fc62ee478eccee3662e6baae7f4c0c2 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Mon, 2 Nov 2020 10:32:03 -0700 Subject: [PATCH 12/27] Fix test for reals --- .../vis_type_timelion/server/series_functions/es/es.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js b/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js index 2fa18b259de52..f4ba36e4fdd67 100644 --- a/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js +++ b/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js @@ -16,8 +16,8 @@ * specific language governing permissions and limitations * under the License. */ -import { from } from 'rxjs'; +import { of } from 'rxjs'; import es from './index'; import tlConfigFn from '../fixtures/tl_config'; import * as aggResponse from './lib/agg_response_to_series_list'; @@ -35,7 +35,7 @@ describe('es', () => { function stubRequestAndServer(response, indexPatternSavedObjects = []) { return { - context: { search: { search: () => from(Promise.resolve(response)) } }, + context: { search: { search: jest.fn().mockReturnValue(of(response)) } }, savedObjectsClient: { find: function () { return Promise.resolve({ From e32a4355227eaa6f974a28e0ebc9fdf897323e61 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Mon, 2 Nov 2020 17:18:21 -0700 Subject: [PATCH 13/27] Add restore method --- src/plugins/data/public/search/session_service.ts | 4 +++- src/plugins/discover/public/application/angular/discover.js | 4 ++++ .../public/application/angular/discover_legacy.html | 6 +++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/plugins/data/public/search/session_service.ts b/src/plugins/data/public/search/session_service.ts index 421f3b26053dc..23e194d81b84a 100644 --- a/src/plugins/data/public/search/session_service.ts +++ b/src/plugins/data/public/search/session_service.ts @@ -85,6 +85,7 @@ export class SessionService implements ISessionService { public restore(sessionId: string) { this.session$.next(sessionId); + return this.http.get(`/internal/session/${encodeURIComponent(sessionId)}`); } public clear() { @@ -93,11 +94,12 @@ export class SessionService implements ISessionService { } public async save() { - await this.http.post(`/internal/session`, { + const response = await this.http.post(`/internal/session`, { body: JSON.stringify({ sessionId: this.sessionId, }), }); this._isStored = true; + return response; } } diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index a52684264c825..47a6c8a2b1e22 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -827,6 +827,10 @@ function discoverController($element, $route, $scope, $timeout, $window, Promise await data.search.session.save(); }; + $scope.restoreSession = async (id) => { + return data.search.session.restore(id); + }; + $scope.handleRefresh = function (_payload, isUpdate) { if (isUpdate === false) { refetch$.next(); diff --git a/src/plugins/discover/public/application/angular/discover_legacy.html b/src/plugins/discover/public/application/angular/discover_legacy.html index 38e96b2cc28a2..aa3e662a463a2 100644 --- a/src/plugins/discover/public/application/angular/discover_legacy.html +++ b/src/plugins/discover/public/application/angular/discover_legacy.html @@ -1,5 +1,9 @@ - +
+ + + +
Date: Mon, 9 Nov 2020 13:12:34 -0700 Subject: [PATCH 14/27] Add code to search examples --- .../search_examples/public/components/app.tsx | 33 ++++++++ .../data/common/search/session/types.ts | 8 ++ .../data/public/search/session_service.ts | 22 ++++- .../data/server/search/routes/session.ts | 70 ++++++++++++++++ .../server/search/session/session_service.ts | 82 +++++++++++-------- .../public/search/search_interceptor.ts | 8 +- .../server/search/es_search_strategy.ts | 1 + 7 files changed, 185 insertions(+), 39 deletions(-) diff --git a/examples/search_examples/public/components/app.tsx b/examples/search_examples/public/components/app.tsx index 2425f3bbad8a9..98c1edb720747 100644 --- a/examples/search_examples/public/components/app.tsx +++ b/examples/search_examples/public/components/app.tsx @@ -24,6 +24,7 @@ import { BrowserRouter as Router } from 'react-router-dom'; import { EuiButton, + EuiButtonEmpty, EuiPage, EuiPageBody, EuiPageContent, @@ -97,6 +98,7 @@ export const SearchExamplesApp = ({ const [indexPattern, setIndexPattern] = useState(); const [numericFields, setNumericFields] = useState(); const [selectedField, setSelectedField] = useState(); + const [searchSessionId, setSearchSessionId] = useState(); // Fetch the default index pattern using the `data.indexPatterns` service, as the component is mounted. useEffect(() => { @@ -117,6 +119,10 @@ export const SearchExamplesApp = ({ setSelectedField(fields?.length ? fields[0] : null); }, [indexPattern]); + useEffect(() => { + setSearchSessionId(data.search.session.start()); + }, [data]); + const doAsyncSearch = async (strategy?: string) => { if (!indexPattern || !selectedField) return; @@ -143,6 +149,8 @@ export const SearchExamplesApp = ({ const searchSubscription$ = data.search .search(request, { strategy, + sessionId: searchSessionId, + isStored: data.search.session.isStored(), }) .subscribe({ next: (response) => { @@ -268,6 +276,31 @@ export const SearchExamplesApp = ({ values={{ time: timeTook || 'Unknown' }} /> + + +
+ setSearchSessionId(data.search.session.start())} + > + Generate new search session ID + + + data.search.session.save( + `Search example ${searchSessionId}`, + `/app/searchExamples?sessionId=${searchSessionId}` + ) + } + > + Save session + +
+

diff --git a/src/plugins/data/common/search/session/types.ts b/src/plugins/data/common/search/session/types.ts index 339432c365527..c279368411127 100644 --- a/src/plugins/data/common/search/session/types.ts +++ b/src/plugins/data/common/search/session/types.ts @@ -50,3 +50,11 @@ export interface ISessionService { */ isStored: () => boolean; } + +export interface SearchSessionFindOptions { + page?: number; + perPage?: number; + sortField?: string; + sortOrder?: string; + filter?: string; +} diff --git a/src/plugins/data/public/search/session_service.ts b/src/plugins/data/public/search/session_service.ts index 66f29ee29a9d9..d13b8af8928b5 100644 --- a/src/plugins/data/public/search/session_service.ts +++ b/src/plugins/data/public/search/session_service.ts @@ -21,7 +21,7 @@ import uuid from 'uuid'; import { BehaviorSubject, Subscription } from 'rxjs'; import { HttpStart, PluginInitializerContext, StartServicesAccessor } from 'kibana/public'; import { ConfigSchema } from '../../config'; -import { ISessionService } from '../../common'; +import { ISessionService, SearchSessionFindOptions } from '../../common'; export class SessionService implements ISessionService { private session$ = new BehaviorSubject(undefined); @@ -93,13 +93,29 @@ export class SessionService implements ISessionService { this.session$.next(undefined); } - public async save() { + public async save(name, url, sessionId = this.sessionId) { const response = await this.http!.post(`/internal/session`, { body: JSON.stringify({ - sessionId: this.sessionId, + name, + url, + sessionId, }), }); this._isStored = true; return response; } + + public delete(sessionId: string) { + return this.http!.delete(`/internal/session/${encodeURIComponent(sessionId)}}`); + } + + public get(sessionId: string) { + return this.http!.get(`/internal/session/${encodeURIComponent(sessionId)}}`); + } + + public find(options: SearchSessionFindOptions) { + return this.http!.post(`/internal/session`, { + body: JSON.stringify(options), + }); + } } diff --git a/src/plugins/data/server/search/routes/session.ts b/src/plugins/data/server/search/routes/session.ts index d00748777b1b3..70afb31a17036 100644 --- a/src/plugins/data/server/search/routes/session.ts +++ b/src/plugins/data/server/search/routes/session.ts @@ -85,4 +85,74 @@ export function registerSessionRoutes(router: IRouter): void { } } ); + + router.post( + { + path: '/internal/session/_find', + validate: { + body: schema.object({ + page: schema.maybe(schema.number()), + perPage: schema.maybe(schema.number()), + sortField: schema.maybe(schema.string()), + sortOrder: schema.maybe(schema.string()), + filter: schema.maybe(schema.string()), + }), + }, + }, + async (context, request, res) => { + const { page, perPage, sortField, sortOrder, filter } = request.body; + try { + const response = await context.search!.session.find({ + page, + perPage, + sortField, + sortOrder, + filter, + }); + + return res.ok({ + body: response, + }); + } catch (err) { + return res.customError({ + statusCode: err.statusCode || 500, + body: { + message: err.message, + attributes: { + error: err.body?.error || err.message, + }, + }, + }); + } + } + ); + + router.delete( + { + path: '/internal/session/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + async (context, request, res) => { + const { id } = request.params; + try { + await context.search!.session.delete(id); + + return res.ok(); + } catch (err) { + return res.customError({ + statusCode: err.statusCode || 500, + body: { + message: err.message, + attributes: { + error: err.body?.error || err.message, + }, + }, + }); + } + } + ); } diff --git a/src/plugins/data/server/search/session/session_service.ts b/src/plugins/data/server/search/session/session_service.ts index 6ffabfb757642..7e70161d69f96 100644 --- a/src/plugins/data/server/search/session/session_service.ts +++ b/src/plugins/data/server/search/session/session_service.ts @@ -17,8 +17,18 @@ * under the License. */ -import { CoreStart, KibanaRequest, SavedObjectsClientContract } from 'kibana/server'; -import { createRequestHash, IKibanaSearchRequest, ISearchOptions } from '../../../common/search'; +import { + CoreStart, + ISavedObjectsRepository, + KibanaRequest, + SavedObjectsClientContract, +} from 'kibana/server'; +import { + createRequestHash, + IKibanaSearchRequest, + ISearchOptions, + SearchSessionFindOptions, +} from '../../../common/search'; import { BACKGROUND_SESSION_TYPE, BackgroundSessionSavedObjectAttributes, @@ -41,6 +51,8 @@ export class BackgroundSessionService { */ private sessionSearchMap = new Map>(); + private internalSavedObjectsRepo?: ISavedObjectsRepository; + private timeout?: NodeJS.Timeout; constructor() {} @@ -54,13 +66,11 @@ export class BackgroundSessionService { asScoped: this.asScopedProvider(core), }; - const savedObjectsClient = core.savedObjects.createInternalRepository([ - BACKGROUND_SESSION_TYPE, - ]); - - (async () => { - this.timeout = await this.updateBackgroundSessions({ savedObjectsClient }); - })(); + // this.internalSavedObjectsRepo = core.savedObjects.createInternalRepository([ + // BACKGROUND_SESSION_TYPE, + // ]); + // + // this.timeout = setTimeout(this.updateBackgroundSessions, 10000); }; public stop = () => { @@ -100,6 +110,17 @@ export class BackgroundSessionService { ); }; + // TODO: Send 404 if the given ID doesn't belong to this user + public find = ( + options: SearchSessionFindOptions, + { savedObjectsClient }: BackgroundSessionDependencies + ) => { + return savedObjectsClient.find({ + ...options, + type: BACKGROUND_SESSION_TYPE, + }); + }; + // TODO: Send 404 if the given ID doesn't belong to this user public delete = (sessionId: string, { savedObjectsClient }: BackgroundSessionDependencies) => { return savedObjectsClient.delete(BACKGROUND_SESSION_TYPE, sessionId); @@ -115,14 +136,13 @@ export class BackgroundSessionService { * @param isStored * @internal */ - public trackId = ( + public trackId = async ( searchRequest: IKibanaSearchRequest, searchId: string, { sessionId, isStored = false }: ISearchOptions, { savedObjectsClient }: BackgroundSessionDependencies ) => { if (!sessionId) return; - console.log(`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. @@ -131,7 +151,7 @@ export class BackgroundSessionService { const attributes = { idMapping: { [requestHash]: searchId }, }; - return savedObjectsClient.update( + await savedObjectsClient.update( BACKGROUND_SESSION_TYPE, sessionId, attributes @@ -139,7 +159,7 @@ export class BackgroundSessionService { } else { const map = this.sessionSearchMap.get(sessionId) ?? new Map(); map.set(requestHash, searchId); - return Promise.resolve(this.sessionSearchMap.set(sessionId, map)); + this.sessionSearchMap.set(sessionId, map); } }; @@ -153,30 +173,24 @@ export class BackgroundSessionService { const savedObjectsClient = savedObjects.getScopedClient(request, { includedHiddenTypes: [BACKGROUND_SESSION_TYPE], }); + const deps = { savedObjectsClient }; return { - save: (sessionId: string, name: string, url: string, userId: string, expires?: Date) => { - return this.save(sessionId, name, url, userId, expires, { savedObjectsClient }); - }, - get: (sessionId: string) => { - return this.get(sessionId, { savedObjectsClient }); - }, - delete: (sessionId: string) => { - return this.delete(sessionId, { savedObjectsClient }); - }, + save: (sessionId: string, name: string, url: string, userId: string, expires?: Date) => + this.save(sessionId, name, url, userId, expires, deps), + get: (sessionId: string) => this.get(sessionId, deps), + find: (options: SearchSessionFindOptions) => this.find(options, deps), + delete: (sessionId: string) => this.delete(sessionId, deps), }; }; }; - private updateBackgroundSessions = async ({ - savedObjectsClient, - }: BackgroundSessionDependencies): Promise => { - for (const sessionId in this.sessionSearchMap.keys()) { - const searchMap = this.sessionSearchMap.get(sessionId); - const { attributes } = await this.get(sessionId, { savedObjectsClient }); - const idMapping = Object.fromEntries(searchMap.entries()); - console.log(`updateBackgroundSessions ${sessionId}`); - await savedObjectsClient.update(BACKGROUND_SESSION_TYPE, sessionId, { idMapping }); - } - return setTimeout(() => this.updateBackgroundSessions({ savedObjectsClient }), 10000); - }; + // private updateBackgroundSessions = async (): Promise => { + // for (const [sessionId, searchMap] of this.sessionSearchMap.entries()) { + // const idMapping = Object.fromEntries(searchMap.entries()); + // await this.internalSavedObjectsRepo!.update(BACKGROUND_SESSION_TYPE, sessionId, { + // idMapping, + // }); + // } + // return (this.timeout = setTimeout(this.updateBackgroundSessions, 10000)); + // }; } 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 4cafcdb29ae8d..3d25507f209ce 100644 --- a/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts +++ b/x-pack/plugins/data_enhanced/public/search/search_interceptor.ts @@ -76,8 +76,12 @@ export class EnhancedSearchInterceptor extends SearchInterceptor { this.pendingCount$.next(this.pendingCount$.getValue() + 1); return doPartialSearch( - () => this.runSearch(request, combinedSignal, strategy), - (requestId) => this.runSearch({ ...request, id: requestId }, combinedSignal, strategy), + () => this.runSearch(request, { ...options, strategy, abortSignal: combinedSignal }), + (requestId) => + this.runSearch( + { ...request, id: requestId }, + { ...options, strategy, abortSignal: combinedSignal } + ), (r) => !r.isRunning, (response) => response.id, id, diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts index 53bcac02cb01d..c4ab2d7d0e581 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts @@ -57,6 +57,7 @@ export const enhancedEsSearchStrategyProvider = ( utils.toSnakeCase({ ...(await getDefaultSearchParams(uiSettingsClient)), batchedReduceSize: 64, + keepOnCompletion: true, ...asyncOptions, ...request.params, }) From 8fbe3172f6988319da45394e2b004afd614ff74b Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Mon, 9 Nov 2020 17:26:27 -0700 Subject: [PATCH 15/27] Add restore and search using restored ID --- examples/search_examples/kibana.json | 2 +- .../search_examples/public/components/app.tsx | 51 ++++++++++++++----- .../data/common/search/session/types.ts | 12 ++++- src/plugins/data/common/search/types.ts | 6 +++ .../data/public/search/search_interceptor.ts | 1 + .../data/public/search/session_service.ts | 18 +++++++ .../data/server/search/routes/search.ts | 4 +- .../data/server/search/search_service.ts | 18 +++++-- .../server/search/session/session_service.ts | 15 ++++++ 9 files changed, 107 insertions(+), 20 deletions(-) diff --git a/examples/search_examples/kibana.json b/examples/search_examples/kibana.json index 7e392b8417360..4965f09b85a1f 100644 --- a/examples/search_examples/kibana.json +++ b/examples/search_examples/kibana.json @@ -3,7 +3,7 @@ "version": "8.0.0", "server": true, "ui": true, - "requiredPlugins": ["navigation", "data", "developerExamples"], + "requiredPlugins": ["navigation", "data", "developerExamples", "kibanaUtils"], "optionalPlugins": [], "requiredBundles": [] } diff --git a/examples/search_examples/public/components/app.tsx b/examples/search_examples/public/components/app.tsx index 98c1edb720747..5f7f52a2a4177 100644 --- a/examples/search_examples/public/components/app.tsx +++ b/examples/search_examples/public/components/app.tsx @@ -39,11 +39,14 @@ import { EuiCode, EuiComboBox, EuiFormLabel, + EuiLink, } from '@elastic/eui'; +import { Location } from 'history'; import { CoreStart } from '../../../../src/core/public'; import { mountReactNode } from '../../../../src/core/public/utils'; import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/public'; +import { getQueryParams } from '../../../../src/plugins/kibana_utils/public'; import { PLUGIN_ID, @@ -120,7 +123,13 @@ export const SearchExamplesApp = ({ }, [indexPattern]); useEffect(() => { - setSearchSessionId(data.search.session.start()); + const { sessionId } = getQueryParams(window.location as Location); + if (sessionId) { + data.search.session.restore(sessionId as string); + setSearchSessionId(sessionId as string); + } else { + setSearchSessionId(data.search.session.start()); + } }, [data]); const doAsyncSearch = async (strategy?: string) => { @@ -137,7 +146,14 @@ export const SearchExamplesApp = ({ params: { index: indexPattern.title, body: { - aggs: aggsDsl, + aggs: { + ...aggsDsl, + delayed: { + shard_delay: { + value: '5s', + }, + }, + }, query, }, }, @@ -151,6 +167,7 @@ export const SearchExamplesApp = ({ strategy, sessionId: searchSessionId, isStored: data.search.session.isStored(), + isRestore: data.search.session.isRestore(), }) .subscribe({ next: (response) => { @@ -289,16 +306,26 @@ export const SearchExamplesApp = ({ > Generate new search session ID - - data.search.session.save( - `Search example ${searchSessionId}`, - `/app/searchExamples?sessionId=${searchSessionId}` - ) - } - > - Save session - + {data.search.session.isStored() ? ( + data.search.session.isRestore() ? ( + '' + ) : ( + + Visit restored session + + ) + ) : ( + + data.search.session.save( + `Search example ${searchSessionId}`, + `/app/searchExamples?sessionId=${searchSessionId}` + ) + } + > + Save session + + )} diff --git a/src/plugins/data/common/search/session/types.ts b/src/plugins/data/common/search/session/types.ts index c279368411127..4f579525fe7c2 100644 --- a/src/plugins/data/common/search/session/types.ts +++ b/src/plugins/data/common/search/session/types.ts @@ -35,10 +35,15 @@ export interface ISessionService { */ start: () => string; + /** + * Saves a session + */ + save: (name: string, url: string, sessionId?: string) => Promise; + /** * Restores existing session */ - restore: (sessionId: string) => void; + restore: (sessionId: string) => Promise; /** * Clears the active session. @@ -49,6 +54,11 @@ export interface ISessionService { * Whether the active session is already saved (i.e. sent to background) */ isStored: () => boolean; + + /** + * Whether the active session is restored (i.e. reusing previous search IDs) + */ + isRestore: () => boolean; } export interface SearchSessionFindOptions { diff --git a/src/plugins/data/common/search/types.ts b/src/plugins/data/common/search/types.ts index 9bcdb1861e64b..695ee34d3b468 100644 --- a/src/plugins/data/common/search/types.ts +++ b/src/plugins/data/common/search/types.ts @@ -97,4 +97,10 @@ export interface ISearchOptions { * Whether the session is already saved (i.e. sent to background) */ isStored?: boolean; + + /** + * Whether the session is restored (i.e. search requests should re-use the stored search IDs, + * rather than starting from scratch) + */ + isRestore?: boolean; } diff --git a/src/plugins/data/public/search/search_interceptor.ts b/src/plugins/data/public/search/search_interceptor.ts index 45ef97548394e..edee64cfd6862 100644 --- a/src/plugins/data/public/search/search_interceptor.ts +++ b/src/plugins/data/public/search/search_interceptor.ts @@ -137,6 +137,7 @@ export class SearchInterceptor { const body = JSON.stringify({ sessionId: options?.sessionId, isStored: options?.isStored, + isRestore: options?.isRestore, ...searchRequest, }); diff --git a/src/plugins/data/public/search/session_service.ts b/src/plugins/data/public/search/session_service.ts index d13b8af8928b5..abfa44c76e5c1 100644 --- a/src/plugins/data/public/search/session_service.ts +++ b/src/plugins/data/public/search/session_service.ts @@ -31,8 +31,18 @@ export class SessionService implements ISessionService { private appChangeSubscription$?: Subscription; private curApp?: string; private http?: HttpStart; + + /** + * Has the session already been stored (i.e. "sent to background")? + */ private _isStored: boolean = false; + /** + * Is this session a restored session (have these requests already been made, and we're just + * looking to re-use the previous search IDs)? + */ + private _isRestore: boolean = false; + constructor( initializerContext: PluginInitializerContext, getStartServices: StartServicesAccessor @@ -77,19 +87,27 @@ export class SessionService implements ISessionService { return this._isStored; } + public isRestore() { + return this._isRestore; + } + public start() { this._isStored = false; + this._isRestore = false; this.session$.next(uuid.v4()); return this.sessionId!; } public restore(sessionId: string) { + this._isStored = true; + this._isRestore = true; this.session$.next(sessionId); return this.http!.get(`/internal/session/${encodeURIComponent(sessionId)}`); } public clear() { this._isStored = false; + this._isRestore = true; this.session$.next(undefined); } diff --git a/src/plugins/data/server/search/routes/search.ts b/src/plugins/data/server/search/routes/search.ts index 08e80ba40f694..ed519164c8e43 100644 --- a/src/plugins/data/server/search/routes/search.ts +++ b/src/plugins/data/server/search/routes/search.ts @@ -39,13 +39,14 @@ export function registerSearchRoute(router: IRouter): void { { sessionId: schema.maybe(schema.string()), isStored: schema.maybe(schema.boolean()), + isRestore: schema.maybe(schema.boolean()), }, { unknowns: 'allow' } ), }, }, async (context, request, res) => { - const { sessionId, isStored, ...searchRequest } = request.body; + const { sessionId, isStored, isRestore, ...searchRequest } = request.body; const { strategy, id } = request.params; const abortSignal = getRequestAbortedSignal(request.events.aborted$); @@ -58,6 +59,7 @@ export function registerSearchRoute(router: IRouter): void { strategy, sessionId, isStored, + isRestore, } ) .pipe(first()) diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 4c9099366ddb0..6e53c1526d0a5 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, Observable } from 'rxjs'; +import { BehaviorSubject, from, Observable } from 'rxjs'; import { pick } from 'lodash'; import { CoreSetup, @@ -29,7 +29,7 @@ import { SharedGlobalConfig, StartServicesAccessor, } from 'src/core/server'; -import { first } from 'rxjs/operators'; +import { first, switchMap } from 'rxjs/operators'; import { ISearchSetup, ISearchStart, @@ -246,9 +246,18 @@ export class SearchService implements Plugin { options.strategy ); - return strategy.search(searchRequest, options, deps).pipe( + const getSearchRequest = async () => + !options.isRestore || searchRequest.id + ? searchRequest + : { + ...searchRequest, + id: await this.sessionService.getId(searchRequest, options.sessionId, deps), + }; + + return from(getSearchRequest()).pipe( + switchMap((request) => strategy.search(request, options, deps)), tapFirst((response) => { - if (!options.sessionId || !response.id) return; + if (!options.sessionId || !response.id || options.isRestore) return; this.sessionService.trackId(searchRequest, response.id, options, { savedObjectsClient: deps.savedObjectsClient, }); @@ -278,7 +287,6 @@ export class SearchService implements Plugin { private asScopedProvider = ({ elasticsearch, savedObjects, uiSettings }: CoreStart) => { return (request: KibanaRequest): ISearchClient => { - savedObjects.createInternalRepository const savedObjectsClient = savedObjects.getScopedClient(request, { includedHiddenTypes: [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 7e70161d69f96..2ae6daf73c0a3 100644 --- a/src/plugins/data/server/search/session/session_service.ts +++ b/src/plugins/data/server/search/session/session_service.ts @@ -163,6 +163,21 @@ export class BackgroundSessionService { } }; + public getId = async ( + searchRequest: IKibanaSearchRequest, + sessionId: string, + deps: BackgroundSessionDependencies + ) => { + const session = await this.get(sessionId, deps); + const requestHash = createRequestHash(searchRequest.params); + if (!session.attributes.idMapping.hasOwnProperty(requestHash)) { + throw new Error( + `Search request is not associated with session. Are you sure you searched with the same parameters?` + ); + } + return session.attributes.idMapping[requestHash]; + }; + // TODO: When should we call this? Should we call `deleteId` or something instead? public clear = () => { this.sessionSearchMap.clear(); From fda7a79e3ff4cffce80a9a7f2656116476cd20ff Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Tue, 10 Nov 2020 19:33:07 -0700 Subject: [PATCH 16/27] Fix handling of preference and order of params --- src/plugins/data/common/search/session/utils.ts | 3 ++- src/plugins/data/server/search/routes/session.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/plugins/data/common/search/session/utils.ts b/src/plugins/data/common/search/session/utils.ts index 56927d09a64d8..2fe6e91453789 100644 --- a/src/plugins/data/common/search/session/utils.ts +++ b/src/plugins/data/common/search/session/utils.ts @@ -20,5 +20,6 @@ import { createHash } from 'crypto'; export function createRequestHash(keys: Record) { - return createHash(`sha256`).update(JSON.stringify(keys)).digest('hex'); + const { preference, ...params } = keys; + return createHash(`sha256`).update(JSON.stringify(params)).digest('hex'); } diff --git a/src/plugins/data/server/search/routes/session.ts b/src/plugins/data/server/search/routes/session.ts index 70afb31a17036..688cc42c53e00 100644 --- a/src/plugins/data/server/search/routes/session.ts +++ b/src/plugins/data/server/search/routes/session.ts @@ -36,7 +36,7 @@ export function registerSessionRoutes(router: IRouter): void { const { sessionId, name, url } = request.body; try { - const response = await context.search!.session.save(sessionId, name, 'username', url); + const response = await context.search!.session.save(sessionId, name, url, 'username'); return res.ok({ body: response, From c24f83d8be4a74097d728efa5a78fb4e1fd400dc Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Wed, 11 Nov 2020 19:11:50 -0700 Subject: [PATCH 17/27] Trim & cleanup --- .../search_examples/public/components/app.tsx | 62 +-------- .../data/common/search/session/types.ts | 48 +++++-- .../data/common/search/session/utils.ts | 5 + .../data/public/search/session_service.ts | 24 +++- .../saved_objects/background_session.ts | 8 -- .../data/server/search/routes/session.test.ts | 18 +++ .../data/server/search/routes/session.ts | 37 +++++ .../data/server/search/search_service.ts | 5 +- .../search/session/session_service.test.ts | 18 +++ .../server/search/session/session_service.ts | 127 ++++++++---------- .../public/application/angular/discover.js | 9 -- .../application/angular/discover_legacy.html | 5 - .../server/search/es_search_strategy.ts | 2 +- 13 files changed, 197 insertions(+), 171 deletions(-) create mode 100644 src/plugins/data/server/search/routes/session.test.ts create mode 100644 src/plugins/data/server/search/session/session_service.test.ts diff --git a/examples/search_examples/public/components/app.tsx b/examples/search_examples/public/components/app.tsx index 5f7f52a2a4177..2425f3bbad8a9 100644 --- a/examples/search_examples/public/components/app.tsx +++ b/examples/search_examples/public/components/app.tsx @@ -24,7 +24,6 @@ import { BrowserRouter as Router } from 'react-router-dom'; import { EuiButton, - EuiButtonEmpty, EuiPage, EuiPageBody, EuiPageContent, @@ -39,14 +38,11 @@ import { EuiCode, EuiComboBox, EuiFormLabel, - EuiLink, } from '@elastic/eui'; -import { Location } from 'history'; import { CoreStart } from '../../../../src/core/public'; import { mountReactNode } from '../../../../src/core/public/utils'; import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/public'; -import { getQueryParams } from '../../../../src/plugins/kibana_utils/public'; import { PLUGIN_ID, @@ -101,7 +97,6 @@ export const SearchExamplesApp = ({ const [indexPattern, setIndexPattern] = useState(); const [numericFields, setNumericFields] = useState(); const [selectedField, setSelectedField] = useState(); - const [searchSessionId, setSearchSessionId] = useState(); // Fetch the default index pattern using the `data.indexPatterns` service, as the component is mounted. useEffect(() => { @@ -122,16 +117,6 @@ export const SearchExamplesApp = ({ setSelectedField(fields?.length ? fields[0] : null); }, [indexPattern]); - useEffect(() => { - const { sessionId } = getQueryParams(window.location as Location); - if (sessionId) { - data.search.session.restore(sessionId as string); - setSearchSessionId(sessionId as string); - } else { - setSearchSessionId(data.search.session.start()); - } - }, [data]); - const doAsyncSearch = async (strategy?: string) => { if (!indexPattern || !selectedField) return; @@ -146,14 +131,7 @@ export const SearchExamplesApp = ({ params: { index: indexPattern.title, body: { - aggs: { - ...aggsDsl, - delayed: { - shard_delay: { - value: '5s', - }, - }, - }, + aggs: aggsDsl, query, }, }, @@ -165,9 +143,6 @@ export const SearchExamplesApp = ({ const searchSubscription$ = data.search .search(request, { strategy, - sessionId: searchSessionId, - isStored: data.search.session.isStored(), - isRestore: data.search.session.isRestore(), }) .subscribe({ next: (response) => { @@ -293,41 +268,6 @@ export const SearchExamplesApp = ({ values={{ time: timeTook || 'Unknown' }} /> - - -
- setSearchSessionId(data.search.session.start())} - > - Generate new search session ID - - {data.search.session.isStored() ? ( - data.search.session.isRestore() ? ( - '' - ) : ( - - Visit restored session - - ) - ) : ( - - data.search.session.save( - `Search example ${searchSessionId}`, - `/app/searchExamples?sessionId=${searchSessionId}` - ) - } - > - Save session - - )} -
-

diff --git a/src/plugins/data/common/search/session/types.ts b/src/plugins/data/common/search/session/types.ts index 4f579525fe7c2..3c71409b138e7 100644 --- a/src/plugins/data/common/search/session/types.ts +++ b/src/plugins/data/common/search/session/types.ts @@ -30,15 +30,21 @@ export interface ISessionService { * @returns `Observable` */ getSession$: () => Observable; + /** - * Starts a new session + * Whether the active session is already saved (i.e. sent to background) */ - start: () => string; + isStored: () => boolean; /** - * Saves a session + * Whether the active session is restored (i.e. reusing previous search IDs) */ - save: (name: string, url: string, sessionId?: string) => Promise; + isRestore: () => boolean; + + /** + * Starts a new session + */ + start: () => string; /** * Restores existing session @@ -51,14 +57,40 @@ export interface ISessionService { clear: () => void; /** - * Whether the active session is already saved (i.e. sent to background) + * Saves a session */ - isStored: () => boolean; + save: (name: string, url: string) => Promise; /** - * Whether the active session is restored (i.e. reusing previous search IDs) + * Gets a saved session */ - isRestore: () => boolean; + get: (sessionId: string) => Promise; + + /** + * Gets a list of saved sessions + */ + find: (options: SearchSessionFindOptions) => Promise; + + /** + * Updates a session + */ + update: ( + sessionId: string, + attributes: Partial + ) => Promise; + + /** + * Deletes a session + */ + delete: (sessionId: string) => Promise; +} + +export interface BackgroundSessionSavedObjectAttributes { + name: string; + userId: string; + url: string; + expires: string; + idMapping: Record; } export interface SearchSessionFindOptions { diff --git a/src/plugins/data/common/search/session/utils.ts b/src/plugins/data/common/search/session/utils.ts index 2fe6e91453789..c3332f80b6e3f 100644 --- a/src/plugins/data/common/search/session/utils.ts +++ b/src/plugins/data/common/search/session/utils.ts @@ -19,6 +19,11 @@ 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/public/search/session_service.ts b/src/plugins/data/public/search/session_service.ts index abfa44c76e5c1..06a7e6bfcf29e 100644 --- a/src/plugins/data/public/search/session_service.ts +++ b/src/plugins/data/public/search/session_service.ts @@ -21,7 +21,11 @@ import uuid from 'uuid'; import { BehaviorSubject, Subscription } from 'rxjs'; import { HttpStart, PluginInitializerContext, StartServicesAccessor } from 'kibana/public'; import { ConfigSchema } from '../../config'; -import { ISessionService, SearchSessionFindOptions } from '../../common'; +import { + ISessionService, + BackgroundSessionSavedObjectAttributes, + SearchSessionFindOptions, +} from '../../common'; export class SessionService implements ISessionService { private session$ = new BehaviorSubject(undefined); @@ -111,22 +115,18 @@ export class SessionService implements ISessionService { this.session$.next(undefined); } - public async save(name, url, sessionId = this.sessionId) { + public async save(name: string, url: string) { const response = await this.http!.post(`/internal/session`, { body: JSON.stringify({ name, url, - sessionId, + sessionId: this.sessionId, }), }); this._isStored = true; return response; } - public delete(sessionId: string) { - return this.http!.delete(`/internal/session/${encodeURIComponent(sessionId)}}`); - } - public get(sessionId: string) { return this.http!.get(`/internal/session/${encodeURIComponent(sessionId)}}`); } @@ -136,4 +136,14 @@ export class SessionService implements ISessionService { body: JSON.stringify(options), }); } + + public update(sessionId: string, attributes: Partial) { + return this.http!.put(`/internal/session/${encodeURIComponent(sessionId)}}`, { + body: JSON.stringify(attributes), + }); + } + + public delete(sessionId: string) { + return this.http!.delete(`/internal/session/${encodeURIComponent(sessionId)}}`); + } } diff --git a/src/plugins/data/server/saved_objects/background_session.ts b/src/plugins/data/server/saved_objects/background_session.ts index e4c793c9d47d2..ba802fb991421 100644 --- a/src/plugins/data/server/saved_objects/background_session.ts +++ b/src/plugins/data/server/saved_objects/background_session.ts @@ -46,11 +46,3 @@ export const backgroundSessionMapping: SavedObjectsType = { }, }, }; - -export interface BackgroundSessionSavedObjectAttributes { - name: string; - userId: string; - url: string; - expires: string; - idMapping: Record; -} diff --git a/src/plugins/data/server/search/routes/session.test.ts b/src/plugins/data/server/search/routes/session.test.ts new file mode 100644 index 0000000000000..9880b336e76e5 --- /dev/null +++ b/src/plugins/data/server/search/routes/session.test.ts @@ -0,0 +1,18 @@ +/* + * 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. + */ diff --git a/src/plugins/data/server/search/routes/session.ts b/src/plugins/data/server/search/routes/session.ts index 688cc42c53e00..e55fb1ba00646 100644 --- a/src/plugins/data/server/search/routes/session.ts +++ b/src/plugins/data/server/search/routes/session.ts @@ -155,4 +155,41 @@ export function registerSessionRoutes(router: IRouter): void { } } ); + + router.put( + { + path: '/internal/session/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + body: schema.object({ + name: schema.maybe(schema.string()), + url: schema.maybe(schema.string()), + expires: schema.maybe(schema.string()), + }), + }, + }, + async (context, request, res) => { + const { id } = request.params; + const { name, url, expires } = request.body; + try { + const response = await context.search!.session.update(id, { name, url, expires }); + + return res.ok({ + body: response, + }); + } catch (err) { + return res.customError({ + statusCode: err.statusCode || 500, + body: { + message: err.message, + attributes: { + error: err.body?.error || err.message, + }, + }, + }); + } + } + ); } diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 724b20cc5b3e7..b44980164d097 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -259,18 +259,19 @@ 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.sessionId, deps), + id: await this.sessionService.getId(searchRequest, options, deps), }; return from(getSearchRequest()).pipe( switchMap((request) => strategy.search(request, options, deps)), tapFirst((response) => { - if (!options.sessionId || !response.id || options.isRestore) return; + if (searchRequest.id || !options.sessionId || !response.id || options.isRestore) return; this.sessionService.trackId(searchRequest, response.id, options, { savedObjectsClient: deps.savedObjectsClient, }); diff --git a/src/plugins/data/server/search/session/session_service.test.ts b/src/plugins/data/server/search/session/session_service.test.ts new file mode 100644 index 0000000000000..9880b336e76e5 --- /dev/null +++ b/src/plugins/data/server/search/session/session_service.test.ts @@ -0,0 +1,18 @@ +/* + * 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. + */ diff --git a/src/plugins/data/server/search/session/session_service.ts b/src/plugins/data/server/search/session/session_service.ts index 2ae6daf73c0a3..70bebf1b6dcdd 100644 --- a/src/plugins/data/server/search/session/session_service.ts +++ b/src/plugins/data/server/search/session/session_service.ts @@ -17,22 +17,15 @@ * under the License. */ +import { CoreStart, KibanaRequest, SavedObjectsClientContract } from 'kibana/server'; import { - CoreStart, - ISavedObjectsRepository, - KibanaRequest, - SavedObjectsClientContract, -} from 'kibana/server'; -import { + BackgroundSessionSavedObjectAttributes, createRequestHash, IKibanaSearchRequest, ISearchOptions, SearchSessionFindOptions, -} from '../../../common/search'; -import { - BACKGROUND_SESSION_TYPE, - BackgroundSessionSavedObjectAttributes, -} from '../../saved_objects'; +} from '../../../common'; +import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; const DEFAULT_EXPIRATION = 7 * 24 * 60 * 60 * 1000; @@ -51,31 +44,18 @@ export class BackgroundSessionService { */ private sessionSearchMap = new Map>(); - private internalSavedObjectsRepo?: ISavedObjectsRepository; - - private timeout?: NodeJS.Timeout; - constructor() {} public setup = () => {}; public start = (core: CoreStart) => { return { - save: this.save, - trackId: this.trackId, asScoped: this.asScopedProvider(core), }; - - // this.internalSavedObjectsRepo = core.savedObjects.createInternalRepository([ - // BACKGROUND_SESSION_TYPE, - // ]); - // - // this.timeout = setTimeout(this.updateBackgroundSessions, 10000); }; public stop = () => { - this.clear(); - if (this.timeout) clearTimeout(this.timeout); + this.sessionSearchMap.clear(); }; // TODO: Generate the `userId` from the realm type/realm name/username @@ -90,19 +70,21 @@ export class BackgroundSessionService { // Get the mapping of request hash/search ID for this session const searchMap = this.sessionSearchMap.get(sessionId) ?? new Map(); - // Clear out the entries for this session ID so they don't get saved next time - this.sessionSearchMap.delete(sessionId); - const idMapping = Object.fromEntries(searchMap.entries()); const attributes = { name, url, userId, expires: expires.toISOString(), idMapping }; - return savedObjectsClient.create( + const session = 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: Send 404 if the given ID doesn't belong to this user + // 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, @@ -110,7 +92,7 @@ export class BackgroundSessionService { ); }; - // TODO: Send 404 if the given ID doesn't belong to this user + // TODO: Throw an error if this session doesn't belong to this user public find = ( options: SearchSessionFindOptions, { savedObjectsClient }: BackgroundSessionDependencies @@ -121,41 +103,43 @@ export class BackgroundSessionService { }); }; - // TODO: Send 404 if the given ID doesn't belong to this user + // 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 memory. Should only be called directly by the - * search service. - * @param savedObjectsClient - * @param searchRequest - * @param searchId - * @param sessionId - * @param isStored + * 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 = false }: ISearchOptions, - { savedObjectsClient }: BackgroundSessionDependencies + { sessionId, isStored }: ISearchOptions, + deps: BackgroundSessionDependencies ) => { - if (!sessionId) return; + 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. + // Otherwise, just update the in-memory mapping for this session for when the session is saved. if (isStored) { - const attributes = { - idMapping: { [requestHash]: searchId }, - }; - await savedObjectsClient.update( - BACKGROUND_SESSION_TYPE, - sessionId, - attributes - ); + const attributes = { idMapping: { [requestHash]: searchId } }; + await this.update(sessionId, attributes, deps); } else { const map = this.sessionSearchMap.get(sessionId) ?? new Map(); map.set(requestHash, searchId); @@ -163,24 +147,31 @@ export class BackgroundSessionService { } }; + /** + * 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: string, + { 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( - `Search request is not associated with session. Are you sure you searched with the same parameters?` - ); + throw new Error('No search ID in this session matching the given search request'); } - return session.attributes.idMapping[requestHash]; - }; - // TODO: When should we call this? Should we call `deleteId` or something instead? - public clear = () => { - this.sessionSearchMap.clear(); + return session.attributes.idMapping[requestHash]; }; public asScopedProvider = ({ savedObjects }: CoreStart) => { @@ -194,18 +185,14 @@ export class BackgroundSessionService { this.save(sessionId, name, url, userId, expires, 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), + trackId: (searchRequest: IKibanaSearchRequest, searchId: string, options: ISearchOptions) => + this.trackId(searchRequest, searchId, options, deps), + getId: (searchRequest: IKibanaSearchRequest, options: ISearchOptions) => + this.getId(searchRequest, options, deps), }; }; }; - - // private updateBackgroundSessions = async (): Promise => { - // for (const [sessionId, searchMap] of this.sessionSearchMap.entries()) { - // const idMapping = Object.fromEntries(searchMap.entries()); - // await this.internalSavedObjectsRepo!.update(BACKGROUND_SESSION_TYPE, sessionId, { - // idMapping, - // }); - // } - // return (this.timeout = setTimeout(this.updateBackgroundSessions, 10000)); - // }; } diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index 8a549ff015253..9319c58db3e33 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -829,7 +829,6 @@ function discoverController($element, $route, $scope, $timeout, $window, Promise return $scope.searchSource.fetch({ abortSignal: abortController.signal, sessionId: searchSessionId, - isStored: data.search.session.isStored(), }); }) .then(onResults) @@ -844,14 +843,6 @@ function discoverController($element, $route, $scope, $timeout, $window, Promise }); }; - $scope.saveSession = async () => { - await data.search.session.save(); - }; - - $scope.restoreSession = async (id) => { - return data.search.session.restore(id); - }; - $scope.handleRefresh = function (_payload, isUpdate) { if (isUpdate === false) { refetch$.next(); diff --git a/src/plugins/discover/public/application/angular/discover_legacy.html b/src/plugins/discover/public/application/angular/discover_legacy.html index d59b7981835c8..7cdcd6cbbca3a 100644 --- a/src/plugins/discover/public/application/angular/discover_legacy.html +++ b/src/plugins/discover/public/application/angular/discover_legacy.html @@ -1,9 +1,4 @@ -
- - - -
Date: Thu, 12 Nov 2020 12:06:32 -0700 Subject: [PATCH 18/27] Fix types --- src/plugins/data/common/search/session/mocks.ts | 7 +++++++ src/plugins/data/server/saved_objects/index.ts | 6 +----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/plugins/data/common/search/session/mocks.ts b/src/plugins/data/common/search/session/mocks.ts index 2b64bbbd27565..63792475bfd29 100644 --- a/src/plugins/data/common/search/session/mocks.ts +++ b/src/plugins/data/common/search/session/mocks.ts @@ -26,5 +26,12 @@ export function getSessionServiceMock(): jest.Mocked { restore: jest.fn(), getSessionId: jest.fn(), getSession$: jest.fn(), + isStored: jest.fn(), + isRestore: jest.fn(), + save: jest.fn(), + get: jest.fn(), + find: jest.fn(), + update: jest.fn(), + delete: jest.fn(), }; } diff --git a/src/plugins/data/server/saved_objects/index.ts b/src/plugins/data/server/saved_objects/index.ts index fd7942615b4c4..7cd4d319e6417 100644 --- a/src/plugins/data/server/saved_objects/index.ts +++ b/src/plugins/data/server/saved_objects/index.ts @@ -20,8 +20,4 @@ export { querySavedObjectType } from './query'; export { indexPatternSavedObjectType } from './index_patterns'; export { kqlTelemetry } from './kql_telemetry'; export { searchTelemetry } from './search_telemetry'; -export { - BACKGROUND_SESSION_TYPE, - backgroundSessionMapping, - BackgroundSessionSavedObjectAttributes, -} from './background_session'; +export { BACKGROUND_SESSION_TYPE, backgroundSessionMapping } from './background_session'; From 700f2a0e8b83e9e2c80a8626d3aa61cce921abc2 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Thu, 12 Nov 2020 12:11:26 -0700 Subject: [PATCH 19/27] Review feedback --- 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 70bebf1b6dcdd..81359da7fb6d2 100644 --- a/src/plugins/data/server/search/session/session_service.ts +++ b/src/plugins/data/server/search/session/session_service.ts @@ -59,7 +59,7 @@ export class BackgroundSessionService { }; // TODO: Generate the `userId` from the realm type/realm name/username - public save = ( + public save = async ( sessionId: string, name: string, url: string, @@ -72,7 +72,7 @@ export class BackgroundSessionService { const idMapping = Object.fromEntries(searchMap.entries()); const attributes = { name, url, userId, expires: expires.toISOString(), idMapping }; - const session = savedObjectsClient.create( + const session = await savedObjectsClient.create( BACKGROUND_SESSION_TYPE, attributes, { id: sessionId } From 0f34c4cd9b8825636ebc0f3a4b3c1cfdffb554e6 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Thu, 12 Nov 2020 14:41:04 -0700 Subject: [PATCH 20/27] Add tests and remove handling of username --- .../data/common/search/session/types.ts | 1 - .../saved_objects/background_session.ts | 3 - src/plugins/data/server/search/mocks.ts | 21 ++ .../data/server/search/routes/session.test.ts | 103 +++++++++ .../data/server/search/routes/session.ts | 2 +- .../search/session/session_service.test.ts | 205 ++++++++++++++++++ .../server/search/session/session_service.ts | 7 +- 7 files changed, 333 insertions(+), 9 deletions(-) diff --git a/src/plugins/data/common/search/session/types.ts b/src/plugins/data/common/search/session/types.ts index 3c71409b138e7..5b289c78a3a44 100644 --- a/src/plugins/data/common/search/session/types.ts +++ b/src/plugins/data/common/search/session/types.ts @@ -87,7 +87,6 @@ export interface ISessionService { export interface BackgroundSessionSavedObjectAttributes { name: string; - userId: string; url: string; expires: string; idMapping: Record; diff --git a/src/plugins/data/server/saved_objects/background_session.ts b/src/plugins/data/server/saved_objects/background_session.ts index ba802fb991421..0c3e202a37c0b 100644 --- a/src/plugins/data/server/saved_objects/background_session.ts +++ b/src/plugins/data/server/saved_objects/background_session.ts @@ -33,9 +33,6 @@ export const backgroundSessionMapping: SavedObjectsType = { url: { type: 'keyword', }, - userId: { - type: 'keyword', - }, expires: { type: 'date', }, diff --git a/src/plugins/data/server/search/mocks.ts b/src/plugins/data/server/search/mocks.ts index 4914726c85ef8..290e94ee7cf99 100644 --- a/src/plugins/data/server/search/mocks.ts +++ b/src/plugins/data/server/search/mocks.ts @@ -17,6 +17,8 @@ * 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'; @@ -40,3 +42,22 @@ 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: { + save: jest.fn(), + get: jest.fn(), + find: jest.fn(), + update: jest.fn(), + delete: jest.fn(), + trackId: jest.fn(), + getId: jest.fn(), + }, + }, + }; +} diff --git a/src/plugins/data/server/search/routes/session.test.ts b/src/plugins/data/server/search/routes/session.test.ts index 9880b336e76e5..0c111ee5a1f59 100644 --- a/src/plugins/data/server/search/routes/session.test.ts +++ b/src/plugins/data/server/search/routes/session.test.ts @@ -16,3 +16,106 @@ * specific language governing permissions and limitations * under the License. */ + +import type { MockedKeys } from '@kbn/utility-types/jest'; +import type { CoreSetup, RequestHandlerContext } from 'kibana/server'; +import type { DataPluginStart } from '../../plugin'; +import { coreMock, httpServerMock } from '../../../../../../src/core/server/mocks'; +import { createSearchRequestHandlerContext } from '../mocks'; +import { registerSessionRoutes } from './session'; + +describe('registerSessionRoutes', () => { + let mockCoreSetup: MockedKeys>; + let mockContext: jest.Mocked; + + beforeEach(() => { + mockCoreSetup = coreMock.createSetup(); + mockContext = createSearchRequestHandlerContext(); + registerSessionRoutes(mockCoreSetup.http.createRouter()); + }); + + it('save calls session.save with sessionId, name, and url', async () => { + const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const name = 'my saved background search session'; + const url = '/path/to/restored/session'; + const body = { sessionId, name, url }; + + const mockRequest = httpServerMock.createKibanaRequest({ body }); + const mockResponse = httpServerMock.createResponseFactory(); + + const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value; + const [[, saveHandler]] = mockRouter.post.mock.calls; + + saveHandler(mockContext, mockRequest, mockResponse); + + expect(mockContext.search!.session.save).toHaveBeenCalledWith(sessionId, name, url); + }); + + it('get calls session.get with sessionId', async () => { + const id = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const params = { id }; + + const mockRequest = httpServerMock.createKibanaRequest({ params }); + const mockResponse = httpServerMock.createResponseFactory(); + + const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value; + const [[, getHandler]] = mockRouter.get.mock.calls; + + getHandler(mockContext, mockRequest, mockResponse); + + expect(mockContext.search!.session.get).toHaveBeenCalledWith(id); + }); + + it('find calls session.find with options', async () => { + const page = 1; + const perPage = 5; + const sortField = 'my_field'; + const sortOrder = 'desc'; + const filter = 'foo: bar'; + const body = { page, perPage, sortField, sortOrder, filter }; + + const mockRequest = httpServerMock.createKibanaRequest({ body }); + const mockResponse = httpServerMock.createResponseFactory(); + + const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value; + const [, [, findHandler]] = mockRouter.post.mock.calls; + + findHandler(mockContext, mockRequest, mockResponse); + + expect(mockContext.search!.session.find).toHaveBeenCalledWith(body); + }); + + it('update calls session.update with id, name, url, and expires', async () => { + const id = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const name = 'my saved background search session'; + const url = '/path/to/restored/session'; + const expires = new Date().toISOString(); + const params = { id }; + const body = { name, url, expires }; + + const mockRequest = httpServerMock.createKibanaRequest({ params, body }); + const mockResponse = httpServerMock.createResponseFactory(); + + const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value; + const [[, updateHandler]] = mockRouter.put.mock.calls; + + updateHandler(mockContext, mockRequest, mockResponse); + + expect(mockContext.search!.session.update).toHaveBeenCalledWith(id, body); + }); + + it('delete calls session.delete with id', async () => { + const id = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const params = { id }; + + const mockRequest = httpServerMock.createKibanaRequest({ params }); + const mockResponse = httpServerMock.createResponseFactory(); + + const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value; + const [[, deleteHandler]] = mockRouter.delete.mock.calls; + + deleteHandler(mockContext, mockRequest, mockResponse); + + expect(mockContext.search!.session.delete).toHaveBeenCalledWith(id); + }); +}); diff --git a/src/plugins/data/server/search/routes/session.ts b/src/plugins/data/server/search/routes/session.ts index e55fb1ba00646..a5754c00851ba 100644 --- a/src/plugins/data/server/search/routes/session.ts +++ b/src/plugins/data/server/search/routes/session.ts @@ -36,7 +36,7 @@ export function registerSessionRoutes(router: IRouter): void { const { sessionId, name, url } = request.body; try { - const response = await context.search!.session.save(sessionId, name, url, 'username'); + const response = await context.search!.session.save(sessionId, name, url); return res.ok({ body: response, 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 9880b336e76e5..89f846a3edf2c 100644 --- a/src/plugins/data/server/search/session/session_service.test.ts +++ b/src/plugins/data/server/search/session/session_service.test.ts @@ -16,3 +16,208 @@ * specific language governing permissions and limitations * under the License. */ + +import type { SavedObject, SavedObjectsClientContract } from 'kibana/server'; +import { savedObjectsClientMock } from '../../../../../core/server/mocks'; +import { createRequestHash } from '../../../common'; +import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; +import { BackgroundSessionService } from './session_service'; + +describe('BackgroundSessionService', () => { + let savedObjectsClient: jest.Mocked; + let service: BackgroundSessionService; + + const mockSavedObject: SavedObject = { + id: 'd7170a35-7e2c-48d6-8dec-9a056721b489', + type: BACKGROUND_SESSION_TYPE, + attributes: { + name: 'my_name', + url: 'my_url', + idMapping: {}, + }, + references: [], + }; + + beforeEach(() => { + savedObjectsClient = savedObjectsClientMock.create(); + service = new 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); + 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 sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + 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 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('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 url = '/path/to/restored/session'; + const expires = new Date(); + + await service.trackId( + searchRequest, + searchId, + { sessionId, isStored }, + { savedObjectsClient } + ); + + expect(savedObjectsClient.update).not.toHaveBeenCalled(); + + await service.save(sessionId, name, url, expires, { savedObjectsClient }); + + expect(savedObjectsClient.create).toHaveBeenCalledWith( + BACKGROUND_SESSION_TYPE, + { + name, + url, + expires: expires.toISOString(), + idMapping: { [requestHash]: searchId }, + }, + { id: sessionId } + ); + }); + + it('updates saved object when `isStored` is `true`', async () => { + const searchRequest = { params: {} }; + const requestHash = createRequestHash(searchRequest.params); + const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; + const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + 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: {} }; + const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + + 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: {} }; + const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + + 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 sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const mockSession = { + id: 'd7170a35-7e2c-48d6-8dec-9a056721b489', + type: BACKGROUND_SESSION_TYPE, + attributes: { + name: 'my_name', + url: 'my_url', + 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/src/plugins/data/server/search/session/session_service.ts b/src/plugins/data/server/search/session/session_service.ts index 81359da7fb6d2..4365a9f0fe6c0 100644 --- a/src/plugins/data/server/search/session/session_service.ts +++ b/src/plugins/data/server/search/session/session_service.ts @@ -63,7 +63,6 @@ export class BackgroundSessionService { sessionId: string, name: string, url: string, - userId: string, expires: Date = new Date(Date.now() + DEFAULT_EXPIRATION), { savedObjectsClient }: BackgroundSessionDependencies ) => { @@ -71,7 +70,7 @@ export class BackgroundSessionService { const searchMap = this.sessionSearchMap.get(sessionId) ?? new Map(); const idMapping = Object.fromEntries(searchMap.entries()); - const attributes = { name, url, userId, expires: expires.toISOString(), idMapping }; + const attributes = { name, url, expires: expires.toISOString(), idMapping }; const session = await savedObjectsClient.create( BACKGROUND_SESSION_TYPE, attributes, @@ -181,8 +180,8 @@ export class BackgroundSessionService { }); const deps = { savedObjectsClient }; return { - save: (sessionId: string, name: string, url: string, userId: string, expires?: Date) => - this.save(sessionId, name, url, userId, expires, deps), + save: (sessionId: string, name: string, url: string, expires?: Date) => + this.save(sessionId, name, url, expires, deps), get: (sessionId: string) => this.get(sessionId, deps), find: (options: SearchSessionFindOptions) => this.find(options, deps), update: (sessionId: string, attributes: Partial) => From c4e63c20f2cce430165d85585d8a6be63778abe0 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Thu, 12 Nov 2020 14:49:22 -0700 Subject: [PATCH 21/27] Update docs --- ...lugins-data-public.isearchoptions.isrestore.md | 13 +++++++++++++ ...plugins-data-public.isearchoptions.isstored.md | 13 +++++++++++++ ...a-plugin-plugins-data-public.isearchoptions.md | 2 ++ ...-plugins-data-public.isessionservice.delete.md | 13 +++++++++++++ ...in-plugins-data-public.isessionservice.find.md | 13 +++++++++++++ ...gin-plugins-data-public.isessionservice.get.md | 13 +++++++++++++ ...ugins-data-public.isessionservice.isrestore.md | 13 +++++++++++++ ...lugins-data-public.isessionservice.isstored.md | 13 +++++++++++++ ...-plugin-plugins-data-public.isessionservice.md | 9 ++++++++- ...plugins-data-public.isessionservice.restore.md | 2 +- ...in-plugins-data-public.isessionservice.save.md | 13 +++++++++++++ ...-plugins-data-public.isessionservice.update.md | 13 +++++++++++++ ...lugins-data-server.isearchoptions.isrestore.md | 13 +++++++++++++ ...plugins-data-server.isearchoptions.isstored.md | 13 +++++++++++++ ...a-plugin-plugins-data-server.isearchoptions.md | 2 ++ src/plugins/data/public/public.api.md | 15 +++++++++++++-- src/plugins/data/server/server.api.md | 2 ++ 17 files changed, 171 insertions(+), 4 deletions(-) create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.isrestore.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.isstored.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.delete.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.find.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.get.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.isrestore.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.isstored.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.save.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.update.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.isrestore.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.isstored.md diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.isrestore.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.isrestore.md new file mode 100644 index 0000000000000..672d77719962f --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.isrestore.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISearchOptions](./kibana-plugin-plugins-data-public.isearchoptions.md) > [isRestore](./kibana-plugin-plugins-data-public.isearchoptions.isrestore.md) + +## ISearchOptions.isRestore property + +Whether the session is restored (i.e. search requests should re-use the stored search IDs, rather than starting from scratch) + +Signature: + +```typescript +isRestore?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.isstored.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.isstored.md new file mode 100644 index 0000000000000..0d2c173f351c8 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.isstored.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISearchOptions](./kibana-plugin-plugins-data-public.isearchoptions.md) > [isStored](./kibana-plugin-plugins-data-public.isearchoptions.isstored.md) + +## ISearchOptions.isStored property + +Whether the session is already saved (i.e. sent to background) + +Signature: + +```typescript +isStored?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md index 76d0914173447..5acd837495dac 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md @@ -15,6 +15,8 @@ export interface ISearchOptions | Property | Type | Description | | --- | --- | --- | | [abortSignal](./kibana-plugin-plugins-data-public.isearchoptions.abortsignal.md) | AbortSignal | An AbortSignal that allows the caller of search to abort a search request. | +| [isRestore](./kibana-plugin-plugins-data-public.isearchoptions.isrestore.md) | boolean | Whether the session is restored (i.e. search requests should re-use the stored search IDs, rather than starting from scratch) | +| [isStored](./kibana-plugin-plugins-data-public.isearchoptions.isstored.md) | boolean | Whether the session is already saved (i.e. sent to background) | | [sessionId](./kibana-plugin-plugins-data-public.isearchoptions.sessionid.md) | string | A session ID, grouping multiple search requests into a single session. | | [strategy](./kibana-plugin-plugins-data-public.isearchoptions.strategy.md) | string | Use this option to force using a specific server side search strategy. Leave empty to use the default strategy. | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.delete.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.delete.md new file mode 100644 index 0000000000000..a04e44778a9ee --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.delete.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [delete](./kibana-plugin-plugins-data-public.isessionservice.delete.md) + +## ISessionService.delete property + +Deletes a session + +Signature: + +```typescript +delete: (sessionId: string) => Promise; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.find.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.find.md new file mode 100644 index 0000000000000..dd5f5237554aa --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.find.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [find](./kibana-plugin-plugins-data-public.isessionservice.find.md) + +## ISessionService.find property + +Gets a list of saved sessions + +Signature: + +```typescript +find: (options: SearchSessionFindOptions) => Promise; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.get.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.get.md new file mode 100644 index 0000000000000..4e1ec3d431b9a --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.get.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [get](./kibana-plugin-plugins-data-public.isessionservice.get.md) + +## ISessionService.get property + +Gets a saved session + +Signature: + +```typescript +get: (sessionId: string) => Promise; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.isrestore.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.isrestore.md new file mode 100644 index 0000000000000..8d8cd1f31bb95 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.isrestore.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [isRestore](./kibana-plugin-plugins-data-public.isessionservice.isrestore.md) + +## ISessionService.isRestore property + +Whether the active session is restored (i.e. reusing previous search IDs) + +Signature: + +```typescript +isRestore: () => boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.isstored.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.isstored.md new file mode 100644 index 0000000000000..db737880bb84e --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.isstored.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [isStored](./kibana-plugin-plugins-data-public.isessionservice.isstored.md) + +## ISessionService.isStored property + +Whether the active session is already saved (i.e. sent to background) + +Signature: + +```typescript +isStored: () => boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md index 174f9dbe66bf4..2a6e0cfa09bc2 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md @@ -15,8 +15,15 @@ export interface ISessionService | Property | Type | Description | | --- | --- | --- | | [clear](./kibana-plugin-plugins-data-public.isessionservice.clear.md) | () => void | Clears the active session. | +| [delete](./kibana-plugin-plugins-data-public.isessionservice.delete.md) | (sessionId: string) => Promise<any> | Deletes a session | +| [find](./kibana-plugin-plugins-data-public.isessionservice.find.md) | (options: SearchSessionFindOptions) => Promise<any> | Gets a list of saved sessions | +| [get](./kibana-plugin-plugins-data-public.isessionservice.get.md) | (sessionId: string) => Promise<any> | Gets a saved session | | [getSession$](./kibana-plugin-plugins-data-public.isessionservice.getsession_.md) | () => Observable<string | undefined> | Returns the observable that emits an update every time the session ID changes | | [getSessionId](./kibana-plugin-plugins-data-public.isessionservice.getsessionid.md) | () => string | undefined | Returns the active session ID | -| [restore](./kibana-plugin-plugins-data-public.isessionservice.restore.md) | (sessionId: string) => void | Restores existing session | +| [isRestore](./kibana-plugin-plugins-data-public.isessionservice.isrestore.md) | () => boolean | Whether the active session is restored (i.e. reusing previous search IDs) | +| [isStored](./kibana-plugin-plugins-data-public.isessionservice.isstored.md) | () => boolean | Whether the active session is already saved (i.e. sent to background) | +| [restore](./kibana-plugin-plugins-data-public.isessionservice.restore.md) | (sessionId: string) => Promise<any> | Restores existing session | +| [save](./kibana-plugin-plugins-data-public.isessionservice.save.md) | (name: string, url: string) => Promise<any> | Saves a session | | [start](./kibana-plugin-plugins-data-public.isessionservice.start.md) | () => string | Starts a new session | +| [update](./kibana-plugin-plugins-data-public.isessionservice.update.md) | (sessionId: string, attributes: Partial<BackgroundSessionSavedObjectAttributes>) => Promise<any> | Updates a session | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.restore.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.restore.md index 857e85bbd30eb..b182e09655738 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.restore.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.restore.md @@ -9,5 +9,5 @@ Restores existing session Signature: ```typescript -restore: (sessionId: string) => void; +restore: (sessionId: string) => Promise; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.save.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.save.md new file mode 100644 index 0000000000000..ba48c8aec220e --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.save.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [save](./kibana-plugin-plugins-data-public.isessionservice.save.md) + +## ISessionService.save property + +Saves a session + +Signature: + +```typescript +save: (name: string, url: string) => Promise; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.update.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.update.md new file mode 100644 index 0000000000000..5e2ff53d58ab7 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.update.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [update](./kibana-plugin-plugins-data-public.isessionservice.update.md) + +## ISessionService.update property + +Updates a session + +Signature: + +```typescript +update: (sessionId: string, attributes: Partial) => Promise; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.isrestore.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.isrestore.md new file mode 100644 index 0000000000000..ae518e5a052fc --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.isrestore.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchOptions](./kibana-plugin-plugins-data-server.isearchoptions.md) > [isRestore](./kibana-plugin-plugins-data-server.isearchoptions.isrestore.md) + +## ISearchOptions.isRestore property + +Whether the session is restored (i.e. search requests should re-use the stored search IDs, rather than starting from scratch) + +Signature: + +```typescript +isRestore?: boolean; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.isstored.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.isstored.md new file mode 100644 index 0000000000000..aceee7fd6df68 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.isstored.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchOptions](./kibana-plugin-plugins-data-server.isearchoptions.md) > [isStored](./kibana-plugin-plugins-data-server.isearchoptions.isstored.md) + +## ISearchOptions.isStored property + +Whether the session is already saved (i.e. sent to background) + +Signature: + +```typescript +isStored?: boolean; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md index af96e1413ba0c..85847e1c61d25 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md @@ -15,6 +15,8 @@ export interface ISearchOptions | Property | Type | Description | | --- | --- | --- | | [abortSignal](./kibana-plugin-plugins-data-server.isearchoptions.abortsignal.md) | AbortSignal | An AbortSignal that allows the caller of search to abort a search request. | +| [isRestore](./kibana-plugin-plugins-data-server.isearchoptions.isrestore.md) | boolean | Whether the session is restored (i.e. search requests should re-use the stored search IDs, rather than starting from scratch) | +| [isStored](./kibana-plugin-plugins-data-server.isearchoptions.isstored.md) | boolean | Whether the session is already saved (i.e. sent to background) | | [sessionId](./kibana-plugin-plugins-data-server.isearchoptions.sessionid.md) | string | A session ID, grouping multiple search requests into a single session. | | [strategy](./kibana-plugin-plugins-data-server.isearchoptions.strategy.md) | string | Use this option to force using a specific server side search strategy. Leave empty to use the default strategy. | diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 78b974758f8c0..017b8aab71682 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -1423,6 +1423,8 @@ export type ISearchGeneric = | undefined // @public (undocumented) export interface ISessionService { clear: () => void; + delete: (sessionId: string) => Promise; + // Warning: (ae-forgotten-export) The symbol "SearchSessionFindOptions" needs to be exported by the entry point index.d.ts + find: (options: SearchSessionFindOptions) => Promise; + get: (sessionId: string) => Promise; getSession$: () => Observable; getSessionId: () => string | undefined; - restore: (sessionId: string) => void; + isRestore: () => boolean; + isStored: () => boolean; + restore: (sessionId: string) => Promise; + save: (name: string, url: string) => Promise; start: () => string; + // Warning: (ae-forgotten-export) The symbol "BackgroundSessionSavedObjectAttributes" needs to be exported by the entry point index.d.ts + update: (sessionId: string, attributes: Partial) => Promise; } // Warning: (ae-missing-release-tag) "isFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -2046,7 +2057,7 @@ export class SearchInterceptor { // @internal protected pendingCount$: BehaviorSubject; // @internal (undocumented) - protected runSearch(request: IKibanaSearchRequest, signal: AbortSignal, strategy?: string): Promise; + protected runSearch(request: IKibanaSearchRequest, options?: ISearchOptions): Promise; search(request: IKibanaSearchRequest, options?: ISearchOptions): Observable; // @internal (undocumented) protected setupAbortSignal({ abortSignal, timeout, }: { diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 2984ca336819a..a40d1e295b10e 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -736,6 +736,8 @@ export class IndexPatternsService implements Plugin_3 Date: Mon, 16 Nov 2020 16:41:22 -0700 Subject: [PATCH 22/27] Move utils to server --- .../data/common/search/session/index.ts | 1 - .../search/session/session_service.test.ts | 2 +- .../server/search/session/session_service.ts | 2 +- .../data/server/search/session/utils.test.ts | 37 +++++++++++++++++++ .../search/session/utils.ts | 0 5 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 src/plugins/data/server/search/session/utils.test.ts rename src/plugins/data/{common => server}/search/session/utils.ts (100%) diff --git a/src/plugins/data/common/search/session/index.ts b/src/plugins/data/common/search/session/index.ts index 8e8897c7d7517..d8f7b5091eb8f 100644 --- a/src/plugins/data/common/search/session/index.ts +++ b/src/plugins/data/common/search/session/index.ts @@ -18,4 +18,3 @@ */ export * from './types'; -export * from './utils'; 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 89f846a3edf2c..883a3b2a574a8 100644 --- a/src/plugins/data/server/search/session/session_service.test.ts +++ b/src/plugins/data/server/search/session/session_service.test.ts @@ -19,9 +19,9 @@ import type { SavedObject, SavedObjectsClientContract } from 'kibana/server'; import { savedObjectsClientMock } from '../../../../../core/server/mocks'; -import { createRequestHash } from '../../../common'; import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; import { BackgroundSessionService } from './session_service'; +import { createRequestHash } from './utils'; describe('BackgroundSessionService', () => { let savedObjectsClient: jest.Mocked; diff --git a/src/plugins/data/server/search/session/session_service.ts b/src/plugins/data/server/search/session/session_service.ts index 4365a9f0fe6c0..633137d396288 100644 --- a/src/plugins/data/server/search/session/session_service.ts +++ b/src/plugins/data/server/search/session/session_service.ts @@ -20,12 +20,12 @@ import { CoreStart, KibanaRequest, SavedObjectsClientContract } from 'kibana/server'; import { BackgroundSessionSavedObjectAttributes, - createRequestHash, IKibanaSearchRequest, ISearchOptions, SearchSessionFindOptions, } from '../../../common'; import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; +import { createRequestHash } from './utils'; const DEFAULT_EXPIRATION = 7 * 24 * 60 * 60 * 1000; diff --git a/src/plugins/data/server/search/session/utils.test.ts b/src/plugins/data/server/search/session/utils.test.ts new file mode 100644 index 0000000000000..d190f892a7f84 --- /dev/null +++ b/src/plugins/data/server/search/session/utils.test.ts @@ -0,0 +1,37 @@ +/* + * 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/common/search/session/utils.ts b/src/plugins/data/server/search/session/utils.ts similarity index 100% rename from src/plugins/data/common/search/session/utils.ts rename to src/plugins/data/server/search/session/utils.ts From 967214da7b941660613625521d0f065a2ef02f53 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Tue, 17 Nov 2020 21:01:19 -0700 Subject: [PATCH 23/27] Review feedback --- .../data/common/search/session/index.ts | 1 + .../data/common/search/session/status.ts | 26 +++++++++++++++++++ .../data/common/search/session/types.ts | 13 +++++++--- .../data/public/search/session_service.ts | 16 ++++++------ .../saved_objects/background_session.ts | 6 +++++ .../server/search/session/session_service.ts | 10 ++++++- .../server/search/es_search_strategy.ts | 2 +- 7 files changed, 60 insertions(+), 14 deletions(-) create mode 100644 src/plugins/data/common/search/session/status.ts diff --git a/src/plugins/data/common/search/session/index.ts b/src/plugins/data/common/search/session/index.ts index d8f7b5091eb8f..0feb43f8f1d4b 100644 --- a/src/plugins/data/common/search/session/index.ts +++ b/src/plugins/data/common/search/session/index.ts @@ -17,4 +17,5 @@ * 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 new file mode 100644 index 0000000000000..4965427873470 --- /dev/null +++ b/src/plugins/data/common/search/session/status.ts @@ -0,0 +1,26 @@ +/* + * 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 { + INCOMPLETE = 'incomplete', + ERROR = 'error', + COMPLETE = 'complete', + CANCELLED = 'cancelled', + EXPIRED = 'expired', +} diff --git a/src/plugins/data/common/search/session/types.ts b/src/plugins/data/common/search/session/types.ts index 5b289c78a3a44..d18b810dd6013 100644 --- a/src/plugins/data/common/search/session/types.ts +++ b/src/plugins/data/common/search/session/types.ts @@ -18,6 +18,7 @@ */ import { Observable } from 'rxjs'; +import type { SavedObject, SavedObjectsFindResponse } from 'kibana/server'; export interface ISessionService { /** @@ -49,7 +50,7 @@ export interface ISessionService { /** * Restores existing session */ - restore: (sessionId: string) => Promise; + restore: (sessionId: string) => Promise>; /** * Clears the active session. @@ -59,17 +60,19 @@ export interface ISessionService { /** * Saves a session */ - save: (name: string, url: string) => Promise; + save: (name: string, url: string) => Promise>; /** * Gets a saved session */ - get: (sessionId: string) => Promise; + get: (sessionId: string) => Promise>; /** * Gets a list of saved sessions */ - find: (options: SearchSessionFindOptions) => Promise; + find: ( + options: SearchSessionFindOptions + ) => Promise>; /** * Updates a session @@ -88,7 +91,9 @@ export interface ISessionService { export interface BackgroundSessionSavedObjectAttributes { name: string; url: string; + created: string; expires: string; + status: string; idMapping: Record; } diff --git a/src/plugins/data/public/search/session_service.ts b/src/plugins/data/public/search/session_service.ts index 06a7e6bfcf29e..0141cff258a9f 100644 --- a/src/plugins/data/public/search/session_service.ts +++ b/src/plugins/data/public/search/session_service.ts @@ -34,7 +34,7 @@ export class SessionService implements ISessionService { } private appChangeSubscription$?: Subscription; private curApp?: string; - private http?: HttpStart; + private http!: HttpStart; /** * Has the session already been stored (i.e. "sent to background")? @@ -106,17 +106,17 @@ export class SessionService implements ISessionService { this._isStored = true; this._isRestore = true; this.session$.next(sessionId); - return this.http!.get(`/internal/session/${encodeURIComponent(sessionId)}`); + return this.http.get(`/internal/session/${encodeURIComponent(sessionId)}`); } public clear() { this._isStored = false; - this._isRestore = true; + this._isRestore = false; this.session$.next(undefined); } public async save(name: string, url: string) { - const response = await this.http!.post(`/internal/session`, { + const response = await this.http.post(`/internal/session`, { body: JSON.stringify({ name, url, @@ -128,22 +128,22 @@ export class SessionService implements ISessionService { } public get(sessionId: string) { - return this.http!.get(`/internal/session/${encodeURIComponent(sessionId)}}`); + return this.http.get(`/internal/session/${encodeURIComponent(sessionId)}`); } public find(options: SearchSessionFindOptions) { - return this.http!.post(`/internal/session`, { + return this.http.post(`/internal/session`, { body: JSON.stringify(options), }); } public update(sessionId: string, attributes: Partial) { - return this.http!.put(`/internal/session/${encodeURIComponent(sessionId)}}`, { + return this.http.put(`/internal/session/${encodeURIComponent(sessionId)}`, { body: JSON.stringify(attributes), }); } public delete(sessionId: string) { - return this.http!.delete(`/internal/session/${encodeURIComponent(sessionId)}}`); + return this.http.delete(`/internal/session/${encodeURIComponent(sessionId)}`); } } diff --git a/src/plugins/data/server/saved_objects/background_session.ts b/src/plugins/data/server/saved_objects/background_session.ts index 0c3e202a37c0b..ca38ebb42711a 100644 --- a/src/plugins/data/server/saved_objects/background_session.ts +++ b/src/plugins/data/server/saved_objects/background_session.ts @@ -33,9 +33,15 @@ export const backgroundSessionMapping: SavedObjectsType = { url: { type: 'keyword', }, + created: { + type: 'date', + }, expires: { type: 'date', }, + status: { + type: 'keyword', + }, idMapping: { type: 'object', enabled: false, diff --git a/src/plugins/data/server/search/session/session_service.ts b/src/plugins/data/server/search/session/session_service.ts index 633137d396288..ffe43381307d7 100644 --- a/src/plugins/data/server/search/session/session_service.ts +++ b/src/plugins/data/server/search/session/session_service.ts @@ -23,6 +23,7 @@ import { IKibanaSearchRequest, ISearchOptions, SearchSessionFindOptions, + BackgroundSessionStatus, } from '../../../common'; import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; import { createRequestHash } from './utils'; @@ -70,7 +71,14 @@ export class BackgroundSessionService { const searchMap = this.sessionSearchMap.get(sessionId) ?? new Map(); const idMapping = Object.fromEntries(searchMap.entries()); - const attributes = { name, url, expires: expires.toISOString(), idMapping }; + const attributes = { + name, + url, + created: new Date().toISOString(), + expires: expires.toISOString(), + status: BackgroundSessionStatus.INCOMPLETE, + idMapping, + }; const session = await savedObjectsClient.create( BACKGROUND_SESSION_TYPE, attributes, diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts index 32331c3bc10f0..2070610ceb20e 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts @@ -57,7 +57,7 @@ export const enhancedEsSearchStrategyProvider = ( utils.toSnakeCase({ ...(await getDefaultSearchParams(uiSettingsClient)), batchedReduceSize: 64, - keepOnCompletion: true, // Always return an ID, even if the request completes quickly + keepOnCompletion: !!options.sessionId, // Always return an ID, even if the request completes quickly ...asyncOptions, ...request.params, }) From dc6104cb10a949f41c697e08f7df403bf9a03b68 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Wed, 18 Nov 2020 13:24:29 -0700 Subject: [PATCH 24/27] More review feedback --- .../data/common/search/session/types.ts | 3 ++- .../saved_objects/background_session.ts | 11 +++++--- .../data/server/search/routes/session.test.ts | 12 ++++----- .../data/server/search/routes/session.ts | 18 ++++++++----- .../search/session/session_service.test.ts | 24 ++++++++++++----- .../server/search/session/session_service.ts | 27 +++++++++---------- 6 files changed, 57 insertions(+), 38 deletions(-) diff --git a/src/plugins/data/common/search/session/types.ts b/src/plugins/data/common/search/session/types.ts index d18b810dd6013..81c3923719838 100644 --- a/src/plugins/data/common/search/session/types.ts +++ b/src/plugins/data/common/search/session/types.ts @@ -90,10 +90,11 @@ export interface ISessionService { export interface BackgroundSessionSavedObjectAttributes { name: string; - url: string; created: string; expires: string; status: string; + initialState: Record; + restoreState: Record; idMapping: Record; } diff --git a/src/plugins/data/server/saved_objects/background_session.ts b/src/plugins/data/server/saved_objects/background_session.ts index ca38ebb42711a..74b03c4d867e4 100644 --- a/src/plugins/data/server/saved_objects/background_session.ts +++ b/src/plugins/data/server/saved_objects/background_session.ts @@ -30,9 +30,6 @@ export const backgroundSessionMapping: SavedObjectsType = { name: { type: 'keyword', }, - url: { - type: 'keyword', - }, created: { type: 'date', }, @@ -42,6 +39,14 @@ export const backgroundSessionMapping: SavedObjectsType = { status: { type: 'keyword', }, + initialState: { + type: 'object', + enabled: false, + }, + restoreState: { + type: 'object', + enabled: false, + }, idMapping: { type: 'object', enabled: false, diff --git a/src/plugins/data/server/search/routes/session.test.ts b/src/plugins/data/server/search/routes/session.test.ts index 0c111ee5a1f59..f697f6d5a5c2b 100644 --- a/src/plugins/data/server/search/routes/session.test.ts +++ b/src/plugins/data/server/search/routes/session.test.ts @@ -34,11 +34,10 @@ describe('registerSessionRoutes', () => { registerSessionRoutes(mockCoreSetup.http.createRouter()); }); - it('save calls session.save with sessionId, name, and url', async () => { + it('save calls session.save with sessionId and attributes', async () => { const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; const name = 'my saved background search session'; - const url = '/path/to/restored/session'; - const body = { sessionId, name, url }; + const body = { sessionId, name }; const mockRequest = httpServerMock.createKibanaRequest({ body }); const mockResponse = httpServerMock.createResponseFactory(); @@ -48,7 +47,7 @@ describe('registerSessionRoutes', () => { saveHandler(mockContext, mockRequest, mockResponse); - expect(mockContext.search!.session.save).toHaveBeenCalledWith(sessionId, name, url); + expect(mockContext.search!.session.save).toHaveBeenCalledWith(sessionId, { name }); }); it('get calls session.get with sessionId', async () => { @@ -85,13 +84,12 @@ describe('registerSessionRoutes', () => { expect(mockContext.search!.session.find).toHaveBeenCalledWith(body); }); - it('update calls session.update with id, name, url, and expires', async () => { + it('update calls session.update with id and attributes', async () => { const id = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; const name = 'my saved background search session'; - const url = '/path/to/restored/session'; const expires = new Date().toISOString(); const params = { id }; - const body = { name, url, expires }; + const body = { name, expires }; const mockRequest = httpServerMock.createKibanaRequest({ params, body }); const mockResponse = httpServerMock.createResponseFactory(); diff --git a/src/plugins/data/server/search/routes/session.ts b/src/plugins/data/server/search/routes/session.ts index a5754c00851ba..93f07ecfb92ff 100644 --- a/src/plugins/data/server/search/routes/session.ts +++ b/src/plugins/data/server/search/routes/session.ts @@ -28,15 +28,22 @@ export function registerSessionRoutes(router: IRouter): void { body: schema.object({ sessionId: schema.string(), name: schema.string(), - url: schema.string(), + expires: schema.maybe(schema.string()), + initialState: schema.maybe(schema.object({}, { unknowns: 'allow' })), + restoreState: schema.maybe(schema.object({}, { unknowns: 'allow' })), }), }, }, async (context, request, res) => { - const { sessionId, name, url } = request.body; + const { sessionId, name, expires, initialState, restoreState } = request.body; try { - const response = await context.search!.session.save(sessionId, name, url); + const response = await context.search!.session.save(sessionId, { + name, + expires, + initialState, + restoreState, + }); return res.ok({ body: response, @@ -165,16 +172,15 @@ export function registerSessionRoutes(router: IRouter): void { }), body: schema.object({ name: schema.maybe(schema.string()), - url: schema.maybe(schema.string()), expires: schema.maybe(schema.string()), }), }, }, async (context, request, res) => { const { id } = request.params; - const { name, url, expires } = request.body; + const { name, expires } = request.body; try { - const response = await context.search!.session.update(id, { name, url, expires }); + const response = await context.search!.session.update(id, { name, expires }); return res.ok({ body: response, 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 883a3b2a574a8..f7f62f73d10a6 100644 --- a/src/plugins/data/server/search/session/session_service.test.ts +++ b/src/plugins/data/server/search/session/session_service.test.ts @@ -19,6 +19,7 @@ import type { SavedObject, SavedObjectsClientContract } from 'kibana/server'; 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'; @@ -32,7 +33,6 @@ describe('BackgroundSessionService', () => { type: BACKGROUND_SESSION_TYPE, attributes: { name: 'my_name', - url: 'my_url', idMapping: {}, }, references: [], @@ -43,6 +43,14 @@ describe('BackgroundSessionService', () => { service = new BackgroundSessionService(); }); + it('save throws if `name` is not provided', () => { + const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + + expect(() => service.save(sessionId, {}, { savedObjectsClient })).rejects.toMatchInlineSnapshot( + `[Error: Name is required]` + ); + }); + it('get calls saved objects client', async () => { savedObjectsClient.get.mockResolvedValue(mockSavedObject); @@ -113,8 +121,8 @@ describe('BackgroundSessionService', () => { const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; const isStored = false; const name = 'my saved background search session'; - const url = '/path/to/restored/session'; - const expires = new Date(); + const created = new Date().toISOString(); + const expires = new Date().toISOString(); await service.trackId( searchRequest, @@ -125,14 +133,17 @@ describe('BackgroundSessionService', () => { expect(savedObjectsClient.update).not.toHaveBeenCalled(); - await service.save(sessionId, name, url, expires, { savedObjectsClient }); + await service.save(sessionId, { name, created, expires }, { savedObjectsClient }); expect(savedObjectsClient.create).toHaveBeenCalledWith( BACKGROUND_SESSION_TYPE, { name, - url, - expires: expires.toISOString(), + created, + expires, + initialState: {}, + restoreState: {}, + status: BackgroundSessionStatus.INCOMPLETE, idMapping: { [requestHash]: searchId }, }, { id: sessionId } @@ -204,7 +215,6 @@ describe('BackgroundSessionService', () => { type: BACKGROUND_SESSION_TYPE, attributes: { name: 'my_name', - url: 'my_url', idMapping: { [requestHash]: searchId }, }, references: [], diff --git a/src/plugins/data/server/search/session/session_service.ts b/src/plugins/data/server/search/session/session_service.ts index ffe43381307d7..a109ebb91a09d 100644 --- a/src/plugins/data/server/search/session/session_service.ts +++ b/src/plugins/data/server/search/session/session_service.ts @@ -62,23 +62,22 @@ export class BackgroundSessionService { // TODO: Generate the `userId` from the realm type/realm name/username public save = async ( sessionId: string, - name: string, - url: string, - expires: Date = new Date(Date.now() + DEFAULT_EXPIRATION), + { + name, + created = new Date().toISOString(), + expires = new Date(Date.now() + DEFAULT_EXPIRATION).toISOString(), + status = BackgroundSessionStatus.INCOMPLETE, + initialState = {}, + restoreState = {}, + }: Partial, { savedObjectsClient }: BackgroundSessionDependencies ) => { + 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 attributes = { - name, - url, - created: new Date().toISOString(), - expires: expires.toISOString(), - status: BackgroundSessionStatus.INCOMPLETE, - idMapping, - }; + const attributes = { name, created, expires, status, initialState, restoreState, idMapping }; const session = await savedObjectsClient.create( BACKGROUND_SESSION_TYPE, attributes, @@ -188,8 +187,8 @@ export class BackgroundSessionService { }); const deps = { savedObjectsClient }; return { - save: (sessionId: string, name: string, url: string, expires?: Date) => - this.save(sessionId, name, url, expires, deps), + 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) => From 8d81f395bed35e769dc65410abcd4096dcc1d539 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Wed, 18 Nov 2020 14:36:25 -0700 Subject: [PATCH 25/27] Regenerate docs --- ...lugins-data-public.isessionservice.find.md | 2 +- ...plugins-data-public.isessionservice.get.md | 2 +- ...gin-plugins-data-public.isessionservice.md | 8 ++++---- ...ins-data-public.isessionservice.restore.md | 2 +- ...lugins-data-public.isessionservice.save.md | 2 +- src/plugins/data/public/public.api.md | 20 ++++++++++--------- src/plugins/embeddable/public/public.api.md | 4 +++- 7 files changed, 22 insertions(+), 18 deletions(-) diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.find.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.find.md index dd5f5237554aa..58e2fea0e6fe9 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.find.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.find.md @@ -9,5 +9,5 @@ Gets a list of saved sessions Signature: ```typescript -find: (options: SearchSessionFindOptions) => Promise; +find: (options: SearchSessionFindOptions) => Promise>; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.get.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.get.md index 4e1ec3d431b9a..a2dff2f18253b 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.get.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.get.md @@ -9,5 +9,5 @@ Gets a saved session Signature: ```typescript -get: (sessionId: string) => Promise; +get: (sessionId: string) => Promise>; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md index 2a6e0cfa09bc2..bff72a9d1517f 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md @@ -16,14 +16,14 @@ export interface ISessionService | --- | --- | --- | | [clear](./kibana-plugin-plugins-data-public.isessionservice.clear.md) | () => void | Clears the active session. | | [delete](./kibana-plugin-plugins-data-public.isessionservice.delete.md) | (sessionId: string) => Promise<any> | Deletes a session | -| [find](./kibana-plugin-plugins-data-public.isessionservice.find.md) | (options: SearchSessionFindOptions) => Promise<any> | Gets a list of saved sessions | -| [get](./kibana-plugin-plugins-data-public.isessionservice.get.md) | (sessionId: string) => Promise<any> | Gets a saved session | +| [find](./kibana-plugin-plugins-data-public.isessionservice.find.md) | (options: SearchSessionFindOptions) => Promise<SavedObjectsFindResponse<BackgroundSessionSavedObjectAttributes>> | Gets a list of saved sessions | +| [get](./kibana-plugin-plugins-data-public.isessionservice.get.md) | (sessionId: string) => Promise<SavedObject<BackgroundSessionSavedObjectAttributes>> | Gets a saved session | | [getSession$](./kibana-plugin-plugins-data-public.isessionservice.getsession_.md) | () => Observable<string | undefined> | Returns the observable that emits an update every time the session ID changes | | [getSessionId](./kibana-plugin-plugins-data-public.isessionservice.getsessionid.md) | () => string | undefined | Returns the active session ID | | [isRestore](./kibana-plugin-plugins-data-public.isessionservice.isrestore.md) | () => boolean | Whether the active session is restored (i.e. reusing previous search IDs) | | [isStored](./kibana-plugin-plugins-data-public.isessionservice.isstored.md) | () => boolean | Whether the active session is already saved (i.e. sent to background) | -| [restore](./kibana-plugin-plugins-data-public.isessionservice.restore.md) | (sessionId: string) => Promise<any> | Restores existing session | -| [save](./kibana-plugin-plugins-data-public.isessionservice.save.md) | (name: string, url: string) => Promise<any> | Saves a session | +| [restore](./kibana-plugin-plugins-data-public.isessionservice.restore.md) | (sessionId: string) => Promise<SavedObject<BackgroundSessionSavedObjectAttributes>> | Restores existing session | +| [save](./kibana-plugin-plugins-data-public.isessionservice.save.md) | (name: string, url: string) => Promise<SavedObject<BackgroundSessionSavedObjectAttributes>> | Saves a session | | [start](./kibana-plugin-plugins-data-public.isessionservice.start.md) | () => string | Starts a new session | | [update](./kibana-plugin-plugins-data-public.isessionservice.update.md) | (sessionId: string, attributes: Partial<BackgroundSessionSavedObjectAttributes>) => Promise<any> | Updates a session | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.restore.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.restore.md index b182e09655738..96106a6ef7e2d 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.restore.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.restore.md @@ -9,5 +9,5 @@ Restores existing session Signature: ```typescript -restore: (sessionId: string) => Promise; +restore: (sessionId: string) => Promise>; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.save.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.save.md index ba48c8aec220e..4ac4a96614467 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.save.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.save.md @@ -9,5 +9,5 @@ Saves a session Signature: ```typescript -save: (name: string, url: string) => Promise; +save: (name: string, url: string) => Promise>; ``` diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 6a3b12941958a..0a444c8ede193 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -69,10 +69,12 @@ import { RequestAdapter } from 'src/plugins/inspector/common'; import { RequestStatistics } from 'src/plugins/inspector/common'; import { Required } from '@kbn/utility-types'; import * as Rx from 'rxjs'; -import { SavedObject } from 'src/core/server'; -import { SavedObject as SavedObject_2 } from 'src/core/public'; +import { SavedObject } from 'kibana/server'; +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 { SavedObjectsFindResponse } from 'kibana/server'; import { Search } from '@elastic/elasticsearch/api/requestParams'; import { SearchResponse } from 'elasticsearch'; import { SerializedFieldFormat as SerializedFieldFormat_2 } from 'src/plugins/expressions/common'; @@ -1388,7 +1390,7 @@ export class IndexPatternsService { // Warning: (ae-forgotten-export) The symbol "IndexPatternSavedObjectAttrs" needs to be exported by the entry point index.d.ts // // (undocumented) - getCache: () => Promise[] | null | undefined>; + getCache: () => Promise[] | null | undefined>; getDefault: () => Promise; getFieldsForIndexPattern: (indexPattern: IndexPattern | IndexPatternSpec, options?: GetFieldsOptions | undefined) => Promise; // Warning: (ae-forgotten-export) The symbol "GetFieldsOptions" needs to be exported by the entry point index.d.ts @@ -1400,7 +1402,7 @@ export class IndexPatternsService { }>>; getTitles: (refresh?: boolean) => Promise; refreshFields: (indexPattern: IndexPattern) => Promise; - savedObjectToSpec: (savedObject: SavedObject) => IndexPatternSpec; + savedObjectToSpec: (savedObject: SavedObject_2) => IndexPatternSpec; setDefault: (id: string, force?: boolean) => Promise; updateSavedObject(indexPattern: IndexPattern, saveAttempts?: number, ignoreErrors?: boolean): Promise; } @@ -1501,16 +1503,16 @@ export interface ISessionService { clear: () => void; delete: (sessionId: string) => Promise; // Warning: (ae-forgotten-export) The symbol "SearchSessionFindOptions" needs to be exported by the entry point index.d.ts - find: (options: SearchSessionFindOptions) => Promise; - get: (sessionId: string) => Promise; + find: (options: SearchSessionFindOptions) => Promise>; + get: (sessionId: string) => Promise>; getSession$: () => Observable; getSessionId: () => string | undefined; isRestore: () => boolean; isStored: () => boolean; - restore: (sessionId: string) => Promise; - save: (name: string, url: string) => Promise; - start: () => string; // Warning: (ae-forgotten-export) The symbol "BackgroundSessionSavedObjectAttributes" needs to be exported by the entry point index.d.ts + restore: (sessionId: string) => Promise>; + save: (name: string, url: string) => Promise>; + start: () => string; update: (sessionId: string, attributes: Partial) => Promise; } diff --git a/src/plugins/embeddable/public/public.api.md b/src/plugins/embeddable/public/public.api.md index 6a2565edf2f67..6033563d468c5 100644 --- a/src/plugins/embeddable/public/public.api.md +++ b/src/plugins/embeddable/public/public.api.md @@ -62,11 +62,13 @@ import { RecursiveReadonly } from '@kbn/utility-types'; import { RequestAdapter } from 'src/plugins/inspector/common'; import { Required } from '@kbn/utility-types'; import * as Rx from 'rxjs'; -import { SavedObject as SavedObject_2 } from 'src/core/server'; +import { SavedObject as SavedObject_2 } from 'kibana/server'; +import { SavedObject as SavedObject_3 } from 'src/core/server'; 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 { SavedObjectsFindResponse as SavedObjectsFindResponse_2 } from 'kibana/server'; import { Search } from '@elastic/elasticsearch/api/requestParams'; import { SearchResponse } from 'elasticsearch'; import { SerializedFieldFormat as SerializedFieldFormat_2 } from 'src/plugins/expressions/common'; From 9bbfa3a98407b1012d42492a816a8f1a814f3d03 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Wed, 18 Nov 2020 16:14:57 -0700 Subject: [PATCH 26/27] Review feedback --- src/plugins/data/common/search/session/status.ts | 2 +- src/plugins/data/common/search/session/types.ts | 2 +- src/plugins/data/server/search/session/session_service.test.ts | 2 +- src/plugins/data/server/search/session/session_service.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plugins/data/common/search/session/status.ts b/src/plugins/data/common/search/session/status.ts index 4965427873470..1f6b6eb3084bb 100644 --- a/src/plugins/data/common/search/session/status.ts +++ b/src/plugins/data/common/search/session/status.ts @@ -18,7 +18,7 @@ */ export enum BackgroundSessionStatus { - INCOMPLETE = 'incomplete', + IN_PROGRESS = 'in_progress', ERROR = 'error', COMPLETE = 'complete', CANCELLED = 'cancelled', diff --git a/src/plugins/data/common/search/session/types.ts b/src/plugins/data/common/search/session/types.ts index 81c3923719838..d1ab22057695a 100644 --- a/src/plugins/data/common/search/session/types.ts +++ b/src/plugins/data/common/search/session/types.ts @@ -85,7 +85,7 @@ export interface ISessionService { /** * Deletes a session */ - delete: (sessionId: string) => Promise; + delete: (sessionId: string) => Promise; } export interface BackgroundSessionSavedObjectAttributes { 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 f7f62f73d10a6..1ceebae967d4c 100644 --- a/src/plugins/data/server/search/session/session_service.test.ts +++ b/src/plugins/data/server/search/session/session_service.test.ts @@ -143,7 +143,7 @@ describe('BackgroundSessionService', () => { expires, initialState: {}, restoreState: {}, - status: BackgroundSessionStatus.INCOMPLETE, + status: BackgroundSessionStatus.IN_PROGRESS, idMapping: { [requestHash]: searchId }, }, { id: sessionId } diff --git a/src/plugins/data/server/search/session/session_service.ts b/src/plugins/data/server/search/session/session_service.ts index a109ebb91a09d..eca5f428b8555 100644 --- a/src/plugins/data/server/search/session/session_service.ts +++ b/src/plugins/data/server/search/session/session_service.ts @@ -66,7 +66,7 @@ export class BackgroundSessionService { name, created = new Date().toISOString(), expires = new Date(Date.now() + DEFAULT_EXPIRATION).toISOString(), - status = BackgroundSessionStatus.INCOMPLETE, + status = BackgroundSessionStatus.IN_PROGRESS, initialState = {}, restoreState = {}, }: Partial, From a3dc0d89a7493eed4ba1a78b6b6f3194ec1f6cb7 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Wed, 18 Nov 2020 16:44:44 -0700 Subject: [PATCH 27/27] Doc changes --- .../kibana-plugin-plugins-data-public.isessionservice.delete.md | 2 +- .../public/kibana-plugin-plugins-data-public.isessionservice.md | 2 +- src/plugins/data/public/public.api.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.delete.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.delete.md index a04e44778a9ee..eabb966160c4d 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.delete.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.delete.md @@ -9,5 +9,5 @@ Deletes a session Signature: ```typescript -delete: (sessionId: string) => Promise; +delete: (sessionId: string) => Promise; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md index bff72a9d1517f..02c0a821e552d 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md @@ -15,7 +15,7 @@ export interface ISessionService | Property | Type | Description | | --- | --- | --- | | [clear](./kibana-plugin-plugins-data-public.isessionservice.clear.md) | () => void | Clears the active session. | -| [delete](./kibana-plugin-plugins-data-public.isessionservice.delete.md) | (sessionId: string) => Promise<any> | Deletes a session | +| [delete](./kibana-plugin-plugins-data-public.isessionservice.delete.md) | (sessionId: string) => Promise<void> | Deletes a session | | [find](./kibana-plugin-plugins-data-public.isessionservice.find.md) | (options: SearchSessionFindOptions) => Promise<SavedObjectsFindResponse<BackgroundSessionSavedObjectAttributes>> | Gets a list of saved sessions | | [get](./kibana-plugin-plugins-data-public.isessionservice.get.md) | (sessionId: string) => Promise<SavedObject<BackgroundSessionSavedObjectAttributes>> | Gets a saved session | | [getSession$](./kibana-plugin-plugins-data-public.isessionservice.getsession_.md) | () => Observable<string | undefined> | Returns the observable that emits an update every time the session ID changes | diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 0a444c8ede193..ae36e961a2641 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -1501,7 +1501,7 @@ export const isErrorResponse: (response?: IKibanaSearchResponse | undefined // @public (undocumented) export interface ISessionService { clear: () => void; - delete: (sessionId: string) => Promise; + delete: (sessionId: string) => Promise; // Warning: (ae-forgotten-export) The symbol "SearchSessionFindOptions" needs to be exported by the entry point index.d.ts find: (options: SearchSessionFindOptions) => Promise>; get: (sessionId: string) => Promise>;