diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.dataapirequesthandlercontext.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.dataapirequesthandlercontext.md deleted file mode 100644 index 8b7b025d80181..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.dataapirequesthandlercontext.md +++ /dev/null @@ -1,18 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [DataApiRequestHandlerContext](./kibana-plugin-plugins-data-server.dataapirequesthandlercontext.md) - -## DataApiRequestHandlerContext interface - -Signature: - -```typescript -export interface DataApiRequestHandlerContext extends ISearchClient -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [session](./kibana-plugin-plugins-data-server.dataapirequesthandlercontext.session.md) | IScopedSessionService | | - diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.dataapirequesthandlercontext.session.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.dataapirequesthandlercontext.session.md deleted file mode 100644 index 9a6e3f55d3929..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.dataapirequesthandlercontext.session.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [DataApiRequestHandlerContext](./kibana-plugin-plugins-data-server.dataapirequesthandlercontext.md) > [session](./kibana-plugin-plugins-data-server.dataapirequesthandlercontext.session.md) - -## DataApiRequestHandlerContext.session property - -Signature: - -```typescript -session: IScopedSessionService; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iscopedsessionservice.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iscopedsessionservice.md deleted file mode 100644 index eaac671b9a182..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iscopedsessionservice.md +++ /dev/null @@ -1,18 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IScopedSessionService](./kibana-plugin-plugins-data-server.iscopedsessionservice.md) - -## IScopedSessionService interface - -Signature: - -```typescript -export interface IScopedSessionService -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [search](./kibana-plugin-plugins-data-server.iscopedsessionservice.search.md) | <Request extends IKibanaSearchRequest, Response extends IKibanaSearchResponse>(strategy: ISearchStrategy<Request, Response>, ...args: Parameters<ISearchStrategy<Request, Response>['search']>) => Observable<Response> | | - diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iscopedsessionservice.search.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iscopedsessionservice.search.md deleted file mode 100644 index d58a9fd9f3761..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iscopedsessionservice.search.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IScopedSessionService](./kibana-plugin-plugins-data-server.iscopedsessionservice.md) > [search](./kibana-plugin-plugins-data-server.iscopedsessionservice.search.md) - -## IScopedSessionService.search property - -Signature: - -```typescript -search: (strategy: ISearchStrategy, ...args: Parameters['search']>) => Observable; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsessionservice.asscopedprovider.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsessionservice.asscopedprovider.md new file mode 100644 index 0000000000000..3f3d1a2429933 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsessionservice.asscopedprovider.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchSessionService](./kibana-plugin-plugins-data-server.isearchsessionservice.md) > [asScopedProvider](./kibana-plugin-plugins-data-server.isearchsessionservice.asscopedprovider.md) + +## ISearchSessionService.asScopedProvider property + +Signature: + +```typescript +asScopedProvider: (core: CoreStart) => (request: KibanaRequest) => IScopedSearchSessionsClient; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsessionservice.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsessionservice.md new file mode 100644 index 0000000000000..e7a92497308b9 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsessionservice.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchSessionService](./kibana-plugin-plugins-data-server.isearchsessionservice.md) + +## ISearchSessionService interface + +Signature: + +```typescript +export interface ISearchSessionService +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [asScopedProvider](./kibana-plugin-plugins-data-server.isearchsessionservice.asscopedprovider.md) | (core: CoreStart) => (request: KibanaRequest) => IScopedSearchSessionsClient<T> | | + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.asscoped.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.asscoped.md index f97cc22a53001..1c65aeb8f137f 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.asscoped.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.asscoped.md @@ -7,5 +7,5 @@ Signature: ```typescript -asScoped: (request: KibanaRequest) => ISearchClient; +asScoped: (request: KibanaRequest) => IScopedSearchClient; ``` 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 771b529f23824..52579a0a14b4d 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 | | -| [asScoped](./kibana-plugin-plugins-data-server.isearchstart.asscoped.md) | (request: KibanaRequest) => ISearchClient | | +| [asScoped](./kibana-plugin-plugins-data-server.isearchstart.asscoped.md) | (request: KibanaRequest) => IScopedSearchClient | | | [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.isessionservice.asscopedprovider.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isessionservice.asscopedprovider.md deleted file mode 100644 index d52b9b783919b..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isessionservice.asscopedprovider.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISessionService](./kibana-plugin-plugins-data-server.isessionservice.md) > [asScopedProvider](./kibana-plugin-plugins-data-server.isessionservice.asscopedprovider.md) - -## ISessionService.asScopedProvider property - -Signature: - -```typescript -asScopedProvider: (core: CoreStart) => (request: KibanaRequest) => IScopedSessionService; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isessionservice.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isessionservice.md deleted file mode 100644 index dcc7dfc8bb946..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isessionservice.md +++ /dev/null @@ -1,18 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISessionService](./kibana-plugin-plugins-data-server.isessionservice.md) - -## ISessionService interface - -Signature: - -```typescript -export interface ISessionService -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [asScopedProvider](./kibana-plugin-plugins-data-server.isessionservice.asscopedprovider.md) | (core: CoreStart) => (request: KibanaRequest) => IScopedSessionService | | - diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md index 6847808d51bdb..4739de481e020 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md @@ -14,7 +14,6 @@ | [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) | | | [OptionedParamType](./kibana-plugin-plugins-data-server.optionedparamtype.md) | | | [Plugin](./kibana-plugin-plugins-data-server.plugin.md) | | -| [SessionService](./kibana-plugin-plugins-data-server.sessionservice.md) | The OSS session service. See data\_enhanced in X-Pack for the search session service. | ## Enumerations @@ -45,7 +44,6 @@ | --- | --- | | [AggFunctionsMapping](./kibana-plugin-plugins-data-server.aggfunctionsmapping.md) | A global list of the expression function definitions for each agg type function. | | [AggParamOption](./kibana-plugin-plugins-data-server.aggparamoption.md) | | -| [DataApiRequestHandlerContext](./kibana-plugin-plugins-data-server.dataapirequesthandlercontext.md) | | | [EsQueryConfig](./kibana-plugin-plugins-data-server.esqueryconfig.md) | | | [FieldDescriptor](./kibana-plugin-plugins-data-server.fielddescriptor.md) | | | [FieldFormatConfig](./kibana-plugin-plugins-data-server.fieldformatconfig.md) | | @@ -53,12 +51,11 @@ | [IFieldSubType](./kibana-plugin-plugins-data-server.ifieldsubtype.md) | | | [IFieldType](./kibana-plugin-plugins-data-server.ifieldtype.md) | | | [IndexPatternAttributes](./kibana-plugin-plugins-data-server.indexpatternattributes.md) | Interface for an index pattern saved object | -| [IScopedSessionService](./kibana-plugin-plugins-data-server.iscopedsessionservice.md) | | | [ISearchOptions](./kibana-plugin-plugins-data-server.isearchoptions.md) | | +| [ISearchSessionService](./kibana-plugin-plugins-data-server.isearchsessionservice.md) | | | [ISearchSetup](./kibana-plugin-plugins-data-server.isearchsetup.md) | | | [ISearchStart](./kibana-plugin-plugins-data-server.isearchstart.md) | | | [ISearchStrategy](./kibana-plugin-plugins-data-server.isearchstrategy.md) | Search strategy interface contains a search method that takes in a request and returns a promise that resolves to a response. | -| [ISessionService](./kibana-plugin-plugins-data-server.isessionservice.md) | | | [KueryNode](./kibana-plugin-plugins-data-server.kuerynode.md) | | | [OptionedValueProp](./kibana-plugin-plugins-data-server.optionedvalueprop.md) | | | [PluginSetup](./kibana-plugin-plugins-data-server.pluginsetup.md) | | @@ -110,5 +107,6 @@ | [KibanaContext](./kibana-plugin-plugins-data-server.kibanacontext.md) | | | [ParsedInterval](./kibana-plugin-plugins-data-server.parsedinterval.md) | | | [Query](./kibana-plugin-plugins-data-server.query.md) | | +| [SearchRequestHandlerContext](./kibana-plugin-plugins-data-server.searchrequesthandlercontext.md) | | | [TimeRange](./kibana-plugin-plugins-data-server.timerange.md) | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchrequesthandlercontext.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchrequesthandlercontext.md new file mode 100644 index 0000000000000..f031ddfbd09af --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchrequesthandlercontext.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [SearchRequestHandlerContext](./kibana-plugin-plugins-data-server.searchrequesthandlercontext.md) + +## SearchRequestHandlerContext type + +Signature: + +```typescript +export declare type SearchRequestHandlerContext = IScopedSearchClient; +``` 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 index be95fb04a2c4f..b47e00542da97 100644 --- 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 @@ -16,5 +16,6 @@ export interface SearchStrategyDependencies | --- | --- | --- | | [esClient](./kibana-plugin-plugins-data-server.searchstrategydependencies.esclient.md) | IScopedClusterClient | | | [savedObjectsClient](./kibana-plugin-plugins-data-server.searchstrategydependencies.savedobjectsclient.md) | SavedObjectsClientContract | | +| [searchSessionsClient](./kibana-plugin-plugins-data-server.searchstrategydependencies.searchsessionsclient.md) | IScopedSearchSessionsClient | | | [uiSettingsClient](./kibana-plugin-plugins-data-server.searchstrategydependencies.uisettingsclient.md) | IUiSettingsClient | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchstrategydependencies.searchsessionsclient.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchstrategydependencies.searchsessionsclient.md new file mode 100644 index 0000000000000..5340ed9673c02 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchstrategydependencies.searchsessionsclient.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) > [searchSessionsClient](./kibana-plugin-plugins-data-server.searchstrategydependencies.searchsessionsclient.md) + +## SearchStrategyDependencies.searchSessionsClient property + +Signature: + +```typescript +searchSessionsClient: IScopedSearchSessionsClient; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice._constructor_.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice._constructor_.md deleted file mode 100644 index 73d658455a66f..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice._constructor_.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [SessionService](./kibana-plugin-plugins-data-server.sessionservice.md) > [(constructor)](./kibana-plugin-plugins-data-server.sessionservice._constructor_.md) - -## SessionService.(constructor) - -Constructs a new instance of the `SessionService` class - -Signature: - -```typescript -constructor(); -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.asscopedprovider.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.asscopedprovider.md deleted file mode 100644 index f3af7fb0f61d9..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.asscopedprovider.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [SessionService](./kibana-plugin-plugins-data-server.sessionservice.md) > [asScopedProvider](./kibana-plugin-plugins-data-server.sessionservice.asscopedprovider.md) - -## SessionService.asScopedProvider() method - -Signature: - -```typescript -asScopedProvider(core: CoreStart): (request: KibanaRequest) => { - search: , Response_1 extends IKibanaSearchResponse>(strategy: ISearchStrategy, request: Request_1, options: import("../../../common").ISearchOptions, deps: import("../types").SearchStrategyDependencies) => import("rxjs").Observable; - }; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| core | CoreStart | | - -Returns: - -`(request: KibanaRequest) => { - search: , Response_1 extends IKibanaSearchResponse>(strategy: ISearchStrategy, request: Request_1, options: import("../../../common").ISearchOptions, deps: import("../types").SearchStrategyDependencies) => import("rxjs").Observable; - }` - diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.md deleted file mode 100644 index 2457f7103d8fa..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.md +++ /dev/null @@ -1,27 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [SessionService](./kibana-plugin-plugins-data-server.sessionservice.md) - -## SessionService class - -The OSS session service. See data\_enhanced in X-Pack for the search session service. - -Signature: - -```typescript -export declare class SessionService implements ISessionService -``` - -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)()](./kibana-plugin-plugins-data-server.sessionservice._constructor_.md) | | Constructs a new instance of the SessionService class | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [asScopedProvider(core)](./kibana-plugin-plugins-data-server.sessionservice.asscopedprovider.md) | | | -| [search(strategy, args)](./kibana-plugin-plugins-data-server.sessionservice.search.md) | | | - diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.search.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.search.md deleted file mode 100644 index 8f8620a6ed5ee..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.search.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [SessionService](./kibana-plugin-plugins-data-server.sessionservice.md) > [search](./kibana-plugin-plugins-data-server.sessionservice.search.md) - -## SessionService.search() method - -Signature: - -```typescript -search(strategy: ISearchStrategy, ...args: Parameters['search']>): import("rxjs").Observable; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| strategy | ISearchStrategy<Request, Response> | | -| args | Parameters<ISearchStrategy<Request, Response>['search']> | | - -Returns: - -`import("rxjs").Observable` - diff --git a/examples/search_examples/server/plugin.ts b/examples/search_examples/server/plugin.ts index e7ee311c8d652..595af844b0997 100644 --- a/examples/search_examples/server/plugin.ts +++ b/examples/search_examples/server/plugin.ts @@ -12,10 +12,9 @@ import type { CoreStart, Plugin, Logger, - RequestHandlerContext, } from 'src/core/server'; -import type { DataApiRequestHandlerContext } from 'src/plugins/data/server'; +import type { DataRequestHandlerContext } from 'src/plugins/data/server'; import { SearchExamplesPluginSetup, @@ -45,9 +44,7 @@ export class SearchExamplesPlugin deps: SearchExamplesPluginSetupDeps ) { this.logger.debug('search_examples: Setup'); - const router = core.http.createRouter< - RequestHandlerContext & { search: DataApiRequestHandlerContext } - >(); + const router = core.http.createRouter(); core.getStartServices().then(([_, depsStart]) => { const myStrategy = mySearchStrategyProvider(depsStart.data); diff --git a/examples/search_examples/server/routes/register_routes.ts b/examples/search_examples/server/routes/register_routes.ts index d7a18509b9a79..1159864bed31a 100644 --- a/examples/search_examples/server/routes/register_routes.ts +++ b/examples/search_examples/server/routes/register_routes.ts @@ -6,12 +6,10 @@ * Public License, v 1. */ -import type { IRouter, RequestHandlerContext } from 'kibana/server'; -import { DataApiRequestHandlerContext } from 'src/plugins/data/server'; +import type { IRouter } from 'kibana/server'; +import { DataRequestHandlerContext } from 'src/plugins/data/server'; import { registerServerSearchRoute } from './server_search_route'; -export function registerRoutes( - router: IRouter -) { +export function registerRoutes(router: IRouter) { registerServerSearchRoute(router); } diff --git a/examples/search_examples/server/routes/server_search_route.ts b/examples/search_examples/server/routes/server_search_route.ts index 99a1aba99d8ec..b4805a8de0d3c 100644 --- a/examples/search_examples/server/routes/server_search_route.ts +++ b/examples/search_examples/server/routes/server_search_route.ts @@ -9,13 +9,11 @@ import { IEsSearchRequest } from 'src/plugins/data/server'; import { schema } from '@kbn/config-schema'; import { IEsSearchResponse } from 'src/plugins/data/common'; -import type { DataApiRequestHandlerContext } from 'src/plugins/data/server'; -import type { IRouter, RequestHandlerContext } from 'src/core/server'; +import type { DataRequestHandlerContext } from 'src/plugins/data/server'; +import type { IRouter } from 'src/core/server'; import { SERVER_SEARCH_ROUTE_PATH } from '../../common'; -export function registerServerSearchRoute( - router: IRouter -) { +export function registerServerSearchRoute(router: IRouter) { router.get( { path: SERVER_SEARCH_ROUTE_PATH, diff --git a/src/plugins/data/public/search/session/mocks.ts b/src/plugins/data/public/search/session/mocks.ts index 679898e3e51dd..66cc8cd86f2b6 100644 --- a/src/plugins/data/public/search/session/mocks.ts +++ b/src/plugins/data/public/search/session/mocks.ts @@ -17,6 +17,7 @@ export function getSessionsClientMock(): jest.Mocked { create: jest.fn(), find: jest.fn(), update: jest.fn(), + extend: jest.fn(), delete: jest.fn(), }; } diff --git a/src/plugins/data/public/search/session/sessions_client.ts b/src/plugins/data/public/search/session/sessions_client.ts index 5b0ba51c2f344..91db4e4fb9f1d 100644 --- a/src/plugins/data/public/search/session/sessions_client.ts +++ b/src/plugins/data/public/search/session/sessions_client.ts @@ -68,6 +68,12 @@ export class SessionsClient { }); } + public extend(sessionId: string, keepAlive: string): Promise { + return this.http!.post(`/internal/session/${encodeURIComponent(sessionId)}/_extend`, { + body: JSON.stringify({ keepAlive }), + }); + } + public delete(sessionId: string): Promise { return this.http!.delete(`/internal/session/${encodeURIComponent(sessionId)}`); } diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index 8853f1fc34f9f..6e8bef49c6ad5 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -229,10 +229,9 @@ export { searchUsageObserver, shimAbortSignal, SearchUsage, - SessionService, - ISessionService, - IScopedSessionService, - DataApiRequestHandlerContext, + SearchSessionService, + ISearchSessionService, + SearchRequestHandlerContext, DataRequestHandlerContext, } from './search'; diff --git a/src/plugins/data/server/mocks.ts b/src/plugins/data/server/mocks.ts index 6d8e085554760..933e831b8c029 100644 --- a/src/plugins/data/server/mocks.ts +++ b/src/plugins/data/server/mocks.ts @@ -6,9 +6,14 @@ * Public License, v 1. */ -import { createSearchSetupMock, createSearchStartMock } from './search/mocks'; +import { + createSearchSetupMock, + createSearchStartMock, + createSearchRequestHandlerContext, +} from './search/mocks'; import { createFieldFormatsSetupMock, createFieldFormatsStartMock } from './field_formats/mocks'; import { createIndexPatternsStartMock } from './index_patterns/mocks'; +import { DataRequestHandlerContext } from './search'; function createSetupContract() { return { @@ -25,7 +30,14 @@ function createStartContract() { }; } +function createRequestHandlerContext() { + return ({ + search: createSearchRequestHandlerContext(), + } as unknown) as jest.Mocked; +} + export const dataPluginMock = { createSetupContract, createStartContract, + createRequestHandlerContext, }; diff --git a/src/plugins/data/server/search/mocks.ts b/src/plugins/data/server/search/mocks.ts index 2d2c546ffa4cf..63917ebc0fbab 100644 --- a/src/plugins/data/server/search/mocks.ts +++ b/src/plugins/data/server/search/mocks.ts @@ -10,6 +10,8 @@ import { ISearchSetup, ISearchStart } from './types'; import { searchAggsSetupMock, searchAggsStartMock } from './aggs/mocks'; import { searchSourceMock } from './search_source/mocks'; +export { createSearchSessionsClientMock } from './session/mocks'; + export function createSearchSetupMock(): jest.Mocked { return { aggs: searchAggsSetupMock(), @@ -22,10 +24,21 @@ export function createSearchStartMock(): jest.Mocked { return { aggs: searchAggsStartMock(), getSearchStrategy: jest.fn(), - asScoped: jest.fn().mockReturnValue({ - search: jest.fn(), - cancel: jest.fn(), - }), + asScoped: jest.fn().mockReturnValue(createSearchRequestHandlerContext()), searchSource: searchSourceMock.createStartContract(), }; } + +export function createSearchRequestHandlerContext() { + return { + search: jest.fn(), + cancel: jest.fn(), + extend: jest.fn(), + saveSession: jest.fn(), + getSession: jest.fn(), + findSessions: jest.fn(), + updateSession: jest.fn(), + extendSession: jest.fn(), + cancelSession: jest.fn(), + }; +} diff --git a/src/plugins/data/server/search/routes/bsearch.ts b/src/plugins/data/server/search/routes/bsearch.ts index ba96726b787c0..58424939fcd44 100644 --- a/src/plugins/data/server/search/routes/bsearch.ts +++ b/src/plugins/data/server/search/routes/bsearch.ts @@ -7,21 +7,17 @@ */ import { catchError, first } from 'rxjs/operators'; -import { CoreStart, KibanaRequest } from 'src/core/server'; import { BfetchServerSetup } from 'src/plugins/bfetch/server'; import { IKibanaSearchRequest, IKibanaSearchResponse, - ISearchClient, ISearchOptions, } from '../../../common/search'; - -type GetScopedProider = (coreStart: CoreStart) => (request: KibanaRequest) => ISearchClient; +import { ISearchStart } from '../types'; export function registerBsearchRoute( bfetch: BfetchServerSetup, - coreStartPromise: Promise<[CoreStart, {}, {}]>, - getScopedProvider: GetScopedProider + getScoped: ISearchStart['asScoped'] ): void { bfetch.addBatchProcessingRoute< { request: IKibanaSearchRequest; options?: ISearchOptions }, @@ -33,8 +29,7 @@ export function registerBsearchRoute( * @throws `KibanaServerError` */ onBatchItem: async ({ request: requestData, options }) => { - const coreStart = await coreStartPromise; - const search = getScopedProvider(coreStart[0])(request); + const search = getScoped(request); return search .search(requestData, options) .pipe( diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index e9f0edbd4d6c4..34aefe33e4402 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -6,8 +6,9 @@ * Public License, v 1. */ -import { BehaviorSubject, Observable, throwError } from 'rxjs'; +import { BehaviorSubject, from, Observable, throwError } from 'rxjs'; import { pick } from 'lodash'; +import moment from 'moment'; import { CoreSetup, CoreStart, @@ -18,10 +19,11 @@ import { SharedGlobalConfig, StartServicesAccessor, } from 'src/core/server'; -import { first } from 'rxjs/operators'; +import { first, switchMap } from 'rxjs/operators'; import { BfetchServerSetup } from 'src/plugins/bfetch/server'; import { ExpressionsServerSetup } from 'src/plugins/expressions/server'; import type { + IScopedSearchClient, ISearchSetup, ISearchStart, ISearchStrategy, @@ -46,7 +48,6 @@ import { IEsSearchResponse, IKibanaSearchRequest, IKibanaSearchResponse, - ISearchClient, ISearchOptions, kibana, kibanaContext, @@ -62,8 +63,9 @@ import { } from '../../common/search/aggs/buckets/shard_delay'; import { aggShardDelay } from '../../common/search/aggs/buckets/shard_delay_fn'; import { ConfigSchema } from '../../config'; -import { IScopedSessionService, ISessionService, SessionService } from './session'; +import { ISearchSessionService, SearchSessionService } from './session'; import { KbnServerError } from '../../../kibana_utils/server'; +import { tapFirst } from '../../common'; import { registerBsearchRoute } from './routes/bsearch'; type StrategyMap = Record>; @@ -92,14 +94,14 @@ export class SearchService implements Plugin { private readonly searchSourceService = new SearchSourceService(); private defaultSearchStrategyName: string = ES_SEARCH_STRATEGY; private searchStrategies: StrategyMap = {}; - private sessionService: ISessionService; - private coreStart?: CoreStart; + private sessionService: ISearchSessionService; + private asScoped!: ISearchStart['asScoped']; constructor( private initializerContext: PluginInitializerContext, private readonly logger: Logger ) { - this.sessionService = new SessionService(); + this.sessionService = new SearchSessionService(); } public setup( @@ -116,16 +118,10 @@ export class SearchService implements Plugin { registerSearchRoute(router); registerMsearchRoute(router, routeDependencies); - core.getStartServices().then(([coreStart]) => { - this.coreStart = coreStart; - }); - core.http.registerRouteHandlerContext( 'search', async (context, request) => { - const search = this.asScopedProvider(this.coreStart!)(request); - const session = this.sessionService.asScopedProvider(this.coreStart!)(request); - return { ...search, session }; + return this.asScoped(request); } ); @@ -138,7 +134,7 @@ export class SearchService implements Plugin { ) ); - registerBsearchRoute(bfetch, core.getStartServices(), this.asScopedProvider); + registerBsearchRoute(bfetch, (request: KibanaRequest) => this.asScoped(request)); core.savedObjects.registerType(searchTelemetry); if (usageCollection) { @@ -181,7 +177,7 @@ export class SearchService implements Plugin { { fieldFormats, indexPatterns }: SearchServiceStartDependencies ): ISearchStart { const { elasticsearch, savedObjects, uiSettings } = core; - const asScoped = this.asScopedProvider(core); + this.asScoped = this.asScopedProvider(core); return { aggs: this.aggsService.start({ fieldFormats, @@ -189,7 +185,7 @@ export class SearchService implements Plugin { indexPatterns, }), getSearchStrategy: this.getSearchStrategy, - asScoped, + asScoped: this.asScoped, searchSource: { asScoped: async (request: KibanaRequest) => { const esClient = elasticsearch.client.asScoped(request); @@ -208,7 +204,7 @@ export class SearchService implements Plugin { const searchSourceDependencies: SearchSourceDependencies = { getConfig: (key: string): T => uiSettingsCache[key], - search: asScoped(request).search, + search: this.asScoped(request).search, onResponse: (req, res) => res, legacy: { callMsearch: getCallMsearch({ @@ -241,26 +237,54 @@ export class SearchService implements Plugin { this.searchStrategies[name] = strategy; }; - private search = < + private getSearchStrategy = < SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest, SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse >( - session: IScopedSessionService, + name: string = this.defaultSearchStrategyName + ): ISearchStrategy => { + this.logger.debug(`Get strategy ${name}`); + const strategy = this.searchStrategies[name]; + if (!strategy) { + throw new KbnServerError(`Search strategy ${name} not found`, 404); + } + return strategy; + }; + + private search = < + SearchStrategyRequest extends IKibanaSearchRequest, + SearchStrategyResponse extends IKibanaSearchResponse + >( + deps: SearchStrategyDependencies, request: SearchStrategyRequest, - options: ISearchOptions, - deps: SearchStrategyDependencies + options: ISearchOptions ) => { try { const strategy = this.getSearchStrategy( options.strategy ); - return session.search(strategy, request, options, deps); + + const getSearchRequest = async () => + !options.sessionId || !options.isRestore || request.id + ? request + : { + ...request, + id: await deps.searchSessionsClient.getId(request, options), + }; + + return from(getSearchRequest()).pipe( + switchMap((searchRequest) => strategy.search(searchRequest, options, deps)), + tapFirst((response) => { + if (request.id || !options.sessionId || !response.id || options.isRestore) return; + deps.searchSessionsClient.trackId(request, response.id, options); + }) + ); } catch (e) { return throwError(e); } }; - private cancel = (id: string, options: ISearchOptions, deps: SearchStrategyDependencies) => { + private cancel = (deps: SearchStrategyDependencies, id: string, options: ISearchOptions = {}) => { const strategy = this.getSearchStrategy(options.strategy); if (!strategy.cancel) { throw new KbnServerError( @@ -272,10 +296,10 @@ export class SearchService implements Plugin { }; private extend = ( + deps: SearchStrategyDependencies, id: string, keepAlive: string, - options: ISearchOptions, - deps: SearchStrategyDependencies + options: ISearchOptions = {} ) => { const strategy = this.getSearchStrategy(options.strategy); if (!strategy.extend) { @@ -284,36 +308,71 @@ export class SearchService implements Plugin { return strategy.extend(id, keepAlive, options, deps); }; - private getSearchStrategy = < - SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest, - SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse - >( - name: string = this.defaultSearchStrategyName - ): ISearchStrategy => { - this.logger.debug(`Get strategy ${name}`); - const strategy = this.searchStrategies[name]; - if (!strategy) { - throw new KbnServerError(`Search strategy ${name} not found`, 404); + private cancelSession = async (deps: SearchStrategyDependencies, sessionId: string) => { + const searchIdMapping = await deps.searchSessionsClient.getSearchIdMapping(sessionId); + const response = await deps.searchSessionsClient.cancel(sessionId); + + for (const [searchId, strategyName] of searchIdMapping.entries()) { + const searchOptions = { + sessionId, + strategy: strategyName, + isStored: true, + }; + this.cancel(deps, searchId, searchOptions); } - return strategy; + + return response; + }; + + private extendSession = async ( + deps: SearchStrategyDependencies, + sessionId: string, + expires: Date + ) => { + const searchIdMapping = await deps.searchSessionsClient.getSearchIdMapping(sessionId); + const keepAlive = `${moment(expires).diff(moment())}ms`; + + for (const [searchId, strategyName] of searchIdMapping.entries()) { + const searchOptions = { + sessionId, + strategy: strategyName, + isStored: true, + }; + await this.extend(deps, searchId, keepAlive, searchOptions); + } + + return deps.searchSessionsClient.extend(sessionId, expires); }; private asScopedProvider = (core: CoreStart) => { const { elasticsearch, savedObjects, uiSettings } = core; const getSessionAsScoped = this.sessionService.asScopedProvider(core); - return (request: KibanaRequest): ISearchClient => { - const scopedSession = getSessionAsScoped(request); + return (request: KibanaRequest): IScopedSearchClient => { const savedObjectsClient = savedObjects.getScopedClient(request); + const searchSessionsClient = getSessionAsScoped(request); const deps = { + searchSessionsClient, savedObjectsClient, esClient: elasticsearch.client.asScoped(request), uiSettingsClient: uiSettings.asScopedToClient(savedObjectsClient), }; return { - search: (searchRequest, options = {}) => - this.search(scopedSession, searchRequest, options, deps), - cancel: (id, options = {}) => this.cancel(id, options, deps), - extend: (id, keepAlive, options = {}) => this.extend(id, keepAlive, options, deps), + search: < + SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest, + SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse + >( + searchRequest: SearchStrategyRequest, + options: ISearchOptions = {} + ) => + this.search(deps, searchRequest, options), + cancel: this.cancel.bind(this, deps), + extend: this.extend.bind(this, deps), + saveSession: searchSessionsClient.save, + getSession: searchSessionsClient.get, + findSessions: searchSessionsClient.find, + updateSession: searchSessionsClient.update, + extendSession: this.extendSession.bind(this, deps), + cancelSession: this.cancelSession.bind(this, deps), }; }; }; diff --git a/src/plugins/data/server/search/session/mocks.ts b/src/plugins/data/server/search/session/mocks.ts new file mode 100644 index 0000000000000..f75ba6fcc6620 --- /dev/null +++ b/src/plugins/data/server/search/session/mocks.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { IScopedSearchSessionsClient } from './types'; + +export function createSearchSessionsClientMock(): jest.Mocked< + IScopedSearchSessionsClient +> { + return { + getId: jest.fn(), + trackId: jest.fn(), + getSearchIdMapping: jest.fn(), + save: jest.fn(), + get: jest.fn(), + find: jest.fn(), + update: jest.fn(), + cancel: jest.fn(), + extend: jest.fn(), + }; +} diff --git a/src/plugins/data/server/search/session/session_service.test.ts b/src/plugins/data/server/search/session/session_service.test.ts deleted file mode 100644 index 2b981e3050947..0000000000000 --- a/src/plugins/data/server/search/session/session_service.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { of } from 'rxjs'; -import { SearchStrategyDependencies } from '../types'; -import { SessionService } from './session_service'; - -describe('SessionService', () => { - it('search invokes `strategy.search`', async () => { - const service = new SessionService(); - const mockSearch = jest.fn().mockReturnValue(of({})); - const mockStrategy = { search: mockSearch }; - const mockRequest = { id: 'bar' }; - const mockOptions = { sessionId: '1234' }; - const mockDeps = { savedObjectsClient: {} } as SearchStrategyDependencies; - - await service.search(mockStrategy, mockRequest, mockOptions, mockDeps); - - expect(mockSearch).toHaveBeenCalled(); - expect(mockSearch).toHaveBeenCalledWith(mockRequest, mockOptions, mockDeps); - }); -}); diff --git a/src/plugins/data/server/search/session/session_service.ts b/src/plugins/data/server/search/session/session_service.ts index d6b7a582fe764..dc4f19f126882 100644 --- a/src/plugins/data/server/search/session/session_service.ts +++ b/src/plugins/data/server/search/session/session_service.ts @@ -6,27 +6,41 @@ * Public License, v 1. */ -import { CoreStart, KibanaRequest } from 'kibana/server'; -import { IKibanaSearchRequest, IKibanaSearchResponse } from '../../../common'; -import { ISearchStrategy } from '../types'; -import { ISessionService } from './types'; +import { ISearchSessionService } from './types'; /** - * The OSS session service. See data_enhanced in X-Pack for the search session service. + * The OSS session service, which leaves most search session-related logic unimplemented. + * @see x-pack/plugins/data_enhanced/server/search/session/session_service.ts + * @internal */ -export class SessionService implements ISessionService { +export class SearchSessionService implements ISearchSessionService { constructor() {} - public search( - strategy: ISearchStrategy, - ...args: Parameters['search']> - ) { - return strategy.search(...args); - } - - public asScopedProvider(core: CoreStart) { - return (request: KibanaRequest) => ({ - search: this.search, + public asScopedProvider() { + return () => ({ + getId: () => { + throw new Error('getId not implemented in OSS search session service'); + }, + trackId: async () => {}, + getSearchIdMapping: async () => new Map(), + save: async () => { + throw new Error('save not implemented in OSS search session service'); + }, + get: async () => { + throw new Error('get not implemented in OSS search session service'); + }, + find: async () => { + throw new Error('find not implemented in OSS search session service'); + }, + update: async () => { + throw new Error('update not implemented in OSS search session service'); + }, + extend: async () => { + throw new Error('extend not implemented in OSS search session service'); + }, + cancel: async () => { + throw new Error('cancel not implemented in OSS search session service'); + }, }); } } diff --git a/src/plugins/data/server/search/session/types.ts b/src/plugins/data/server/search/session/types.ts index 97ef7aa8e61c3..3c074955a108e 100644 --- a/src/plugins/data/server/search/session/types.ts +++ b/src/plugins/data/server/search/session/types.ts @@ -6,19 +6,32 @@ * Public License, v 1. */ -import { Observable } from 'rxjs'; -import { CoreStart, KibanaRequest } from 'kibana/server'; -import { ISearchStrategy } from '../types'; -import { IKibanaSearchRequest, IKibanaSearchResponse } from '../../../common/search'; +import { + CoreStart, + KibanaRequest, + SavedObject, + SavedObjectsFindOptions, + SavedObjectsFindResponse, + SavedObjectsUpdateResponse, +} from 'kibana/server'; +import { IKibanaSearchRequest, ISearchOptions } from '../../../common/search'; -export interface IScopedSessionService { - search: ( - strategy: ISearchStrategy, - ...args: Parameters['search']> - ) => Observable; - [prop: string]: any; +export interface IScopedSearchSessionsClient { + getId: (request: IKibanaSearchRequest, options: ISearchOptions) => Promise; + trackId: ( + request: IKibanaSearchRequest, + searchId: string, + options: ISearchOptions + ) => Promise; + getSearchIdMapping: (sessionId: string) => Promise>; + save: (sessionId: string, attributes: Partial) => Promise>; + get: (sessionId: string) => Promise>; + find: (options: Omit) => Promise>; + update: (sessionId: string, attributes: Partial) => Promise>; + cancel: (sessionId: string) => Promise<{}>; + extend: (sessionId: string, expires: Date) => Promise>; } -export interface ISessionService { - asScopedProvider: (core: CoreStart) => (request: KibanaRequest) => IScopedSessionService; +export interface ISearchSessionService { + asScopedProvider: (core: CoreStart) => (request: KibanaRequest) => IScopedSearchSessionsClient; } diff --git a/src/plugins/data/server/search/types.ts b/src/plugins/data/server/search/types.ts index 7e7d22fdb2be1..ff3844c3d115e 100644 --- a/src/plugins/data/server/search/types.ts +++ b/src/plugins/data/server/search/types.ts @@ -25,17 +25,18 @@ import { import { AggsSetup, AggsStart } from './aggs'; import { SearchUsage } from './collectors'; import { IEsSearchRequest, IEsSearchResponse } from './es_search'; -import { ISessionService, IScopedSessionService } from './session'; +import { IScopedSearchSessionsClient, ISearchSessionService } from './session'; export interface SearchEnhancements { defaultStrategy: string; - sessionService: ISessionService; + sessionService: ISearchSessionService; } export interface SearchStrategyDependencies { savedObjectsClient: SavedObjectsClientContract; esClient: IScopedClusterClient; uiSettingsClient: IUiSettingsClient; + searchSessionsClient: IScopedSearchSessionsClient; } export interface ISearchSetup { @@ -85,6 +86,15 @@ export interface ISearchStrategy< ) => Promise; } +export interface IScopedSearchClient extends ISearchClient { + saveSession: IScopedSearchSessionsClient['save']; + getSession: IScopedSearchSessionsClient['get']; + findSessions: IScopedSearchSessionsClient['find']; + updateSession: IScopedSearchSessionsClient['update']; + cancelSession: IScopedSearchSessionsClient['cancel']; + extendSession: IScopedSearchSessionsClient['extend']; +} + export interface ISearchStart< SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest, SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse @@ -98,21 +108,19 @@ export interface ISearchStart< getSearchStrategy: ( name?: string // Name of the search strategy (defaults to the Elasticsearch strategy) ) => ISearchStrategy; - asScoped: (request: KibanaRequest) => ISearchClient; + asScoped: (request: KibanaRequest) => IScopedSearchClient; searchSource: { asScoped: (request: KibanaRequest) => Promise; }; } -export interface DataApiRequestHandlerContext extends ISearchClient { - session: IScopedSessionService; -} +export type SearchRequestHandlerContext = IScopedSearchClient; /** * @internal */ export interface DataRequestHandlerContext extends RequestHandlerContext { - search: DataApiRequestHandlerContext; + search: SearchRequestHandlerContext; } export type DataPluginRouter = IRouter; diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 127721bbc1e34..2c11ad56efd85 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -55,9 +55,13 @@ 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 { SavedObject } from 'kibana/server'; +import { SavedObject as SavedObject_2 } from 'src/core/server'; import { SavedObjectsClientContract } from 'src/core/server'; import { SavedObjectsClientContract as SavedObjectsClientContract_2 } from 'kibana/server'; +import { SavedObjectsFindOptions } from 'kibana/server'; +import { SavedObjectsFindResponse } from 'kibana/server'; +import { SavedObjectsUpdateResponse } from 'kibana/server'; import { Search } from '@elastic/elasticsearch/api/requestParams'; import { SearchResponse } from 'elasticsearch'; import { SerializedFieldFormat as SerializedFieldFormat_2 } from 'src/plugins/expressions/common'; @@ -305,19 +309,10 @@ export const castEsToKbnFieldTypeName: (esType: ES_FIELD_TYPES | string) => KBN_ // @public (undocumented) export const config: PluginConfigDescriptor; -// Warning: (ae-forgotten-export) The symbol "ISearchClient" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "DataApiRequestHandlerContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export interface DataApiRequestHandlerContext extends ISearchClient { - // (undocumented) - session: IScopedSessionService; -} - // @internal (undocumented) export interface DataRequestHandlerContext extends RequestHandlerContext { // (undocumented) - search: DataApiRequestHandlerContext; + search: SearchRequestHandlerContext; } // @public (undocumented) @@ -914,16 +909,6 @@ export class IndexPatternsService implements Plugin_3(strategy: ISearchStrategy, ...args: Parameters['search']>) => Observable; -} - // Warning: (ae-missing-release-tag) "ISearchOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -936,6 +921,16 @@ export interface ISearchOptions { strategy?: string; } +// Warning: (ae-missing-release-tag) "ISearchSessionService" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ISearchSessionService { + // Warning: (ae-forgotten-export) The symbol "IScopedSearchSessionsClient" needs to be exported by the entry point index.d.ts + // + // (undocumented) + asScopedProvider: (core: CoreStart) => (request: KibanaRequest) => IScopedSearchSessionsClient; +} + // Warning: (ae-missing-release-tag) "ISearchSetup" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -960,8 +955,10 @@ export interface ISearchStart ISearchClient; + asScoped: (request: KibanaRequest_2) => IScopedSearchClient; getSearchStrategy: (name?: string) => ISearchStrategy; // (undocumented) searchSource: { @@ -981,14 +978,6 @@ export interface ISearchStrategy Observable; } -// Warning: (ae-missing-release-tag) "ISessionService" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export interface ISessionService { - // (undocumented) - asScopedProvider: (core: CoreStart) => (request: KibanaRequest) => IScopedSessionService; -} - // @public (undocumented) export enum KBN_FIELD_TYPES { // (undocumented) @@ -1250,6 +1239,28 @@ export const search: { tabifyGetColumns: typeof tabifyGetColumns; }; +// Warning: (ae-missing-release-tag) "SearchRequestHandlerContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type SearchRequestHandlerContext = IScopedSearchClient; + +// @internal +export class SearchSessionService implements ISearchSessionService { + constructor(); + // (undocumented) + asScopedProvider(): () => { + getId: () => never; + trackId: () => Promise; + getSearchIdMapping: () => Promise>; + save: () => Promise; + get: () => Promise; + find: () => Promise; + update: () => Promise; + extend: () => Promise; + cancel: () => Promise; + }; +} + // 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) @@ -1259,6 +1270,8 @@ export interface SearchStrategyDependencies { // (undocumented) savedObjectsClient: SavedObjectsClientContract; // (undocumented) + searchSessionsClient: IScopedSearchSessionsClient; + // (undocumented) uiSettingsClient: IUiSettingsClient; } @@ -1280,19 +1293,6 @@ export function searchUsageObserver(logger: Logger_2, usage?: SearchUsage): { error(): void; }; -// Warning: (ae-missing-release-tag) "SessionService" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public -export class SessionService implements ISessionService { - constructor(); - // (undocumented) - asScopedProvider(core: CoreStart): (request: KibanaRequest) => { - search: , Response_1 extends IKibanaSearchResponse>(strategy: ISearchStrategy, request: Request_1, options: import("../../../common").ISearchOptions, deps: import("../types").SearchStrategyDependencies) => import("rxjs").Observable; - }; - // (undocumented) - search(strategy: ISearchStrategy, ...args: Parameters['search']>): import("rxjs").Observable; -} - // @internal export const shimAbortSignal: (promise: TransportRequestPromise, signal?: AbortSignal | undefined) => TransportRequestPromise; @@ -1418,23 +1418,23 @@ export function usageProvider(core: CoreSetup_2): SearchUsage; // src/plugins/data/server/index.ts:100:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:126:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:126:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:240:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:240:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:240:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:240:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:242:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:243:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:252:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:253:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:254:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:258:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:259:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:263:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:266:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:267:1 - (ae-forgotten-export) The symbol "calcAutoIntervalLessThan" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:239:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:239:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:239:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:239:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:241:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:242:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:251:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:252:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:253:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:257:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:258:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:262:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:265:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:266:1 - (ae-forgotten-export) The symbol "calcAutoIntervalLessThan" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index_patterns/index_patterns_service.ts:59:14 - (ae-forgotten-export) The symbol "IndexPatternsService" needs to be exported by the entry point index.d.ts // src/plugins/data/server/plugin.ts:79:74 - (ae-forgotten-export) The symbol "DataEnhancements" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/search/types.ts:103: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:113:5 - (ae-forgotten-export) The symbol "ISearchStartSearchSource" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/vis_type_timelion/server/plugin.ts b/src/plugins/vis_type_timelion/server/plugin.ts index 6a108fc765897..cdc75527b6f59 100644 --- a/src/plugins/vis_type_timelion/server/plugin.ts +++ b/src/plugins/vis_type_timelion/server/plugin.ts @@ -11,12 +11,8 @@ import { first } from 'rxjs/operators'; import { TypeOf, schema } from '@kbn/config-schema'; import { RecursiveReadonly } from '@kbn/utility-types'; import { deepFreeze } from '@kbn/std'; -import type { RequestHandlerContext } from 'src/core/server'; -import type { - PluginStart, - DataApiRequestHandlerContext, -} from '../../../../src/plugins/data/server'; +import type { PluginStart, DataRequestHandlerContext } from '../../../../src/plugins/data/server'; import { CoreSetup, PluginInitializerContext } from '../../../../src/core/server'; import { configSchema } from '../config'; import loadFunctions from './lib/load_functions'; @@ -73,9 +69,7 @@ export class Plugin { const logger = this.initializerContext.logger.get('timelion'); - const router = core.http.createRouter< - RequestHandlerContext & { search: DataApiRequestHandlerContext } - >(); + const router = core.http.createRouter(); const deps = { configManager, 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 1637fcc464f46..e0e6270735440 100644 --- a/src/plugins/vis_type_timelion/server/routes/validate_es.ts +++ b/src/plugins/vis_type_timelion/server/routes/validate_es.ts @@ -7,12 +7,10 @@ */ import _ from 'lodash'; -import { IRouter, RequestHandlerContext } from 'kibana/server'; -import type { DataApiRequestHandlerContext } from '../../../data/server'; +import { IRouter } from 'kibana/server'; +import type { DataRequestHandlerContext } from '../../../data/server'; -export function validateEsRoute( - router: IRouter -) { +export function validateEsRoute(router: IRouter) { router.get( { path: '/api/timelion/validate/es', diff --git a/src/plugins/vis_type_timeseries/server/types.ts b/src/plugins/vis_type_timeseries/server/types.ts index 29cd33031c883..3ee052bb2bea2 100644 --- a/src/plugins/vis_type_timeseries/server/types.ts +++ b/src/plugins/vis_type_timeseries/server/types.ts @@ -6,11 +6,8 @@ * Public License, v 1. */ -import type { IRouter, RequestHandlerContext } from 'src/core/server'; -import type { DataApiRequestHandlerContext } from '../../data/server'; - -export interface VisTypeTimeseriesRequestHandlerContext extends RequestHandlerContext { - search: DataApiRequestHandlerContext; -} +import type { IRouter } from 'src/core/server'; +import type { DataRequestHandlerContext } from '../../data/server'; +export type VisTypeTimeseriesRequestHandlerContext = DataRequestHandlerContext; export type VisTypeTimeseriesRouter = IRouter; diff --git a/x-pack/plugins/data_enhanced/server/routes/mocks.ts b/x-pack/plugins/data_enhanced/server/routes/mocks.ts deleted file mode 100644 index 4bad563bf393b..0000000000000 --- a/x-pack/plugins/data_enhanced/server/routes/mocks.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import type { DataRequestHandlerContext } from '../../../../../src/plugins/data/server'; -import { coreMock } from '../../../../../src/core/server/mocks'; - -export function createSearchRequestHandlerContext() { - return ({ - core: coreMock.createRequestHandlerContext(), - search: { - search: jest.fn(), - cancel: jest.fn(), - session: { - search: jest.fn(), - save: jest.fn(), - get: jest.fn(), - find: jest.fn(), - delete: jest.fn(), - update: jest.fn(), - }, - }, - } as unknown) as jest.Mocked; -} diff --git a/x-pack/plugins/data_enhanced/server/routes/session.test.ts b/x-pack/plugins/data_enhanced/server/routes/session.test.ts index c4433b562e97a..6af7618e43d9b 100644 --- a/x-pack/plugins/data_enhanced/server/routes/session.test.ts +++ b/x-pack/plugins/data_enhanced/server/routes/session.test.ts @@ -12,7 +12,7 @@ import type { PluginStart as DataPluginStart, DataRequestHandlerContext, } from '../../../../../src/plugins/data/server'; -import { createSearchRequestHandlerContext } from './mocks'; +import { dataPluginMock } from '../../../../../src/plugins/data/server/mocks'; import { registerSessionRoutes } from './session'; describe('registerSessionRoutes', () => { @@ -23,11 +23,11 @@ describe('registerSessionRoutes', () => { beforeEach(() => { mockCoreSetup = coreMock.createSetup(); mockLogger = coreMock.createPluginInitializerContext().logger.get(); - mockContext = createSearchRequestHandlerContext(); + mockContext = dataPluginMock.createRequestHandlerContext(); registerSessionRoutes(mockCoreSetup.http.createRouter(), mockLogger); }); - it('save calls session.save with sessionId and attributes', async () => { + it('save calls saveSession with sessionId and attributes', async () => { const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; const name = 'my saved background search session'; const body = { sessionId, name }; @@ -40,10 +40,10 @@ describe('registerSessionRoutes', () => { saveHandler(mockContext, mockRequest, mockResponse); - expect(mockContext.search!.session.save).toHaveBeenCalledWith(sessionId, { name }); + expect(mockContext.search!.saveSession).toHaveBeenCalledWith(sessionId, { name }); }); - it('get calls session.get with sessionId', async () => { + it('get calls getSession with sessionId', async () => { const id = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; const params = { id }; @@ -55,10 +55,10 @@ describe('registerSessionRoutes', () => { getHandler(mockContext, mockRequest, mockResponse); - expect(mockContext.search!.session.get).toHaveBeenCalledWith(id); + expect(mockContext.search!.getSession).toHaveBeenCalledWith(id); }); - it('find calls session.find with options', async () => { + it('find calls findSession with options', async () => { const page = 1; const perPage = 5; const sortField = 'my_field'; @@ -74,10 +74,10 @@ describe('registerSessionRoutes', () => { findHandler(mockContext, mockRequest, mockResponse); - expect(mockContext.search!.session.find).toHaveBeenCalledWith(body); + expect(mockContext.search!.findSessions).toHaveBeenCalledWith(body); }); - it('update calls session.update with id and attributes', async () => { + it('update calls updateSession with id and attributes', async () => { const id = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; const name = 'my saved background search session'; const expires = new Date().toISOString(); @@ -92,10 +92,10 @@ describe('registerSessionRoutes', () => { updateHandler(mockContext, mockRequest, mockResponse); - expect(mockContext.search!.session.update).toHaveBeenCalledWith(id, body); + expect(mockContext.search!.updateSession).toHaveBeenCalledWith(id, body); }); - it('delete calls session.delete with id', async () => { + it('delete calls cancelSession with id', async () => { const id = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; const params = { id }; @@ -107,6 +107,23 @@ describe('registerSessionRoutes', () => { deleteHandler(mockContext, mockRequest, mockResponse); - expect(mockContext.search!.session.delete).toHaveBeenCalledWith(id); + expect(mockContext.search!.cancelSession).toHaveBeenCalledWith(id); + }); + + it('extend calls extendSession with id', async () => { + const id = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const expires = new Date().toISOString(); + const params = { id }; + const body = { expires }; + + const mockRequest = httpServerMock.createKibanaRequest({ params, body }); + const mockResponse = httpServerMock.createResponseFactory(); + + const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value; + const [, , [, extendHandler]] = mockRouter.post.mock.calls; + + extendHandler(mockContext, mockRequest, mockResponse); + + expect(mockContext.search.extendSession).toHaveBeenCalledWith(id, new Date(expires)); }); }); diff --git a/x-pack/plugins/data_enhanced/server/routes/session.ts b/x-pack/plugins/data_enhanced/server/routes/session.ts index cbf683bd18fd2..b39ffd41f33c8 100644 --- a/x-pack/plugins/data_enhanced/server/routes/session.ts +++ b/x-pack/plugins/data_enhanced/server/routes/session.ts @@ -37,7 +37,7 @@ export function registerSessionRoutes(router: DataEnhancedPluginRouter, logger: } = request.body; try { - const response = await context.search!.session.save(sessionId, { + const response = await context.search!.saveSession(sessionId, { name, appId, expires, @@ -68,7 +68,7 @@ export function registerSessionRoutes(router: DataEnhancedPluginRouter, logger: async (context, request, res) => { const { id } = request.params; try { - const response = await context.search!.session.get(id); + const response = await context.search!.getSession(id); return res.ok({ body: response, @@ -97,7 +97,7 @@ export function registerSessionRoutes(router: DataEnhancedPluginRouter, logger: async (context, request, res) => { const { page, perPage, sortField, sortOrder, filter } = request.body; try { - const response = await context.search!.session.find({ + const response = await context.search!.findSessions({ page, perPage, sortField, @@ -127,7 +127,7 @@ export function registerSessionRoutes(router: DataEnhancedPluginRouter, logger: async (context, request, res) => { const { id } = request.params; try { - await context.search!.session.delete(id); + await context.search!.cancelSession(id); return res.ok(); } catch (e) { @@ -155,7 +155,7 @@ export function registerSessionRoutes(router: DataEnhancedPluginRouter, logger: const { id } = request.params; const { name, expires } = request.body; try { - const response = await context.search!.session.update(id, { name, expires }); + const response = await context.search!.updateSession(id, { name, expires }); return res.ok({ body: response, @@ -166,4 +166,33 @@ export function registerSessionRoutes(router: DataEnhancedPluginRouter, logger: } } ); + + router.post( + { + path: '/internal/session/{id}/_extend', + validate: { + params: schema.object({ + id: schema.string(), + }), + body: schema.object({ + expires: schema.string(), + }), + }, + }, + async (context, request, res) => { + const { id } = request.params; + const { expires } = request.body; + try { + const response = await context.search!.extendSession(id, new Date(expires)); + + return res.ok({ + body: response, + }); + } catch (e) { + const err = e.output?.payload || e; + logger.error(err); + return reportServerError(res, err); + } + } + ); } diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts index 1107ed8155080..38661ff352ffe 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts @@ -4,20 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { BehaviorSubject, of } from 'rxjs'; +import { BehaviorSubject } from 'rxjs'; import type { SavedObject, SavedObjectsClientContract } from 'kibana/server'; -import type { SearchStrategyDependencies } from '../../../../../../src/plugins/data/server'; import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; import { SearchSessionStatus } from '../../../common'; import { SEARCH_SESSION_TYPE } from '../../saved_objects'; -import { SearchSessionDependencies, SearchSessionService, SessionInfo } from './session_service'; +import { SearchSessionService, SessionInfo } from './session_service'; import { createRequestHash } from './utils'; import moment from 'moment'; import { coreMock } from 'src/core/server/mocks'; import { ConfigSchema } from '../../../config'; // @ts-ignore import { taskManagerMock } from '../../../../task_manager/server/mocks'; -import { SearchStatus } from './types'; const INMEM_TRACKING_INTERVAL = 10000; const MAX_UPDATE_RETRIES = 3; @@ -139,13 +137,13 @@ describe('SearchSessionService', () => { }); it('search throws if `name` is not provided', () => { - expect(() => service.save(sessionId, {}, { savedObjectsClient })).rejects.toMatchInlineSnapshot( + expect(() => service.save({ savedObjectsClient }, sessionId, {})).rejects.toMatchInlineSnapshot( `[Error: Name is required]` ); }); it('save throws if `name` is not provided', () => { - expect(() => service.save(sessionId, {}, { savedObjectsClient })).rejects.toMatchInlineSnapshot( + expect(() => service.save({ savedObjectsClient }, sessionId, {})).rejects.toMatchInlineSnapshot( `[Error: Name is required]` ); }); @@ -153,7 +151,7 @@ describe('SearchSessionService', () => { it('get calls saved objects client', async () => { savedObjectsClient.get.mockResolvedValue(mockSavedObject); - const response = await service.get(sessionId, { savedObjectsClient }); + const response = await service.get({ savedObjectsClient }, sessionId); expect(response).toBe(mockSavedObject); expect(savedObjectsClient.get).toHaveBeenCalledWith(SEARCH_SESSION_TYPE, sessionId); @@ -173,7 +171,7 @@ describe('SearchSessionService', () => { savedObjectsClient.find.mockResolvedValue(mockResponse); const options = { page: 0, perPage: 5 }; - const response = await service.find(options, { savedObjectsClient }); + const response = await service.find({ savedObjectsClient }, options); expect(response).toBe(mockResponse); expect(savedObjectsClient.find).toHaveBeenCalledWith({ @@ -190,7 +188,7 @@ describe('SearchSessionService', () => { savedObjectsClient.update.mockResolvedValue(mockUpdateSavedObject); const attributes = { name: 'new_name' }; - const response = await service.update(sessionId, attributes, { savedObjectsClient }); + const response = await service.update({ savedObjectsClient }, sessionId, attributes); expect(response).toBe(mockUpdateSavedObject); expect(savedObjectsClient.update).toHaveBeenCalledWith( @@ -200,93 +198,11 @@ describe('SearchSessionService', () => { ); }); - it('delete calls saved objects client', async () => { - savedObjectsClient.delete.mockResolvedValue({}); + it('cancel updates object status', async () => { + await service.cancel({ savedObjectsClient }, sessionId); - const response = await service.delete(sessionId, { savedObjectsClient }); - - expect(response).toEqual({}); - expect(savedObjectsClient.delete).toHaveBeenCalledWith(SEARCH_SESSION_TYPE, sessionId); - }); - - describe('search', () => { - const mockSearch = jest.fn().mockReturnValue(of({})); - const mockStrategy = { search: mockSearch }; - const mockSearchDeps = {} as SearchStrategyDependencies; - const mockDeps = {} as SearchSessionDependencies; - - beforeEach(() => { - mockSearch.mockClear(); - }); - - it('searches using the original request if not restoring', async () => { - const searchRequest = { params: {} }; - const options = { sessionId, isStored: false, isRestore: false }; - - await service - .search(mockStrategy, searchRequest, options, mockSearchDeps, mockDeps) - .toPromise(); - - expect(mockSearch).toBeCalledWith(searchRequest, options, mockSearchDeps); - }); - - it('searches using the original request if `id` is provided', async () => { - const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; - const searchRequest = { id: searchId, params: {} }; - const options = { sessionId, isStored: true, isRestore: true }; - - await service - .search(mockStrategy, searchRequest, options, mockSearchDeps, mockDeps) - .toPromise(); - - expect(mockSearch).toBeCalledWith(searchRequest, options, mockSearchDeps); - }); - - it('searches by looking up an `id` if restoring and `id` is not provided', async () => { - const searchRequest = { params: {} }; - const options = { sessionId, isStored: true, isRestore: true }; - const spyGetId = jest.spyOn(service, 'getId').mockResolvedValueOnce('my_id'); - - await service - .search(mockStrategy, searchRequest, options, mockSearchDeps, mockDeps) - .toPromise(); - - expect(mockSearch).toBeCalledWith({ ...searchRequest, id: 'my_id' }, options, mockSearchDeps); - - spyGetId.mockRestore(); - }); - - it('calls `trackId` once if the response contains an `id` and not restoring', async () => { - const searchRequest = { params: {} }; - const options = { sessionId, isStored: false, isRestore: false }; - const spyTrackId = jest.spyOn(service, 'trackId').mockResolvedValue(); - mockSearch.mockReturnValueOnce(of({ id: 'my_id' }, { id: 'my_id' })); - - await service - .search(mockStrategy, searchRequest, options, mockSearchDeps, mockDeps) - .toPromise(); - - expect(spyTrackId).toBeCalledTimes(1); - expect(spyTrackId).toBeCalledWith(searchRequest, 'my_id', options, {}); - - spyTrackId.mockRestore(); - }); - - it('does not call `trackId` if restoring', async () => { - const searchRequest = { params: {} }; - const options = { sessionId, isStored: true, isRestore: true }; - const spyGetId = jest.spyOn(service, 'getId').mockResolvedValueOnce('my_id'); - const spyTrackId = jest.spyOn(service, 'trackId').mockResolvedValue(); - mockSearch.mockReturnValueOnce(of({ id: 'my_id' })); - - await service - .search(mockStrategy, searchRequest, options, mockSearchDeps, mockDeps) - .toPromise(); - - expect(spyTrackId).not.toBeCalled(); - - spyGetId.mockRestore(); - spyTrackId.mockRestore(); + expect(savedObjectsClient.update).toHaveBeenCalledWith(SEARCH_SESSION_TYPE, sessionId, { + status: SearchSessionStatus.CANCELLED, }); }); @@ -309,20 +225,21 @@ describe('SearchSessionService', () => { get: () => mockIdMapping, }); - await service.trackId( - searchRequest, - searchId, - { sessionId, isStored, strategy: MOCK_STRATEGY }, - { savedObjectsClient } - ); + await service.trackId({ savedObjectsClient }, searchRequest, searchId, { + sessionId, + isStored, + strategy: MOCK_STRATEGY, + }); expect(savedObjectsClient.update).not.toHaveBeenCalled(); - await service.save( - sessionId, - { name, created, expires, appId, urlGeneratorId }, - { savedObjectsClient } - ); + await service.save({ savedObjectsClient }, sessionId, { + name, + created, + expires, + appId, + urlGeneratorId, + }); expect(savedObjectsClient.create).toHaveBeenCalledWith( SEARCH_SESSION_TYPE, @@ -346,30 +263,6 @@ describe('SearchSessionService', () => { expect(setParams.ids.get(requestHash).strategy).toBe(MOCK_STRATEGY); expect(setSessionId).toBe(sessionId); }); - - it('updates saved object when `isStored` is `true`', async () => { - const searchRequest = { params: {} }; - const requestHash = createRequestHash(searchRequest.params); - const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; - const isStored = true; - - await service.trackId( - searchRequest, - searchId, - { sessionId, isStored, strategy: MOCK_STRATEGY }, - { savedObjectsClient } - ); - - expect(savedObjectsClient.update).toHaveBeenCalledWith(SEARCH_SESSION_TYPE, sessionId, { - idMapping: { - [requestHash]: { - id: searchId, - strategy: MOCK_STRATEGY, - status: SearchStatus.IN_PROGRESS, - }, - }, - }); - }); }); describe('getId', () => { @@ -377,7 +270,7 @@ describe('SearchSessionService', () => { const searchRequest = { params: {} }; expect(() => - service.getId(searchRequest, {}, { savedObjectsClient }) + service.getId({ savedObjectsClient }, searchRequest, {}) ).rejects.toMatchInlineSnapshot(`[Error: Session ID is required]`); }); @@ -385,7 +278,7 @@ describe('SearchSessionService', () => { const searchRequest = { params: {} }; expect(() => - service.getId(searchRequest, { sessionId, isStored: false }, { savedObjectsClient }) + service.getId({ savedObjectsClient }, searchRequest, { sessionId, isStored: false }) ).rejects.toMatchInlineSnapshot( `[Error: Cannot get search ID from a session that is not stored]` ); @@ -395,11 +288,11 @@ describe('SearchSessionService', () => { const searchRequest = { params: {} }; expect(() => - service.getId( - searchRequest, - { sessionId, isStored: true, isRestore: false }, - { savedObjectsClient } - ) + service.getId({ savedObjectsClient }, searchRequest, { + sessionId, + isStored: true, + isRestore: false, + }) ).rejects.toMatchInlineSnapshot( `[Error: Get search ID is only supported when restoring a session]` ); @@ -427,16 +320,47 @@ describe('SearchSessionService', () => { }; savedObjectsClient.get.mockResolvedValue(mockSession); - const id = await service.getId( - searchRequest, - { sessionId, isStored: true, isRestore: true }, - { savedObjectsClient } - ); + const id = await service.getId({ savedObjectsClient }, searchRequest, { + sessionId, + isStored: true, + isRestore: true, + }); expect(id).toBe(searchId); }); }); + describe('getSearchIdMapping', () => { + it('retrieves the search IDs and strategies from the saved object', async () => { + const mockSession = { + id: 'd7170a35-7e2c-48d6-8dec-9a056721b489', + type: SEARCH_SESSION_TYPE, + attributes: { + name: 'my_name', + appId: 'my_app_id', + urlGeneratorId: 'my_url_generator_id', + idMapping: { + foo: { + id: 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0', + strategy: MOCK_STRATEGY, + }, + }, + }, + references: [], + }; + savedObjectsClient.get.mockResolvedValue(mockSession); + const searchIdMapping = await service.getSearchIdMapping( + { savedObjectsClient }, + mockSession.id + ); + expect(searchIdMapping).toMatchInlineSnapshot(` + Map { + "FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0" => "ese", + } + `); + }); + }); + describe('Monitor', () => { it('schedules the next iteration', async () => { const findSpy = jest.fn().mockResolvedValue({ saved_objects: [] }); diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts index 794baa21e2f4c..03466c769d9be 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts @@ -5,39 +5,33 @@ */ import moment, { Moment } from 'moment'; -import { from, Observable } from 'rxjs'; -import { first, switchMap } from 'rxjs/operators'; +import { Observable } from 'rxjs'; +import { first } from 'rxjs/operators'; import { + CoreSetup, CoreStart, KibanaRequest, - SavedObjectsClient, - SavedObjectsClientContract, Logger, SavedObject, - CoreSetup, SavedObjectsBulkUpdateObject, + SavedObjectsClient, + SavedObjectsClientContract, SavedObjectsFindOptions, } from '../../../../../../src/core/server'; import { IKibanaSearchRequest, - IKibanaSearchResponse, ISearchOptions, KueryNode, nodeBuilder, - tapFirst, } from '../../../../../../src/plugins/data/common'; -import { - ISearchStrategy, - ISessionService, - SearchStrategyDependencies, -} from '../../../../../../src/plugins/data/server'; +import { ISearchSessionService } from '../../../../../../src/plugins/data/server'; import { TaskManagerSetupContract, TaskManagerStartContract, } from '../../../../task_manager/server'; import { - SearchSessionSavedObjectAttributes, SearchSessionRequestInfo, + SearchSessionSavedObjectAttributes, SearchSessionStatus, } from '../../../common'; import { SEARCH_SESSION_TYPE } from '../../saved_objects'; @@ -66,7 +60,11 @@ interface StartDependencies { type SearchSessionsConfig = ConfigSchema['search']['sessions']; -export class SearchSessionService implements ISessionService { +/** + * @internal + */ +export class SearchSessionService + implements ISearchSessionService { /** * Map of sessionId to { [requestHash]: searchId } * @private @@ -228,33 +226,9 @@ export class SearchSessionService implements ISessionService { return updateResults.saved_objects; } - public search( - strategy: ISearchStrategy, - searchRequest: Request, - options: ISearchOptions, - searchDeps: SearchStrategyDependencies, - deps: SearchSessionDependencies - ): Observable { - // If this is a restored background search session, look up the ID using the provided sessionId - const getSearchRequest = async () => - !options.isRestore || searchRequest.id - ? searchRequest - : { - ...searchRequest, - id: await this.getId(searchRequest, options, deps), - }; - - return from(getSearchRequest()).pipe( - switchMap((request) => strategy.search(request, options, searchDeps)), - tapFirst((response) => { - if (searchRequest.id || !options.sessionId || !response.id || options.isRestore) return; - this.trackId(searchRequest, response.id, options, deps); - }) - ); - } - // TODO: Generate the `userId` from the realm type/realm name/username public save = async ( + { savedObjectsClient }: SearchSessionDependencies, sessionId: string, { name, @@ -265,8 +239,7 @@ export class SearchSessionService implements ISessionService { urlGeneratorId, initialState = {}, restoreState = {}, - }: Partial, - { savedObjectsClient }: SearchSessionDependencies + }: Partial ) => { if (!name) throw new Error('Name is required'); if (!appId) throw new Error('AppId is required'); @@ -296,7 +269,7 @@ export class SearchSessionService implements ISessionService { }; // TODO: Throw an error if this session doesn't belong to this user - public get = (sessionId: string, { savedObjectsClient }: SearchSessionDependencies) => { + public get = ({ savedObjectsClient }: SearchSessionDependencies, sessionId: string) => { this.logger.debug(`get | ${sessionId}`); return savedObjectsClient.get( SEARCH_SESSION_TYPE, @@ -306,8 +279,8 @@ export class SearchSessionService implements ISessionService { // TODO: Throw an error if this session doesn't belong to this user public find = ( - options: Omit, - { savedObjectsClient }: SearchSessionDependencies + { savedObjectsClient }: SearchSessionDependencies, + options: Omit ) => { return savedObjectsClient.find({ ...options, @@ -317,9 +290,9 @@ export class SearchSessionService implements ISessionService { // TODO: Throw an error if this session doesn't belong to this user public update = ( + { savedObjectsClient }: SearchSessionDependencies, sessionId: string, - attributes: Partial, - { savedObjectsClient }: SearchSessionDependencies + attributes: Partial ) => { this.logger.debug(`update | ${sessionId}`); return savedObjectsClient.update( @@ -329,9 +302,17 @@ export class SearchSessionService implements ISessionService { ); }; + public extend(deps: SearchSessionDependencies, sessionId: string, expires: Date) { + this.logger.debug(`extend | ${sessionId}`); + + return this.update(deps, sessionId, { expires: expires.toISOString() }); + } + // TODO: Throw an error if this session doesn't belong to this user - public delete = (sessionId: string, { savedObjectsClient }: SearchSessionDependencies) => { - return savedObjectsClient.delete(SEARCH_SESSION_TYPE, sessionId); + public cancel = (deps: SearchSessionDependencies, sessionId: string) => { + return this.update(deps, sessionId, { + status: SearchSessionStatus.CANCELLED, + }); }; /** @@ -340,10 +321,10 @@ export class SearchSessionService implements ISessionService { * @internal */ public trackId = async ( + deps: SearchSessionDependencies, searchRequest: IKibanaSearchRequest, searchId: string, - { sessionId, isStored, strategy }: ISearchOptions, - deps: SearchSessionDependencies + { sessionId, strategy }: ISearchOptions ) => { if (!sessionId || !searchId) return; this.logger.debug(`trackId | ${sessionId} | ${searchId}`); @@ -354,33 +335,34 @@ export class SearchSessionService implements ISessionService { status: SearchStatus.IN_PROGRESS, }; - // If there is already a saved object for this session, update it to include this request/ID. - // Otherwise, just update the in-memory mapping for this session for when the session is saved. - if (isStored) { - const attributes = { - idMapping: { [requestHash]: searchInfo }, - }; - await this.update(sessionId, attributes, deps); - } else { - const map = this.sessionSearchMap.get(sessionId) ?? { - insertTime: moment(), - retryCount: 0, - ids: new Map(), - }; - map.ids.set(requestHash, searchInfo); - this.sessionSearchMap.set(sessionId, map); - } + // Update the in-memory mapping for this session for when the session is saved. + const map = this.sessionSearchMap.get(sessionId) ?? { + insertTime: moment(), + retryCount: 0, + ids: new Map(), + }; + map.ids.set(requestHash, searchInfo); + this.sessionSearchMap.set(sessionId, map); }; + public async getSearchIdMapping(deps: SearchSessionDependencies, sessionId: string) { + const searchSession = await this.get(deps, sessionId); + const searchIdMapping = new Map(); + Object.values(searchSession.attributes.idMapping).forEach((requestInfo) => { + searchIdMapping.set(requestInfo.id, requestInfo.strategy); + }); + return searchIdMapping; + } + /** * 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 ( + deps: SearchSessionDependencies, searchRequest: IKibanaSearchRequest, - { sessionId, isStored, isRestore }: ISearchOptions, - deps: SearchSessionDependencies + { sessionId, isStored, isRestore }: ISearchOptions ) => { if (!sessionId) { throw new Error('Session ID is required'); @@ -390,7 +372,7 @@ export class SearchSessionService implements ISessionService { throw new Error('Get search ID is only supported when restoring a session'); } - const session = await this.get(sessionId, deps); + const session = await this.get(deps, sessionId); const requestHash = createRequestHash(searchRequest.params); if (!session.attributes.idMapping.hasOwnProperty(requestHash)) { throw new Error('No search ID in this session matching the given search request'); @@ -406,17 +388,15 @@ export class SearchSessionService implements ISessionService { }); const deps = { savedObjectsClient }; return { - search: ( - strategy: ISearchStrategy, - ...args: Parameters['search']> - ) => this.search(strategy, ...args, deps), - save: (sessionId: string, attributes: Partial) => - this.save(sessionId, attributes, deps), - get: (sessionId: string) => this.get(sessionId, deps), - find: (options: SavedObjectsFindOptions) => this.find(options, deps), - update: (sessionId: string, attributes: Partial) => - this.update(sessionId, attributes, deps), - delete: (sessionId: string) => this.delete(sessionId, deps), + getId: this.getId.bind(this, deps), + trackId: this.trackId.bind(this, deps), + getSearchIdMapping: this.getSearchIdMapping.bind(this, deps), + save: this.save.bind(this, deps), + get: this.get.bind(this, deps), + find: this.find.bind(this, deps), + update: this.update.bind(this, deps), + extend: this.extend.bind(this, deps), + cancel: this.cancel.bind(this, deps), }; }; }; diff --git a/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.test.ts b/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.test.ts index f07ee0508fa6c..f7c2343e0fccb 100644 --- a/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.test.ts +++ b/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.test.ts @@ -17,6 +17,7 @@ import { ISearchStrategy, SearchStrategyDependencies, } from 'src/plugins/data/server'; +import { createSearchSessionsClientMock } from '../../../../../../src/plugins/data/server/search/mocks'; import { InfraSource } from '../../lib/sources'; import { createInfraSourcesMock } from '../../lib/sources/mocks'; import { @@ -307,6 +308,7 @@ const createSearchStrategyDependenciesMock = (): SearchStrategyDependencies => ( uiSettingsClient: uiSettingsServiceMock.createClient(), esClient: elasticsearchServiceMock.createScopedClusterClient(), savedObjectsClient: savedObjectsClientMock.create(), + searchSessionsClient: createSearchSessionsClientMock(), }); // using the official data mock from within x-pack doesn't type-check successfully, diff --git a/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.test.ts b/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.test.ts index b3e1a31f73b7a..e9cdd09d84ac9 100644 --- a/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.test.ts +++ b/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.test.ts @@ -22,6 +22,7 @@ import { logEntrySearchRequestStateRT, logEntrySearchStrategyProvider, } from './log_entry_search_strategy'; +import { createSearchSessionsClientMock } from '../../../../../../src/plugins/data/server/search/mocks'; describe('LogEntry search strategy', () => { it('handles initial search requests', async () => { @@ -244,6 +245,7 @@ const createSearchStrategyDependenciesMock = (): SearchStrategyDependencies => ( uiSettingsClient: uiSettingsServiceMock.createClient(), esClient: elasticsearchServiceMock.createScopedClusterClient(), savedObjectsClient: savedObjectsClientMock.create(), + searchSessionsClient: createSearchSessionsClientMock(), }); // using the official data mock from within x-pack doesn't type-check successfully, diff --git a/x-pack/plugins/infra/server/types.ts b/x-pack/plugins/infra/server/types.ts index 2a30bf7cf093d..8731067e73080 100644 --- a/x-pack/plugins/infra/server/types.ts +++ b/x-pack/plugins/infra/server/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import type { RequestHandlerContext } from 'src/core/server'; -import type { DataApiRequestHandlerContext } from '../../../../src/plugins/data/server'; +import type { SearchRequestHandlerContext } from '../../../../src/plugins/data/server'; import { MlPluginSetup } from '../../ml/server'; export type MlSystem = ReturnType; @@ -27,5 +27,5 @@ export type InfraRequestHandlerContext = InfraMlRequestHandlerContext & */ export interface InfraPluginRequestHandlerContext extends RequestHandlerContext { infra: InfraRequestHandlerContext; - search: DataApiRequestHandlerContext; + search: SearchRequestHandlerContext; } diff --git a/x-pack/plugins/maps/server/mvt/get_tile.ts b/x-pack/plugins/maps/server/mvt/get_tile.ts index fb2b1b675dde0..30a22bfb6164a 100644 --- a/x-pack/plugins/maps/server/mvt/get_tile.ts +++ b/x-pack/plugins/maps/server/mvt/get_tile.ts @@ -8,8 +8,8 @@ import geojsonvt from 'geojson-vt'; // @ts-expect-error import vtpbf from 'vt-pbf'; -import { Logger, RequestHandlerContext } from 'src/core/server'; -import type { DataApiRequestHandlerContext } from 'src/plugins/data/server'; +import { Logger } from 'src/core/server'; +import type { DataRequestHandlerContext } from 'src/plugins/data/server'; import { Feature, FeatureCollection, Polygon } from 'geojson'; import { ES_GEO_FIELD_TYPE, @@ -45,7 +45,7 @@ export async function getGridTile({ z: number; geometryFieldName: string; index: string; - context: RequestHandlerContext & { search: DataApiRequestHandlerContext }; + context: DataRequestHandlerContext; logger: Logger; requestBody: any; requestType: RENDER_AS; @@ -125,7 +125,7 @@ export async function getTile({ z: number; geometryFieldName: string; index: string; - context: RequestHandlerContext & { search: DataApiRequestHandlerContext }; + context: DataRequestHandlerContext; logger: Logger; requestBody: any; geoFieldType: ES_GEO_FIELD_TYPE; diff --git a/x-pack/plugins/maps/server/mvt/mvt_routes.ts b/x-pack/plugins/maps/server/mvt/mvt_routes.ts index 65692619b333a..cf56000d61451 100644 --- a/x-pack/plugins/maps/server/mvt/mvt_routes.ts +++ b/x-pack/plugins/maps/server/mvt/mvt_routes.ts @@ -6,14 +6,9 @@ import rison from 'rison-node'; import { schema } from '@kbn/config-schema'; -import { - KibanaRequest, - KibanaResponseFactory, - Logger, - RequestHandlerContext, -} from 'src/core/server'; +import { KibanaRequest, KibanaResponseFactory, Logger } from 'src/core/server'; import { IRouter } from 'src/core/server'; -import type { DataApiRequestHandlerContext } from 'src/plugins/data/server'; +import type { DataRequestHandlerContext } from 'src/plugins/data/server'; import { MVT_GETTILE_API_PATH, API_ROOT_PATH, @@ -29,7 +24,7 @@ export function initMVTRoutes({ router, logger, }: { - router: IRouter; + router: IRouter; logger: Logger; }) { router.get( @@ -49,7 +44,7 @@ export function initMVTRoutes({ }, }, async ( - context: RequestHandlerContext & { search: DataApiRequestHandlerContext }, + context: DataRequestHandlerContext, request: KibanaRequest, unknown>, response: KibanaResponseFactory ) => { @@ -91,7 +86,7 @@ export function initMVTRoutes({ }, }, async ( - context: RequestHandlerContext & { search: DataApiRequestHandlerContext }, + context: DataRequestHandlerContext, request: KibanaRequest, unknown>, response: KibanaResponseFactory ) => { diff --git a/x-pack/test/api_integration/apis/search/session.ts b/x-pack/test/api_integration/apis/search/session.ts index ee2e4337adc95..96d601a00ff36 100644 --- a/x-pack/test/api_integration/apis/search/session.ts +++ b/x-pack/test/api_integration/apis/search/session.ts @@ -6,6 +6,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { SearchSessionStatus } from '../../../../plugins/data_enhanced/common'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); @@ -29,11 +30,11 @@ export default function ({ getService }: FtrProviderContext) { await supertest.get(`/internal/session/${sessionId}`).set('kbn-xsrf', 'foo').expect(200); }); - it('should fail to delete an unknown session', async () => { + it('should fail to cancel an unknown session', async () => { await supertest.delete(`/internal/session/123`).set('kbn-xsrf', 'foo').expect(404); }); - it('should create and delete a session', async () => { + it('should create and cancel a session', async () => { const sessionId = `my-session-${Math.random()}`; await supertest .post(`/internal/session`) @@ -49,7 +50,13 @@ export default function ({ getService }: FtrProviderContext) { await supertest.delete(`/internal/session/${sessionId}`).set('kbn-xsrf', 'foo').expect(200); - await supertest.get(`/internal/session/${sessionId}`).set('kbn-xsrf', 'foo').expect(404); + const resp = await supertest + .get(`/internal/session/${sessionId}`) + .set('kbn-xsrf', 'foo') + .expect(200); + + const { status } = resp.body.attributes; + expect(status).to.equal(SearchSessionStatus.CANCELLED); }); it('should sync search ids into session', async () => { @@ -123,6 +130,39 @@ export default function ({ getService }: FtrProviderContext) { expect(idMappings).to.contain(id1); expect(idMappings).to.contain(id2); }); + + it('should create and extend a session', async () => { + const sessionId = `my-session-${Math.random()}`; + await supertest + .post(`/internal/session`) + .set('kbn-xsrf', 'foo') + .send({ + sessionId, + name: 'My Session', + appId: 'discover', + expires: '123', + urlGeneratorId: 'discover', + }) + .expect(200); + + await supertest + .post(`/internal/session/${sessionId}/_extend`) + .set('kbn-xsrf', 'foo') + .send({ + expires: '2021-02-26T21:02:43.742Z', + }) + .expect(200); + }); + }); + + it('should fail to extend a nonexistent session', async () => { + await supertest + .post(`/internal/session/123/_extend`) + .set('kbn-xsrf', 'foo') + .send({ + expires: '2021-02-26T21:02:43.742Z', + }) + .expect(404); }); }); }