[Search][Sessions] Rename Background Sessions to Search Sessions (#87500)

* Rename Background Sessions to Search Sessions (with a send to background action)

* doc

* doc

* jest fun

* rename rfc

* translations

* update so name in features

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Liza Katz 2021-01-07 17:28:27 +02:00 committed by GitHub
parent 130a8e766e
commit 91aed6f961
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
54 changed files with 417 additions and 472 deletions

View file

@ -18,6 +18,6 @@ export interface ISearchSetup
| --- | --- | --- | | --- | --- | --- |
| [aggs](./kibana-plugin-plugins-data-public.isearchsetup.aggs.md) | <code>AggsSetup</code> | | | [aggs](./kibana-plugin-plugins-data-public.isearchsetup.aggs.md) | <code>AggsSetup</code> | |
| [session](./kibana-plugin-plugins-data-public.isearchsetup.session.md) | <code>ISessionService</code> | Current session management [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) | | [session](./kibana-plugin-plugins-data-public.isearchsetup.session.md) | <code>ISessionService</code> | Current session management [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) |
| [sessionsClient](./kibana-plugin-plugins-data-public.isearchsetup.sessionsclient.md) | <code>ISessionsClient</code> | Background search sessions SO CRUD [ISessionsClient](./kibana-plugin-plugins-data-public.isessionsclient.md) | | [sessionsClient](./kibana-plugin-plugins-data-public.isearchsetup.sessionsclient.md) | <code>ISessionsClient</code> | Search sessions SO CRUD [ISessionsClient](./kibana-plugin-plugins-data-public.isessionsclient.md) |
| [usageCollector](./kibana-plugin-plugins-data-public.isearchsetup.usagecollector.md) | <code>SearchUsageCollector</code> | | | [usageCollector](./kibana-plugin-plugins-data-public.isearchsetup.usagecollector.md) | <code>SearchUsageCollector</code> | |

View file

@ -4,7 +4,7 @@
## ISearchSetup.sessionsClient property ## ISearchSetup.sessionsClient property
Background search sessions SO CRUD [ISessionsClient](./kibana-plugin-plugins-data-public.isessionsclient.md) Search sessions SO CRUD [ISessionsClient](./kibana-plugin-plugins-data-public.isessionsclient.md)
<b>Signature:</b> <b>Signature:</b>

View file

@ -20,6 +20,6 @@ export interface ISearchStart
| [search](./kibana-plugin-plugins-data-public.isearchstart.search.md) | <code>ISearchGeneric</code> | low level search [ISearchGeneric](./kibana-plugin-plugins-data-public.isearchgeneric.md) | | [search](./kibana-plugin-plugins-data-public.isearchstart.search.md) | <code>ISearchGeneric</code> | low level search [ISearchGeneric](./kibana-plugin-plugins-data-public.isearchgeneric.md) |
| [searchSource](./kibana-plugin-plugins-data-public.isearchstart.searchsource.md) | <code>ISearchStartSearchSource</code> | high level search [ISearchStartSearchSource](./kibana-plugin-plugins-data-public.isearchstartsearchsource.md) | | [searchSource](./kibana-plugin-plugins-data-public.isearchstart.searchsource.md) | <code>ISearchStartSearchSource</code> | high level search [ISearchStartSearchSource](./kibana-plugin-plugins-data-public.isearchstartsearchsource.md) |
| [session](./kibana-plugin-plugins-data-public.isearchstart.session.md) | <code>ISessionService</code> | Current session management [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) | | [session](./kibana-plugin-plugins-data-public.isearchstart.session.md) | <code>ISessionService</code> | Current session management [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) |
| [sessionsClient](./kibana-plugin-plugins-data-public.isearchstart.sessionsclient.md) | <code>ISessionsClient</code> | Background search sessions SO CRUD [ISessionsClient](./kibana-plugin-plugins-data-public.isessionsclient.md) | | [sessionsClient](./kibana-plugin-plugins-data-public.isearchstart.sessionsclient.md) | <code>ISessionsClient</code> | Search sessions SO CRUD [ISessionsClient](./kibana-plugin-plugins-data-public.isessionsclient.md) |
| [showError](./kibana-plugin-plugins-data-public.isearchstart.showerror.md) | <code>(e: Error) =&gt; void</code> | | | [showError](./kibana-plugin-plugins-data-public.isearchstart.showerror.md) | <code>(e: Error) =&gt; void</code> | |

View file

@ -4,7 +4,7 @@
## ISearchStart.sessionsClient property ## ISearchStart.sessionsClient property
Background search sessions SO CRUD [ISessionsClient](./kibana-plugin-plugins-data-public.isessionsclient.md) Search sessions SO CRUD [ISessionsClient](./kibana-plugin-plugins-data-public.isessionsclient.md)
<b>Signature:</b> <b>Signature:</b>

View file

@ -34,7 +34,7 @@
| [KBN\_FIELD\_TYPES](./kibana-plugin-plugins-data-public.kbn_field_types.md) | \* | | [KBN\_FIELD\_TYPES](./kibana-plugin-plugins-data-public.kbn_field_types.md) | \* |
| [METRIC\_TYPES](./kibana-plugin-plugins-data-public.metric_types.md) | | | [METRIC\_TYPES](./kibana-plugin-plugins-data-public.metric_types.md) | |
| [QuerySuggestionTypes](./kibana-plugin-plugins-data-public.querysuggestiontypes.md) | | | [QuerySuggestionTypes](./kibana-plugin-plugins-data-public.querysuggestiontypes.md) | |
| [SessionState](./kibana-plugin-plugins-data-public.sessionstate.md) | Possible state that current session can be in | | [SearchSessionState](./kibana-plugin-plugins-data-public.searchsessionstate.md) | Possible state that current session can be in |
| [SortDirection](./kibana-plugin-plugins-data-public.sortdirection.md) | | | [SortDirection](./kibana-plugin-plugins-data-public.sortdirection.md) | |
| [TimeoutErrorMode](./kibana-plugin-plugins-data-public.timeouterrormode.md) | | | [TimeoutErrorMode](./kibana-plugin-plugins-data-public.timeouterrormode.md) | |
@ -90,7 +90,7 @@
| [SavedQueryService](./kibana-plugin-plugins-data-public.savedqueryservice.md) | | | [SavedQueryService](./kibana-plugin-plugins-data-public.savedqueryservice.md) | |
| [SearchError](./kibana-plugin-plugins-data-public.searcherror.md) | | | [SearchError](./kibana-plugin-plugins-data-public.searcherror.md) | |
| [SearchInterceptorDeps](./kibana-plugin-plugins-data-public.searchinterceptordeps.md) | | | [SearchInterceptorDeps](./kibana-plugin-plugins-data-public.searchinterceptordeps.md) | |
| [SearchSessionInfoProvider](./kibana-plugin-plugins-data-public.searchsessioninfoprovider.md) | Provide info about current search session to be stored in backgroundSearch saved object | | [SearchSessionInfoProvider](./kibana-plugin-plugins-data-public.searchsessioninfoprovider.md) | Provide info about current search session to be stored in the Search Session saved object |
| [SearchSourceFields](./kibana-plugin-plugins-data-public.searchsourcefields.md) | search source fields | | [SearchSourceFields](./kibana-plugin-plugins-data-public.searchsourcefields.md) | search source fields |
| [TabbedAggColumn](./kibana-plugin-plugins-data-public.tabbedaggcolumn.md) | \* | | [TabbedAggColumn](./kibana-plugin-plugins-data-public.tabbedaggcolumn.md) | \* |
| [TabbedTable](./kibana-plugin-plugins-data-public.tabbedtable.md) | \* | | [TabbedTable](./kibana-plugin-plugins-data-public.tabbedtable.md) | \* |

View file

@ -4,7 +4,7 @@
## SearchSessionInfoProvider.getName property ## SearchSessionInfoProvider.getName property
User-facing name of the session. e.g. will be displayed in background sessions management list User-facing name of the session. e.g. will be displayed in saved Search Sessions management list
<b>Signature:</b> <b>Signature:</b>

View file

@ -4,7 +4,7 @@
## SearchSessionInfoProvider interface ## SearchSessionInfoProvider interface
Provide info about current search session to be stored in backgroundSearch saved object Provide info about current search session to be stored in the Search Session saved object
<b>Signature:</b> <b>Signature:</b>
@ -16,6 +16,6 @@ export interface SearchSessionInfoProvider<ID extends UrlGeneratorId = UrlGenera
| Property | Type | Description | | Property | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| [getName](./kibana-plugin-plugins-data-public.searchsessioninfoprovider.getname.md) | <code>() =&gt; Promise&lt;string&gt;</code> | User-facing name of the session. e.g. will be displayed in background sessions management list | | [getName](./kibana-plugin-plugins-data-public.searchsessioninfoprovider.getname.md) | <code>() =&gt; Promise&lt;string&gt;</code> | User-facing name of the session. e.g. will be displayed in saved Search Sessions management list |
| [getUrlGeneratorData](./kibana-plugin-plugins-data-public.searchsessioninfoprovider.geturlgeneratordata.md) | <code>() =&gt; Promise&lt;{</code><br/><code> urlGeneratorId: ID;</code><br/><code> initialState: UrlGeneratorStateMapping[ID]['State'];</code><br/><code> restoreState: UrlGeneratorStateMapping[ID]['State'];</code><br/><code> }&gt;</code> | | | [getUrlGeneratorData](./kibana-plugin-plugins-data-public.searchsessioninfoprovider.geturlgeneratordata.md) | <code>() =&gt; Promise&lt;{</code><br/><code> urlGeneratorId: ID;</code><br/><code> initialState: UrlGeneratorStateMapping[ID]['State'];</code><br/><code> restoreState: UrlGeneratorStateMapping[ID]['State'];</code><br/><code> }&gt;</code> | |

View file

@ -1,25 +1,25 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. --> <!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) &gt; [SessionState](./kibana-plugin-plugins-data-public.sessionstate.md) [Home](./index.md) &gt; [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) &gt; [SearchSessionState](./kibana-plugin-plugins-data-public.searchsessionstate.md)
## SessionState enum ## SearchSessionState enum
Possible state that current session can be in Possible state that current session can be in
<b>Signature:</b> <b>Signature:</b>
```typescript ```typescript
export declare enum SessionState export declare enum SearchSessionState
``` ```
## Enumeration Members ## Enumeration Members
| Member | Value | Description | | Member | Value | Description |
| --- | --- | --- | | --- | --- | --- |
| BackgroundCompleted | <code>&quot;backgroundCompleted&quot;</code> | Page load completed with background session created. | | BackgroundCompleted | <code>&quot;backgroundCompleted&quot;</code> | Page load completed with search session created. |
| BackgroundLoading | <code>&quot;backgroundLoading&quot;</code> | Search request was sent to the background. The page is loading in background. | | BackgroundLoading | <code>&quot;backgroundLoading&quot;</code> | Search session was sent to the background. The page is loading in background. |
| Canceled | <code>&quot;canceled&quot;</code> | Current session requests where explicitly canceled by user Displaying none or partial results | | Canceled | <code>&quot;canceled&quot;</code> | Current session requests where explicitly canceled by user Displaying none or partial results |
| Completed | <code>&quot;completed&quot;</code> | No action was taken and the page completed loading without background session creation. | | Completed | <code>&quot;completed&quot;</code> | No action was taken and the page completed loading without search session creation. |
| Loading | <code>&quot;loading&quot;</code> | Pending search request has not been sent to the background yet | | Loading | <code>&quot;loading&quot;</code> | Pending search request has not been sent to the background yet |
| None | <code>&quot;none&quot;</code> | Session is not active, e.g. didn't start | | None | <code>&quot;none&quot;</code> | Session is not active, e.g. didn't start |
| Restored | <code>&quot;restored&quot;</code> | Revisiting the page after background completion | | Restored | <code>&quot;restored&quot;</code> | Revisiting the page after background completion |

View file

@ -295,7 +295,7 @@ export const SearchExamplesApp = ({
<EuiFormLabel>Index Pattern</EuiFormLabel> <EuiFormLabel>Index Pattern</EuiFormLabel>
<IndexPatternSelect <IndexPatternSelect
placeholder={i18n.translate( placeholder={i18n.translate(
'backgroundSessionExample.selectIndexPatternPlaceholder', 'searchSessionExample.selectIndexPatternPlaceholder',
{ {
defaultMessage: 'Select index pattern', defaultMessage: 'Select index pattern',
} }

View file

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Before After
Before After

View file

@ -5,19 +5,19 @@
- Architecture diagram: https://app.lucidchart.com/documents/edit/cf35b512-616a-4734-bc72-43dde70dbd44/0_0 - Architecture diagram: https://app.lucidchart.com/documents/edit/cf35b512-616a-4734-bc72-43dde70dbd44/0_0
- Mockups: https://www.figma.com/proto/FD2M7MUpLScJKOyYjfbmev/ES-%2F-Query-Management-v4?node-id=440%3A1&viewport=984%2C-99%2C0.09413627535104752&scaling=scale-down - Mockups: https://www.figma.com/proto/FD2M7MUpLScJKOyYjfbmev/ES-%2F-Query-Management-v4?node-id=440%3A1&viewport=984%2C-99%2C0.09413627535104752&scaling=scale-down
- Old issue: https://github.com/elastic/kibana/issues/53335 - Old issue: https://github.com/elastic/kibana/issues/53335
- Background search roadmap: https://github.com/elastic/kibana/issues/61738 - Search Sessions roadmap: https://github.com/elastic/kibana/issues/61738
- POC: https://github.com/elastic/kibana/pull/64641 - POC: https://github.com/elastic/kibana/pull/64641
# Summary # Summary
Background Sessions will enable Kibana applications and solutions to start a group of related search requests (such as those coming from a single load of a dashboard or SIEM timeline), navigate away or close the browser, then retrieve the results when they have completed. Search Sessions will enable Kibana applications and solutions to start a group of related search requests (such as those coming from a single load of a dashboard or SIEM timeline), navigate away or close the browser, then retrieve the results when they have completed.
# Basic example # Basic example
At its core, background sessions are enabled via several new APIs, that: At its core, search sessions are enabled via several new APIs, that:
- Start a session, associating multiple search requests with a single entity - Start a session, associating multiple search requests with a single entity
- Store the session (and continue search requests in the background) - Store the session (and continue search requests in the background)
- Restore the background session - Restore the saved search session
```ts ```ts
const searchService = dataPluginStart.search; const searchService = dataPluginStart.search;
@ -26,7 +26,7 @@ if (appState.sessionId) {
// If we are restoring a session, set the session ID in the search service // If we are restoring a session, set the session ID in the search service
searchService.session.restore(sessionId); searchService.session.restore(sessionId);
} else { } else {
// Otherwise, start a new background session to associate our search requests // Otherwise, start a new search session to associate our search requests
appState.sessionId = searchService.session.start(); appState.sessionId = searchService.session.start();
} }
@ -41,7 +41,7 @@ const response$ = await searchService.search(request);
// Calling `session.store()`, creates a saved object for this session, allowing the user to navigate away. // Calling `session.store()`, creates a saved object for this session, allowing the user to navigate away.
// The session object will be saved with all async search IDs that were executed so far. // The session object will be saved with all async search IDs that were executed so far.
// Any follow up searches executed with this sessionId will be saved into this object as well. // Any follow up searches executed with this sessionId will be saved into this object as well.
const backgroundSession = await searchService.session.store(); const searchSession = await searchService.session.store();
``` ```
# Motivation # Motivation
@ -73,20 +73,20 @@ We call this entity a `session`, and when a user decides that they want to conti
This diagram matches any case where `data.search` is called from the front end: This diagram matches any case where `data.search` is called from the front end:
![image](../images/background_sessions_client.png) ![image](../images/search_sessions_client.png)
### Server side search ### Server side search
This case happens if the server is the one to invoke the `data.search` endpoint, for example with TSVB. This case happens if the server is the one to invoke the `data.search` endpoint, for example with TSVB.
![image](../images/background_sessions_server.png) ![image](../images/search_sessions_server.png)
## Data and Saved Objects ## Data and Saved Objects
### Background Session Status ### Search Session Status
```ts ```ts
export enum BackgroundSessionStatus { export enum SearchSessionStatus {
Running, // The session has at least one running search ID associated with it. Running, // The session has at least one running search ID associated with it.
Done, // All search IDs associated with this session have completed. Done, // All search IDs associated with this session have completed.
Error, // At least one search ID associated with this session had an error. Error, // At least one search ID associated with this session had an error.
@ -96,27 +96,27 @@ export enum BackgroundSessionStatus {
### Saved Object Structure ### Saved Object Structure
The saved object created for a background session will be scoped to a single space, and will be a `hidden` saved object The saved object created for a search session will be scoped to a single space, and will be a `hidden` saved object
(so that it doesn't show in the management listings). We will provide a separate interface for users to manage their own (so that it doesn't show in the management listings). We will provide a separate interface for users to manage their own
background sessions (which will use the `list`, `expire`, and `extend` methods described below, which will be restricted saved search sessions (which will use the `list`, `expire`, and `extend` methods described below, which will be restricted
per-user). per-user).
```ts ```ts
interface BackgroundSessionAttributes extends SavedObjectAttributes { interface SearchSessionAttributes extends SavedObjectAttributes {
sessionId: string; sessionId: string;
userId: string; // Something unique to the user who generated this session, like username/realm-name/realm-type userId: string; // Something unique to the user who generated this session, like username/realm-name/realm-type
status: BackgroundSessionStatus; status: SearchSessionStatus;
name: string; name: string;
creation: Date; creation: Date;
expiration: Date; expiration: Date;
idMapping: { [key: string]: string }; idMapping: { [key: string]: string };
url: string; // A URL relative to the Kibana root to retrieve the results of a completed background session (and/or to return to an incomplete view) url: string; // A URL relative to the Kibana root to retrieve the results of a completed search session (and/or to return to an incomplete view)
metadata: { [key: string]: any } // Any data the specific application requires to restore a background session view metadata: { [key: string]: any } // Any data the specific application requires to restore a search session view
} }
``` ```
The URL that is provided will need to be generated by the specific application implementing background sessions. We The URL that is provided will need to be generated by the specific application implementing search sessions. We
recommend using the URL generator to ensure that URLs are backwards-compatible since background sessions may exist as recommend using the URL generator to ensure that URLs are backwards-compatible since search sessions may exist as
long as a user continues to extend the expiration. long as a user continues to extend the expiration.
## Frontend Services ## Frontend Services
@ -153,10 +153,10 @@ interface ISessionService {
* @param sessionId Session ID to store. Probably retrieved from `sessionService.get()`. * @param sessionId Session ID to store. Probably retrieved from `sessionService.get()`.
* @param name A display name for the session. * @param name A display name for the session.
* @param url TODO: is the URL provided here? How? * @param url TODO: is the URL provided here? How?
* @returns The stored `BackgroundSessionAttributes` object * @returns The stored `SearchSessionAttributes` object
* @throws Throws an error in OSS. * @throws Throws an error in OSS.
*/ */
store: (sessionId: string, name: string, url: string) => Promise<BackgroundSessionAttributes> store: (sessionId: string, name: string, url: string) => Promise<SearchSessionAttributes>
/** /**
* @returns Is the current session stored (i.e. is there a saved object corresponding with this sessionId). * @returns Is the current session stored (i.e. is there a saved object corresponding with this sessionId).
@ -188,17 +188,17 @@ interface ISessionService {
/** /**
* @param sessionId the ID of the session to retrieve the saved object. * @param sessionId the ID of the session to retrieve the saved object.
* @returns a filtered list of BackgroundSessionAttributes objects. * @returns a filtered list of SearchSessionAttributes objects.
* @throws Throws an error in OSS. * @throws Throws an error in OSS.
*/ */
get: (sessionId: string) => Promise<BackgroundSessionAttributes> get: (sessionId: string) => Promise<SearchSessionAttributes>
/** /**
* @param options The options to query for specific background session saved objects. * @param options The options to query for specific search session saved objects.
* @returns a filtered list of BackgroundSessionAttributes objects. * @returns a filtered list of SearchSessionAttributes objects.
* @throws Throws an error in OSS. * @throws Throws an error in OSS.
*/ */
list: (options: SavedObjectsFindOptions) => Promise<BackgroundSessionAttributes[]> list: (options: SavedObjectsFindOptions) => Promise<SearchSessionAttributes[]>
/** /**
* Clears out any session info as well as the current session. Called internally whenever the user navigates * Clears out any session info as well as the current session. Called internally whenever the user navigates
@ -241,12 +241,12 @@ attempt to find the correct id within the saved object, and use it to retrieve t
```ts ```ts
interface ISessionService { interface ISessionService {
/** /**
* Adds a search ID to a Background Session, if it exists. * Adds a search ID to a Search Session, if it exists.
* Also extends the expiration of the search ID to match the session's expiration. * Also extends the expiration of the search ID to match the session's expiration.
* @param request * @param request
* @param sessionId * @param sessionId
* @param searchId * @param searchId
* @returns true if id was added, false if Background Session doesn't exist or if there was an error while updating. * @returns true if id was added, false if Search Session doesn't exist or if there was an error while updating.
* @throws an error if `searchId` already exists in the mapping for this `sessionId` * @throws an error if `searchId` already exists in the mapping for this `sessionId`
*/ */
trackSearchId: ( trackSearchId: (
@ -256,21 +256,21 @@ interface ISessionService {
) => Promise<boolean> ) => Promise<boolean>
/** /**
* Get a Background Session object. * Get a Search Session object.
* @param request * @param request
* @param sessionId * @param sessionId
* @returns the Background Session object if exists, or undefined. * @returns the Search Session object if exists, or undefined.
*/ */
get: async ( get: async (
request: KibanaRequest, request: KibanaRequest,
sessionId: string sessionId: string
) => Promise<BackgroundSessionAttributes?> ) => Promise<SearchSessionAttributes?>
/** /**
* Get a searchId from a Background Session object. * Get a searchId from a Search Session object.
* @param request * @param request
* @param sessionId * @param sessionId
* @returns the searchID if exists on the Background Session, or undefined. * @returns the searchID if exists on the Search Session, or undefined.
*/ */
getSearchId: async ( getSearchId: async (
request: KibanaRequest, request: KibanaRequest,
@ -283,7 +283,7 @@ interface ISessionService {
* @param sessionId Session ID to store. Probably retrieved from `sessionService.get()`. * @param sessionId Session ID to store. Probably retrieved from `sessionService.get()`.
* @param searchIdMap A mapping of hashed requests mapped to the corresponding searchId. * @param searchIdMap A mapping of hashed requests mapped to the corresponding searchId.
* @param url TODO: is the URL provided here? How? * @param url TODO: is the URL provided here? How?
* @returns The stored `BackgroundSessionAttributes` object * @returns The stored `SearchSessionAttributes` object
* @throws Throws an error in OSS. * @throws Throws an error in OSS.
* @internal (Consumers should use searchInterceptor.sendToBackground()) * @internal (Consumers should use searchInterceptor.sendToBackground())
*/ */
@ -293,7 +293,7 @@ interface ISessionService {
name: string, name: string,
url: string, url: string,
searchIdMapping?: Record<string, string> searchIdMapping?: Record<string, string>
) => Promise<BackgroundSessionAttributes> ) => Promise<SearchSessionAttributes>
/** /**
* Mark a session as and all associated searchIds as expired. * Mark a session as and all associated searchIds as expired.
@ -322,7 +322,7 @@ interface ISessionService {
) => Promise<boolean> ) => Promise<boolean>
/** /**
* Get a list of background session objects. * Get a list of Search Session objects.
* @param request * @param request
* @param sessionId * @param sessionId
* @returns success status * @returns success status
@ -330,7 +330,7 @@ interface ISessionService {
*/ */
list: async ( list: async (
request: KibanaRequest, request: KibanaRequest,
) => Promise<BackgroundSessionAttributes[]> ) => Promise<SearchSessionAttributes[]>
/** /**
* Update the status of a given session * Update the status of a given session
@ -343,7 +343,7 @@ interface ISessionService {
updateStatus: async ( updateStatus: async (
request: KibanaRequest, request: KibanaRequest,
sessionId: string, sessionId: string,
status: BackgroundSessionStatus status: SearchSessionStatus
) => Promise<boolean> ) => Promise<boolean>
} }
@ -381,13 +381,13 @@ Each route exposes the corresponding method from the Session Service (used only
### Search Strategy Integration ### Search Strategy Integration
If the `EnhancedEsSearchStrategy` receives a `restore` option, it will attempt reloading data using the Background Session saved object matching the provided `sessionId`. If there are any errors during that process, the strategy will return an error response and *not attempt to re-run the request. If the `EnhancedEsSearchStrategy` receives a `restore` option, it will attempt reloading data using the Search Session saved object matching the provided `sessionId`. If there are any errors during that process, the strategy will return an error response and *not attempt to re-run the request.
The strategy will track the asyncId on the server side, if `trackId` option is provided. The strategy will track the asyncId on the server side, if `trackId` option is provided.
### Monitoring Service ### Monitoring Service
The `data` plugin will register a task with the task manager, periodically monitoring the status of incomplete background sessions. The `data` plugin will register a task with the task manager, periodically monitoring the status of incomplete search sessions.
It will query the list of all incomplete sessions, and check the status of each search that is executing. If the search requests are all complete, it will update the corresponding saved object to have a `status` of `complete`. If any of the searches return an error, it will update the saved object to an `error` state. If the search requests have expired, it will update the saved object to an `expired` state. Expired sessions will be purged once they are older than the time definedby the `EXPIRED_SESSION_TTL` advanced setting. It will query the list of all incomplete sessions, and check the status of each search that is executing. If the search requests are all complete, it will update the corresponding saved object to have a `status` of `complete`. If any of the searches return an error, it will update the saved object to an `error` state. If the search requests have expired, it will update the saved object to an `expired` state. Expired sessions will be purged once they are older than the time definedby the `EXPIRED_SESSION_TTL` advanced setting.
@ -405,23 +405,23 @@ There are two potential scenarios:
Both scenarios require careful attention during the UI design and implementation. Both scenarios require careful attention during the UI design and implementation.
The former can be resolved by clearly displaying the creation time of the restored Background Session. We could also attempt translating relative dates to absolute one's, but this might be challenging as relative dates may appear deeply nested within the DSL. The former can be resolved by clearly displaying the creation time of the restored Search Session. We could also attempt translating relative dates to absolute one's, but this might be challenging as relative dates may appear deeply nested within the DSL.
The latter case happens at the moment for the timepicker only: The relative date is being translated each time into an absolute one, before being sent to Elasticsearch. In order to avoid issues, we'll have to make sure that restore URLs are generated with an absolute date, to make sure they are restored correctly. The latter case happens at the moment for the timepicker only: The relative date is being translated each time into an absolute one, before being sent to Elasticsearch. In order to avoid issues, we'll have to make sure that restore URLs are generated with an absolute date, to make sure they are restored correctly.
#### Changing a restored session #### Changing a restored session
If you have restored a Background Session, making any type of change to it (time range, filters, etc.) will trigger new (potentially long) searches. There should be a clear indication in the UI that the data is no longer stored. A user then may choose to send it to background, resulting in a new Background Session being saved. If you have restored a Search Session, making any type of change to it (time range, filters, etc.) will trigger new (potentially long) searches. There should be a clear indication in the UI that the data is no longer stored. A user then may choose to send it to background, resulting in a new Search Session being saved.
#### Loading an errored \ expired \ canceled session #### Loading an errored \ expired \ canceled session
When trying to restore a Background Session, if any of the requests hashes don't match the ones saved, or if any of the saved async search IDs are expired, a meaningful error code will be returned by the server **by those requests**. It is each application's responsibility to handle these errors appropriately. When trying to restore a Search Session, if any of the requests hashes don't match the ones saved, or if any of the saved async search IDs are expired, a meaningful error code will be returned by the server **by those requests**. It is each application's responsibility to handle these errors appropriately.
In such a scenario, the session will be partially restored. In such a scenario, the session will be partially restored.
#### Extending Expiration #### Extending Expiration
Sessions are given an expiration date defined in an advanced setting (5 days by default). This expiration date is measured from the time the Background Session is saved, and it includes the time it takes to generate the results. Sessions are given an expiration date defined in an advanced setting (5 days by default). This expiration date is measured from the time the Search Session is saved, and it includes the time it takes to generate the results.
A session's expiration date may be extended indefinitely. However, if a session was canceled or has already expired, it needs to be re-run. A session's expiration date may be extended indefinitely. However, if a session was canceled or has already expired, it needs to be re-run.
@ -444,7 +444,7 @@ so we feel comfortable moving forward with this approach.
Two potential drawbacks stem from storing things in server memory. If a Kibana server is restarted, in-memory results Two potential drawbacks stem from storing things in server memory. If a Kibana server is restarted, in-memory results
will be lost. (This can be an issue if a search request has started, and the user has sent to background, but the will be lost. (This can be an issue if a search request has started, and the user has sent to background, but the
background session saved object has not yet been updated with the search request ID.) In such cases, the user interface search session saved object has not yet been updated with the search request ID.) In such cases, the user interface
will need to indicate errors for requests that were not stored in the saved object. will need to indicate errors for requests that were not stored in the saved object.
There is also the consideration of the memory footprint of the Kibana server; however, since There is also the consideration of the memory footprint of the Kibana server; however, since
@ -452,7 +452,7 @@ we are only storing a hash of the request and search request ID, and are periodi
Services and Routes), we do not anticipate the footprint to increase significantly. Services and Routes), we do not anticipate the footprint to increase significantly.
The results of search requests that have been sent to the background will be stored in Elasticsearch for several days, The results of search requests that have been sent to the background will be stored in Elasticsearch for several days,
even if they will only be retrieved once. This will be mitigated by allowing the user manually delete a background even if they will only be retrieved once. This will be mitigated by allowing the user manually delete a search
session object after it has been accessed. session object after it has been accessed.
# Alternatives # Alternatives
@ -463,7 +463,7 @@ What other designs have been considered? What is the impact of not doing this?
(See "Basic example" above.) (See "Basic example" above.)
Any application or solution that uses the `data` plugin `search` services will be able to facilitate background sessions Any application or solution that uses the `data` plugin `search` services will be able to facilitate search sessions
fairly simply. The public side will need to create/clear sessions when appropriate, and ensure the `sessionId` is sent fairly simply. The public side will need to create/clear sessions when appropriate, and ensure the `sessionId` is sent
with all search requests. It will also need to ensure that any necessary application data, as well as a `restoreUrl` is with all search requests. It will also need to ensure that any necessary application data, as well as a `restoreUrl` is
sent when creating the saved object. sent when creating the saved object.

View file

@ -385,7 +385,7 @@ export {
SearchRequest, SearchRequest,
SearchSourceFields, SearchSourceFields,
SortDirection, SortDirection,
SessionState, SearchSessionState,
// expression functions and types // expression functions and types
EsdslExpressionFunctionDefinition, EsdslExpressionFunctionDefinition,
EsRawResponseExpressionTypeDefinition, EsRawResponseExpressionTypeDefinition,

View file

@ -2311,6 +2311,17 @@ export interface SearchSessionInfoProvider<ID extends UrlGeneratorId = UrlGenera
}>; }>;
} }
// @public
export enum SearchSessionState {
BackgroundCompleted = "backgroundCompleted",
BackgroundLoading = "backgroundLoading",
Canceled = "canceled",
Completed = "completed",
Loading = "loading",
None = "none",
Restored = "restored"
}
// @public (undocumented) // @public (undocumented)
export class SearchSource { export class SearchSource {
// Warning: (ae-forgotten-export) The symbol "SearchSourceDependencies" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "SearchSourceDependencies" needs to be exported by the entry point index.d.ts
@ -2418,17 +2429,6 @@ export class SearchTimeoutError extends KbnError {
mode: TimeoutErrorMode; mode: TimeoutErrorMode;
} }
// @public
export enum SessionState {
BackgroundCompleted = "backgroundCompleted",
BackgroundLoading = "backgroundLoading",
Canceled = "canceled",
Completed = "completed",
Loading = "loading",
None = "none",
Restored = "restored"
}
// Warning: (ae-missing-release-tag) "SortDirection" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // Warning: (ae-missing-release-tag) "SortDirection" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
// //
// @public (undocumented) // @public (undocumented)
@ -2620,7 +2620,7 @@ export const UI_SETTINGS: {
// src/plugins/data/public/index.ts:433:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:433:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:436:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:436:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/query/state_sync/connect_to_query_state.ts:45:5 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:45:5 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/search/session/session_service.ts:46:5 - (ae-forgotten-export) The symbol "UrlGeneratorStateMapping" needs to be exported by the entry point index.d.ts // src/plugins/data/public/search/session/session_service.ts:50:5 - (ae-forgotten-export) The symbol "UrlGeneratorStateMapping" needs to be exported by the entry point index.d.ts
// (No @packageDocumentation comment for this package) // (No @packageDocumentation comment for this package)

View file

@ -45,7 +45,7 @@ export {
SessionService, SessionService,
ISessionService, ISessionService,
SearchSessionInfoProvider, SearchSessionInfoProvider,
SessionState, SearchSessionState,
SessionsClient, SessionsClient,
ISessionsClient, ISessionsClient,
} from './session'; } from './session';

View file

@ -18,5 +18,5 @@
*/ */
export { SessionService, ISessionService, SearchSessionInfoProvider } from './session_service'; export { SessionService, ISessionService, SearchSessionInfoProvider } from './session_service';
export { SessionState } from './session_state'; export { SearchSessionState } from './search_session_state';
export { SessionsClient, ISessionsClient } from './sessions_client'; export { SessionsClient, ISessionsClient } from './sessions_client';

View file

@ -20,7 +20,7 @@
import { BehaviorSubject, Subject } from 'rxjs'; import { BehaviorSubject, Subject } from 'rxjs';
import { ISessionsClient } from './sessions_client'; import { ISessionsClient } from './sessions_client';
import { ISessionService } from './session_service'; import { ISessionService } from './session_service';
import { SessionState } from './session_state'; import { SearchSessionState } from './search_session_state';
export function getSessionsClientMock(): jest.Mocked<ISessionsClient> { export function getSessionsClientMock(): jest.Mocked<ISessionsClient> {
return { return {
@ -39,7 +39,7 @@ export function getSessionServiceMock(): jest.Mocked<ISessionService> {
restore: jest.fn(), restore: jest.fn(),
getSessionId: jest.fn(), getSessionId: jest.fn(),
getSession$: jest.fn(() => new BehaviorSubject(undefined).asObservable()), getSession$: jest.fn(() => new BehaviorSubject(undefined).asObservable()),
state$: new BehaviorSubject<SessionState>(SessionState.None).asObservable(), state$: new BehaviorSubject<SearchSessionState>(SearchSessionState.None).asObservable(),
setSearchSessionInfoProvider: jest.fn(), setSearchSessionInfoProvider: jest.fn(),
trackSearch: jest.fn((searchDescriptor) => () => {}), trackSearch: jest.fn((searchDescriptor) => () => {}),
destroy: jest.fn(), destroy: jest.fn(),

View file

@ -17,7 +17,7 @@
* under the License. * under the License.
*/ */
import { createSessionStateContainer, SessionState } from './session_state'; import { createSessionStateContainer, SearchSessionState } from './search_session_state';
describe('Session state container', () => { describe('Session state container', () => {
const { stateContainer: state } = createSessionStateContainer(); const { stateContainer: state } = createSessionStateContainer();
@ -29,7 +29,7 @@ describe('Session state container', () => {
describe('transitions', () => { describe('transitions', () => {
test('start', () => { test('start', () => {
state.transitions.start(); state.transitions.start();
expect(state.selectors.getState()).toBe(SessionState.None); expect(state.selectors.getState()).toBe(SearchSessionState.None);
expect(state.get().sessionId).not.toBeUndefined(); expect(state.get().sessionId).not.toBeUndefined();
}); });
@ -39,22 +39,22 @@ describe('Session state container', () => {
state.transitions.start(); state.transitions.start();
state.transitions.trackSearch({}); state.transitions.trackSearch({});
expect(state.selectors.getState()).toBe(SessionState.Loading); expect(state.selectors.getState()).toBe(SearchSessionState.Loading);
}); });
test('untrack', () => { test('untrack', () => {
state.transitions.start(); state.transitions.start();
const search = {}; const search = {};
state.transitions.trackSearch(search); state.transitions.trackSearch(search);
expect(state.selectors.getState()).toBe(SessionState.Loading); expect(state.selectors.getState()).toBe(SearchSessionState.Loading);
state.transitions.unTrackSearch(search); state.transitions.unTrackSearch(search);
expect(state.selectors.getState()).toBe(SessionState.Completed); expect(state.selectors.getState()).toBe(SearchSessionState.Completed);
}); });
test('clear', () => { test('clear', () => {
state.transitions.start(); state.transitions.start();
state.transitions.clear(); state.transitions.clear();
expect(state.selectors.getState()).toBe(SessionState.None); expect(state.selectors.getState()).toBe(SearchSessionState.None);
expect(state.get().sessionId).toBeUndefined(); expect(state.get().sessionId).toBeUndefined();
}); });
@ -64,11 +64,11 @@ describe('Session state container', () => {
state.transitions.start(); state.transitions.start();
const search = {}; const search = {};
state.transitions.trackSearch(search); state.transitions.trackSearch(search);
expect(state.selectors.getState()).toBe(SessionState.Loading); expect(state.selectors.getState()).toBe(SearchSessionState.Loading);
state.transitions.cancel(); state.transitions.cancel();
expect(state.selectors.getState()).toBe(SessionState.Canceled); expect(state.selectors.getState()).toBe(SearchSessionState.Canceled);
state.transitions.clear(); state.transitions.clear();
expect(state.selectors.getState()).toBe(SessionState.None); expect(state.selectors.getState()).toBe(SearchSessionState.None);
}); });
test('store -> completed', () => { test('store -> completed', () => {
@ -77,48 +77,48 @@ describe('Session state container', () => {
state.transitions.start(); state.transitions.start();
const search = {}; const search = {};
state.transitions.trackSearch(search); state.transitions.trackSearch(search);
expect(state.selectors.getState()).toBe(SessionState.Loading); expect(state.selectors.getState()).toBe(SearchSessionState.Loading);
state.transitions.store(); state.transitions.store();
expect(state.selectors.getState()).toBe(SessionState.BackgroundLoading); expect(state.selectors.getState()).toBe(SearchSessionState.BackgroundLoading);
state.transitions.unTrackSearch(search); state.transitions.unTrackSearch(search);
expect(state.selectors.getState()).toBe(SessionState.BackgroundCompleted); expect(state.selectors.getState()).toBe(SearchSessionState.BackgroundCompleted);
state.transitions.clear(); state.transitions.clear();
expect(state.selectors.getState()).toBe(SessionState.None); expect(state.selectors.getState()).toBe(SearchSessionState.None);
}); });
test('store -> cancel', () => { test('store -> cancel', () => {
state.transitions.start(); state.transitions.start();
const search = {}; const search = {};
state.transitions.trackSearch(search); state.transitions.trackSearch(search);
expect(state.selectors.getState()).toBe(SessionState.Loading); expect(state.selectors.getState()).toBe(SearchSessionState.Loading);
state.transitions.store(); state.transitions.store();
expect(state.selectors.getState()).toBe(SessionState.BackgroundLoading); expect(state.selectors.getState()).toBe(SearchSessionState.BackgroundLoading);
state.transitions.cancel(); state.transitions.cancel();
expect(state.selectors.getState()).toBe(SessionState.Canceled); expect(state.selectors.getState()).toBe(SearchSessionState.Canceled);
state.transitions.trackSearch(search); state.transitions.trackSearch(search);
expect(state.selectors.getState()).toBe(SessionState.Canceled); expect(state.selectors.getState()).toBe(SearchSessionState.Canceled);
state.transitions.start(); state.transitions.start();
expect(state.selectors.getState()).toBe(SessionState.None); expect(state.selectors.getState()).toBe(SearchSessionState.None);
}); });
test('restore', () => { test('restore', () => {
const id = 'id'; const id = 'id';
state.transitions.restore(id); state.transitions.restore(id);
expect(state.selectors.getState()).toBe(SessionState.None); expect(state.selectors.getState()).toBe(SearchSessionState.None);
const search = {}; const search = {};
state.transitions.trackSearch(search); state.transitions.trackSearch(search);
expect(state.selectors.getState()).toBe(SessionState.BackgroundLoading); expect(state.selectors.getState()).toBe(SearchSessionState.BackgroundLoading);
state.transitions.unTrackSearch(search); state.transitions.unTrackSearch(search);
expect(state.selectors.getState()).toBe(SessionState.Restored); expect(state.selectors.getState()).toBe(SearchSessionState.Restored);
expect(() => state.transitions.store()).toThrowError(); expect(() => state.transitions.store()).toThrowError();
expect(state.selectors.getState()).toBe(SessionState.Restored); expect(state.selectors.getState()).toBe(SearchSessionState.Restored);
expect(() => state.transitions.cancel()).toThrowError(); expect(() => state.transitions.cancel()).toThrowError();
expect(state.selectors.getState()).toBe(SessionState.Restored); expect(state.selectors.getState()).toBe(SearchSessionState.Restored);
state.transitions.start(); state.transitions.start();
expect(state.selectors.getState()).toBe(SessionState.None); expect(state.selectors.getState()).toBe(SearchSessionState.None);
}); });
}); });
}); });

View file

@ -27,7 +27,7 @@ import { createStateContainer, StateContainer } from '../../../../kibana_utils/p
* *
* @public * @public
*/ */
export enum SessionState { export enum SearchSessionState {
/** /**
* Session is not active, e.g. didn't start * Session is not active, e.g. didn't start
*/ */
@ -39,18 +39,18 @@ export enum SessionState {
Loading = 'loading', Loading = 'loading',
/** /**
* No action was taken and the page completed loading without background session creation. * No action was taken and the page completed loading without search session creation.
*/ */
Completed = 'completed', Completed = 'completed',
/** /**
* Search request was sent to the background. * Search session was sent to the background.
* The page is loading in background. * The page is loading in background.
*/ */
BackgroundLoading = 'backgroundLoading', BackgroundLoading = 'backgroundLoading',
/** /**
* Page load completed with background session created. * Page load completed with search session created.
*/ */
BackgroundCompleted = 'backgroundCompleted', BackgroundCompleted = 'backgroundCompleted',
@ -68,7 +68,7 @@ export enum SessionState {
/** /**
* Internal state of SessionService * Internal state of SessionService
* {@link SessionState} is inferred from this state * {@link SearchSessionState} is inferred from this state
* *
* @private * @private
*/ */
@ -179,27 +179,29 @@ export interface SessionPureSelectors<
SearchDescriptor = unknown, SearchDescriptor = unknown,
S = SessionStateInternal<SearchDescriptor> S = SessionStateInternal<SearchDescriptor>
> { > {
getState: (state: S) => () => SessionState; getState: (state: S) => () => SearchSessionState;
} }
export const sessionPureSelectors: SessionPureSelectors = { export const sessionPureSelectors: SessionPureSelectors = {
getState: (state) => () => { getState: (state) => () => {
if (!state.sessionId) return SessionState.None; if (!state.sessionId) return SearchSessionState.None;
if (!state.isStarted) return SessionState.None; if (!state.isStarted) return SearchSessionState.None;
if (state.isCanceled) return SessionState.Canceled; if (state.isCanceled) return SearchSessionState.Canceled;
switch (true) { switch (true) {
case state.isRestore: case state.isRestore:
return state.pendingSearches.length > 0 return state.pendingSearches.length > 0
? SessionState.BackgroundLoading ? SearchSessionState.BackgroundLoading
: SessionState.Restored; : SearchSessionState.Restored;
case state.isStored: case state.isStored:
return state.pendingSearches.length > 0 return state.pendingSearches.length > 0
? SessionState.BackgroundLoading ? SearchSessionState.BackgroundLoading
: SessionState.BackgroundCompleted; : SearchSessionState.BackgroundCompleted;
default: default:
return state.pendingSearches.length > 0 ? SessionState.Loading : SessionState.Completed; return state.pendingSearches.length > 0
? SearchSessionState.Loading
: SearchSessionState.Completed;
} }
return SessionState.None; return SearchSessionState.None;
}, },
}; };
@ -213,7 +215,7 @@ export const createSessionStateContainer = <SearchDescriptor = unknown>(
{ freeze = true }: { freeze: boolean } = { freeze: true } { freeze = true }: { freeze: boolean } = { freeze: true }
): { ): {
stateContainer: SessionStateContainer<SearchDescriptor>; stateContainer: SessionStateContainer<SearchDescriptor>;
sessionState$: Observable<SessionState>; sessionState$: Observable<SearchSessionState>;
} => { } => {
const stateContainer = createStateContainer( const stateContainer = createStateContainer(
createSessionDefaultState(), createSessionDefaultState(),
@ -222,7 +224,7 @@ export const createSessionStateContainer = <SearchDescriptor = unknown>(
freeze ? undefined : { freeze: (s) => s } freeze ? undefined : { freeze: (s) => s }
) as SessionStateContainer<SearchDescriptor>; ) as SessionStateContainer<SearchDescriptor>;
const sessionState$: Observable<SessionState> = stateContainer.state$.pipe( const sessionState$: Observable<SearchSessionState> = stateContainer.state$.pipe(
map(() => stateContainer.selectors.getState()), map(() => stateContainer.selectors.getState()),
distinctUntilChanged(), distinctUntilChanged(),
shareReplay(1) shareReplay(1)

View file

@ -22,11 +22,11 @@ import { coreMock } from '../../../../../core/public/mocks';
import { take, toArray } from 'rxjs/operators'; import { take, toArray } from 'rxjs/operators';
import { getSessionsClientMock } from './mocks'; import { getSessionsClientMock } from './mocks';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
import { SessionState } from './session_state'; import { SearchSessionState } from './search_session_state';
describe('Session service', () => { describe('Session service', () => {
let sessionService: ISessionService; let sessionService: ISessionService;
let state$: BehaviorSubject<SessionState>; let state$: BehaviorSubject<SearchSessionState>;
beforeEach(() => { beforeEach(() => {
const initializerContext = coreMock.createPluginInitializerContext(); const initializerContext = coreMock.createPluginInitializerContext();
@ -36,7 +36,7 @@ describe('Session service', () => {
getSessionsClientMock(), getSessionsClientMock(),
{ freezeState: false } // needed to use mocks inside state container { freezeState: false } // needed to use mocks inside state container
); );
state$ = new BehaviorSubject<SessionState>(SessionState.None); state$ = new BehaviorSubject<SearchSessionState>(SearchSessionState.None);
sessionService.state$.subscribe(state$); sessionService.state$.subscribe(state$);
}); });
@ -65,17 +65,17 @@ describe('Session service', () => {
it('Tracks searches for current session', () => { it('Tracks searches for current session', () => {
expect(() => sessionService.trackSearch({ abort: () => {} })).toThrowError(); expect(() => sessionService.trackSearch({ abort: () => {} })).toThrowError();
expect(state$.getValue()).toBe(SessionState.None); expect(state$.getValue()).toBe(SearchSessionState.None);
sessionService.start(); sessionService.start();
const untrack1 = sessionService.trackSearch({ abort: () => {} }); const untrack1 = sessionService.trackSearch({ abort: () => {} });
expect(state$.getValue()).toBe(SessionState.Loading); expect(state$.getValue()).toBe(SearchSessionState.Loading);
const untrack2 = sessionService.trackSearch({ abort: () => {} }); const untrack2 = sessionService.trackSearch({ abort: () => {} });
expect(state$.getValue()).toBe(SessionState.Loading); expect(state$.getValue()).toBe(SearchSessionState.Loading);
untrack1(); untrack1();
expect(state$.getValue()).toBe(SessionState.Loading); expect(state$.getValue()).toBe(SearchSessionState.Loading);
untrack2(); untrack2();
expect(state$.getValue()).toBe(SessionState.Completed); expect(state$.getValue()).toBe(SearchSessionState.Completed);
}); });
it('Cancels all tracked searches within current session', async () => { it('Cancels all tracked searches within current session', async () => {

View file

@ -23,7 +23,11 @@ import { Observable, Subject, Subscription } from 'rxjs';
import { PluginInitializerContext, StartServicesAccessor } from 'kibana/public'; import { PluginInitializerContext, StartServicesAccessor } from 'kibana/public';
import { UrlGeneratorId, UrlGeneratorStateMapping } from '../../../../share/public/'; import { UrlGeneratorId, UrlGeneratorStateMapping } from '../../../../share/public/';
import { ConfigSchema } from '../../../config'; import { ConfigSchema } from '../../../config';
import { createSessionStateContainer, SessionState, SessionStateContainer } from './session_state'; import {
createSessionStateContainer,
SearchSessionState,
SessionStateContainer,
} from './search_session_state';
import { ISessionsClient } from './sessions_client'; import { ISessionsClient } from './sessions_client';
export type ISessionService = PublicContract<SessionService>; export type ISessionService = PublicContract<SessionService>;
@ -33,12 +37,12 @@ export interface TrackSearchDescriptor {
} }
/** /**
* Provide info about current search session to be stored in backgroundSearch saved object * Provide info about current search session to be stored in the Search Session saved object
*/ */
export interface SearchSessionInfoProvider<ID extends UrlGeneratorId = UrlGeneratorId> { export interface SearchSessionInfoProvider<ID extends UrlGeneratorId = UrlGeneratorId> {
/** /**
* User-facing name of the session. * User-facing name of the session.
* e.g. will be displayed in background sessions management list * e.g. will be displayed in saved Search Sessions management list
*/ */
getName: () => Promise<string>; getName: () => Promise<string>;
getUrlGeneratorData: () => Promise<{ getUrlGeneratorData: () => Promise<{
@ -52,7 +56,7 @@ export interface SearchSessionInfoProvider<ID extends UrlGeneratorId = UrlGenera
* Responsible for tracking a current search session. Supports only a single session at a time. * Responsible for tracking a current search session. Supports only a single session at a time.
*/ */
export class SessionService { export class SessionService {
public readonly state$: Observable<SessionState>; public readonly state$: Observable<SearchSessionState>;
private readonly state: SessionStateContainer<TrackSearchDescriptor>; private readonly state: SessionStateContainer<TrackSearchDescriptor>;
private searchSessionInfoProvider?: SearchSessionInfoProvider; private searchSessionInfoProvider?: SearchSessionInfoProvider;
@ -95,7 +99,7 @@ export class SessionService {
/** /**
* Set a provider of info about current session * Set a provider of info about current session
* This will be used for creating a background session saved object * This will be used for creating a search session saved object
* @param searchSessionInfoProvider * @param searchSessionInfoProvider
*/ */
public setSearchSessionInfoProvider<ID extends UrlGeneratorId = UrlGeneratorId>( public setSearchSessionInfoProvider<ID extends UrlGeneratorId = UrlGeneratorId>(
@ -184,7 +188,7 @@ export class SessionService {
private refresh$ = new Subject<void>(); private refresh$ = new Subject<void>();
/** /**
* Observable emits when search result refresh was requested * Observable emits when search result refresh was requested
* For example, search to background UI could have it's own "refresh" button * For example, the UI could have it's own "refresh" button
* Application would use this observable to handle user interaction on that button * Application would use this observable to handle user interaction on that button
*/ */
public onRefresh$ = this.refresh$.asObservable(); public onRefresh$ = this.refresh$.asObservable();

View file

@ -27,7 +27,7 @@ export interface SessionsClientDeps {
} }
/** /**
* CRUD backgroundSession SO * CRUD Search Session SO
*/ */
export class SessionsClient { export class SessionsClient {
private readonly http: HttpSetup; private readonly http: HttpSetup;

View file

@ -45,7 +45,7 @@ export interface ISearchSetup {
*/ */
session: ISessionService; session: ISessionService;
/** /**
* Background search sessions SO CRUD * Search sessions SO CRUD
* {@link ISessionsClient} * {@link ISessionsClient}
*/ */
sessionsClient: ISessionsClient; sessionsClient: ISessionsClient;
@ -84,7 +84,7 @@ export interface ISearchStart {
*/ */
session: ISessionService; session: ISessionService;
/** /**
* Background search sessions SO CRUD * Search sessions SO CRUD
* {@link ISessionsClient} * {@link ISessionsClient}
*/ */
sessionsClient: ISessionsClient; sessionsClient: ISessionsClient;

View file

@ -23,7 +23,7 @@ import { ISearchStrategy } from '../types';
import { ISessionService } from './types'; import { ISessionService } from './types';
/** /**
* The OSS session service. See data_enhanced in X-Pack for the background session service. * The OSS session service. See data_enhanced in X-Pack for the search session service.
*/ */
export class SessionService implements ISessionService { export class SessionService implements ISessionService {
constructor() {} constructor() {}

View file

@ -204,7 +204,7 @@ function discoverController($element, $route, $scope, $timeout, Promise, uiCapab
}; };
const history = getHistory(); const history = getHistory();
// used for restoring background session // used for restoring a search session
let isInitialSearch = true; let isInitialSearch = true;
// search session requested a data refresh // search session requested a data refresh

View file

@ -12,8 +12,8 @@ export {
EqlSearchStrategyResponse, EqlSearchStrategyResponse,
IAsyncSearchOptions, IAsyncSearchOptions,
pollSearch, pollSearch,
BackgroundSessionSavedObjectAttributes, SearchSessionSavedObjectAttributes,
BackgroundSessionFindOptions, SearchSessionFindOptions,
BackgroundSessionStatus, SearchSessionStatus,
BackgroundSessionSearchInfo, SearchSessionRequestInfo,
} from './search'; } from './search';

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
export enum BackgroundSessionStatus { export enum SearchSessionStatus {
IN_PROGRESS = 'in_progress', IN_PROGRESS = 'in_progress',
ERROR = 'error', ERROR = 'error',
COMPLETE = 'complete', COMPLETE = 'complete',

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
export interface BackgroundSessionSavedObjectAttributes { export interface SearchSessionSavedObjectAttributes {
/** /**
* User-facing session name to be displayed in session management * User-facing session name to be displayed in session management
*/ */
@ -19,15 +19,15 @@ export interface BackgroundSessionSavedObjectAttributes {
urlGeneratorId: string; urlGeneratorId: string;
initialState: Record<string, unknown>; initialState: Record<string, unknown>;
restoreState: Record<string, unknown>; restoreState: Record<string, unknown>;
idMapping: Record<string, BackgroundSessionSearchInfo>; idMapping: Record<string, SearchSessionRequestInfo>;
} }
export interface BackgroundSessionSearchInfo { export interface SearchSessionRequestInfo {
id: string; // ID of the async search request id: string; // ID of the async search request
strategy: string; // Search strategy used to submit the search request strategy: string; // Search strategy used to submit the search request
} }
export interface BackgroundSessionFindOptions { export interface SearchSessionFindOptions {
page?: number; page?: number;
perPage?: number; perPage?: number;
sortField?: string; sortField?: string;

View file

@ -13,7 +13,7 @@ import { setAutocompleteService } from './services';
import { setupKqlQuerySuggestionProvider, KUERY_LANGUAGE_NAME } from './autocomplete'; import { setupKqlQuerySuggestionProvider, KUERY_LANGUAGE_NAME } from './autocomplete';
import { EnhancedSearchInterceptor } from './search/search_interceptor'; import { EnhancedSearchInterceptor } from './search/search_interceptor';
import { toMountPoint } from '../../../../src/plugins/kibana_react/public'; import { toMountPoint } from '../../../../src/plugins/kibana_react/public';
import { createConnectedBackgroundSessionIndicator } from './search'; import { createConnectedSearchSessionIndicator } from './search';
import { ConfigSchema } from '../config'; import { ConfigSchema } from '../config';
export interface DataEnhancedSetupDependencies { export interface DataEnhancedSetupDependencies {
@ -66,7 +66,7 @@ export class DataEnhancedPlugin
core.chrome.setBreadcrumbsAppendExtension({ core.chrome.setBreadcrumbsAppendExtension({
content: toMountPoint( content: toMountPoint(
React.createElement( React.createElement(
createConnectedBackgroundSessionIndicator({ createConnectedSearchSessionIndicator({
sessionService: plugins.data.search.session, sessionService: plugins.data.search.session,
application: core.application, application: core.application,
timeFilter: plugins.data.query.timefilter.timefilter, timeFilter: plugins.data.query.timefilter.timefilter,

View file

@ -9,7 +9,7 @@ import { EnhancedSearchInterceptor } from './search_interceptor';
import { CoreSetup, CoreStart } from 'kibana/public'; import { CoreSetup, CoreStart } from 'kibana/public';
import { UI_SETTINGS } from '../../../../../src/plugins/data/common'; import { UI_SETTINGS } from '../../../../../src/plugins/data/common';
import { AbortError } from '../../../../../src/plugins/kibana_utils/public'; import { AbortError } from '../../../../../src/plugins/kibana_utils/public';
import { ISessionService, SearchTimeoutError, SessionState } from 'src/plugins/data/public'; import { ISessionService, SearchTimeoutError, SearchSessionState } from 'src/plugins/data/public';
import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks'; import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks';
import { bfetchPluginMock } from '../../../../../src/plugins/bfetch/public/mocks'; import { bfetchPluginMock } from '../../../../../src/plugins/bfetch/public/mocks';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
@ -45,12 +45,12 @@ function mockFetchImplementation(responses: any[]) {
describe('EnhancedSearchInterceptor', () => { describe('EnhancedSearchInterceptor', () => {
let mockUsageCollector: any; let mockUsageCollector: any;
let sessionService: jest.Mocked<ISessionService>; let sessionService: jest.Mocked<ISessionService>;
let sessionState$: BehaviorSubject<SessionState>; let sessionState$: BehaviorSubject<SearchSessionState>;
beforeEach(() => { beforeEach(() => {
mockCoreSetup = coreMock.createSetup(); mockCoreSetup = coreMock.createSetup();
mockCoreStart = coreMock.createStart(); mockCoreStart = coreMock.createStart();
sessionState$ = new BehaviorSubject<SessionState>(SessionState.None); sessionState$ = new BehaviorSubject<SearchSessionState>(SearchSessionState.None);
const dataPluginMockStart = dataPluginMock.createStartContract(); const dataPluginMockStart = dataPluginMock.createStartContract();
sessionService = { sessionService = {
...(dataPluginMockStart.search.session as jest.Mocked<ISessionService>), ...(dataPluginMockStart.search.session as jest.Mocked<ISessionService>),
@ -408,7 +408,7 @@ describe('EnhancedSearchInterceptor', () => {
expect(next).toHaveBeenCalled(); expect(next).toHaveBeenCalled();
expect(error).not.toHaveBeenCalled(); expect(error).not.toHaveBeenCalled();
sessionState$.next(SessionState.BackgroundLoading); sessionState$.next(SearchSessionState.BackgroundLoading);
await timeTravel(240); await timeTravel(240);

View file

@ -12,7 +12,7 @@ import {
SearchInterceptorDeps, SearchInterceptorDeps,
UI_SETTINGS, UI_SETTINGS,
IKibanaSearchRequest, IKibanaSearchRequest,
SessionState, SearchSessionState,
} from '../../../../../src/plugins/data/public'; } from '../../../../../src/plugins/data/public';
import { AbortError } from '../../../../../src/plugins/kibana_utils/common'; import { AbortError } from '../../../../../src/plugins/kibana_utils/common';
import { ENHANCED_ES_SEARCH_STRATEGY, IAsyncSearchOptions, pollSearch } from '../../common'; import { ENHANCED_ES_SEARCH_STRATEGY, IAsyncSearchOptions, pollSearch } from '../../common';
@ -77,7 +77,7 @@ export class EnhancedSearchInterceptor extends SearchInterceptor {
this.deps.session.state$ this.deps.session.state$
.pipe( .pipe(
skip(1), // ignore any state, we are only interested in transition x -> BackgroundLoading skip(1), // ignore any state, we are only interested in transition x -> BackgroundLoading
filter((state) => isCurrentSession() && state === SessionState.BackgroundLoading), filter((state) => isCurrentSession() && state === SearchSessionState.BackgroundLoading),
take(1) take(1)
) )
.subscribe(() => { .subscribe(() => {

View file

@ -1,39 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import { storiesOf } from '@storybook/react';
import { BackgroundSessionIndicator } from './background_session_indicator';
import { SessionState } from '../../../../../../../src/plugins/data/public';
storiesOf('components/BackgroundSessionIndicator', module).add('default', () => (
<>
<div>
<BackgroundSessionIndicator state={SessionState.Loading} />
</div>
<div>
<BackgroundSessionIndicator state={SessionState.Completed} />
</div>
<div>
<BackgroundSessionIndicator state={SessionState.BackgroundLoading} />
</div>
<div>
<BackgroundSessionIndicator state={SessionState.BackgroundCompleted} />
</div>
<div>
<BackgroundSessionIndicator state={SessionState.Restored} />
</div>
<div>
<BackgroundSessionIndicator
state={SessionState.Completed}
disabled={true}
disabledReasonText={
'Send to background capability is unavailable when auto-refresh is enabled'
}
/>
</div>
</>
));

View file

@ -7,12 +7,12 @@
import React from 'react'; import React from 'react';
import { render, waitFor, screen, act } from '@testing-library/react'; import { render, waitFor, screen, act } from '@testing-library/react';
import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks';
import { createConnectedBackgroundSessionIndicator } from './connected_background_session_indicator'; import { createConnectedSearchSessionIndicator } from './connected_search_session_indicator';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
import { import {
ISessionService, ISessionService,
RefreshInterval, RefreshInterval,
SessionState, SearchSessionState,
TimefilterContract, TimefilterContract,
} from '../../../../../../../src/plugins/data/public'; } from '../../../../../../../src/plugins/data/public';
import { coreMock } from '../../../../../../../src/core/public/mocks'; import { coreMock } from '../../../../../../../src/core/public/mocks';
@ -31,78 +31,76 @@ beforeEach(() => {
}); });
test("shouldn't show indicator in case no active search session", async () => { test("shouldn't show indicator in case no active search session", async () => {
const BackgroundSessionIndicator = createConnectedBackgroundSessionIndicator({ const SearchSessionIndicator = createConnectedSearchSessionIndicator({
sessionService, sessionService,
application: coreStart.application, application: coreStart.application,
timeFilter, timeFilter,
}); });
const { getByTestId, container } = render(<BackgroundSessionIndicator />); const { getByTestId, container } = render(<SearchSessionIndicator />);
// make sure `backgroundSessionIndicator` isn't appearing after some time (lazy-loading) // make sure `searchSessionIndicator` isn't appearing after some time (lazy-loading)
await expect( await expect(
waitFor(() => getByTestId('backgroundSessionIndicator'), { timeout: 100 }) waitFor(() => getByTestId('searchSessionIndicator'), { timeout: 100 })
).rejects.toThrow(); ).rejects.toThrow();
expect(container).toMatchInlineSnapshot(`<div />`); expect(container).toMatchInlineSnapshot(`<div />`);
}); });
test('should show indicator in case there is an active search session', async () => { test('should show indicator in case there is an active search session', async () => {
const state$ = new BehaviorSubject(SessionState.Loading); const state$ = new BehaviorSubject(SearchSessionState.Loading);
const BackgroundSessionIndicator = createConnectedBackgroundSessionIndicator({ const SearchSessionIndicator = createConnectedSearchSessionIndicator({
sessionService: { ...sessionService, state$ }, sessionService: { ...sessionService, state$ },
application: coreStart.application, application: coreStart.application,
timeFilter, timeFilter,
}); });
const { getByTestId } = render(<BackgroundSessionIndicator />); const { getByTestId } = render(<SearchSessionIndicator />);
await waitFor(() => getByTestId('backgroundSessionIndicator')); await waitFor(() => getByTestId('searchSessionIndicator'));
}); });
test('should be disabled when permissions are off', async () => { test('should be disabled when permissions are off', async () => {
const state$ = new BehaviorSubject(SessionState.Loading); const state$ = new BehaviorSubject(SearchSessionState.Loading);
coreStart.application.currentAppId$ = new BehaviorSubject('discover'); coreStart.application.currentAppId$ = new BehaviorSubject('discover');
(coreStart.application.capabilities as any) = { (coreStart.application.capabilities as any) = {
discover: { discover: {
storeSearchSession: false, storeSearchSession: false,
}, },
}; };
const BackgroundSessionIndicator = createConnectedBackgroundSessionIndicator({ const SearchSessionIndicator = createConnectedSearchSessionIndicator({
sessionService: { ...sessionService, state$ }, sessionService: { ...sessionService, state$ },
application: coreStart.application, application: coreStart.application,
timeFilter, timeFilter,
}); });
render(<BackgroundSessionIndicator />); render(<SearchSessionIndicator />);
await waitFor(() => screen.getByTestId('backgroundSessionIndicator')); await waitFor(() => screen.getByTestId('searchSessionIndicator'));
expect(screen.getByTestId('backgroundSessionIndicator').querySelector('button')).toBeDisabled(); expect(screen.getByTestId('searchSessionIndicator').querySelector('button')).toBeDisabled();
}); });
test('should be disabled during auto-refresh', async () => { test('should be disabled during auto-refresh', async () => {
const state$ = new BehaviorSubject(SessionState.Loading); const state$ = new BehaviorSubject(SearchSessionState.Loading);
coreStart.application.currentAppId$ = new BehaviorSubject('discover'); coreStart.application.currentAppId$ = new BehaviorSubject('discover');
(coreStart.application.capabilities as any) = { (coreStart.application.capabilities as any) = {
discover: { discover: {
storeSearchSession: true, storeSearchSession: true,
}, },
}; };
const BackgroundSessionIndicator = createConnectedBackgroundSessionIndicator({ const SearchSessionIndicator = createConnectedSearchSessionIndicator({
sessionService: { ...sessionService, state$ }, sessionService: { ...sessionService, state$ },
application: coreStart.application, application: coreStart.application,
timeFilter, timeFilter,
}); });
render(<BackgroundSessionIndicator />); render(<SearchSessionIndicator />);
await waitFor(() => screen.getByTestId('backgroundSessionIndicator')); await waitFor(() => screen.getByTestId('searchSessionIndicator'));
expect( expect(screen.getByTestId('searchSessionIndicator').querySelector('button')).not.toBeDisabled();
screen.getByTestId('backgroundSessionIndicator').querySelector('button')
).not.toBeDisabled();
act(() => { act(() => {
refreshInterval$.next({ value: 0, pause: false }); refreshInterval$.next({ value: 0, pause: false });
}); });
expect(screen.getByTestId('backgroundSessionIndicator').querySelector('button')).toBeDisabled(); expect(screen.getByTestId('searchSessionIndicator').querySelector('button')).toBeDisabled();
}); });

View file

@ -8,22 +8,22 @@ import React from 'react';
import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators'; import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators';
import useObservable from 'react-use/lib/useObservable'; import useObservable from 'react-use/lib/useObservable';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { BackgroundSessionIndicator } from '../background_session_indicator'; import { SearchSessionIndicator } from '../search_session_indicator';
import { ISessionService, TimefilterContract } from '../../../../../../../src/plugins/data/public/'; import { ISessionService, TimefilterContract } from '../../../../../../../src/plugins/data/public/';
import { RedirectAppLinks } from '../../../../../../../src/plugins/kibana_react/public'; import { RedirectAppLinks } from '../../../../../../../src/plugins/kibana_react/public';
import { ApplicationStart } from '../../../../../../../src/core/public'; import { ApplicationStart } from '../../../../../../../src/core/public';
export interface BackgroundSessionIndicatorDeps { export interface SearchSessionIndicatorDeps {
sessionService: ISessionService; sessionService: ISessionService;
timeFilter: TimefilterContract; timeFilter: TimefilterContract;
application: ApplicationStart; application: ApplicationStart;
} }
export const createConnectedBackgroundSessionIndicator = ({ export const createConnectedSearchSessionIndicator = ({
sessionService, sessionService,
application, application,
timeFilter, timeFilter,
}: BackgroundSessionIndicatorDeps): React.FC => { }: SearchSessionIndicatorDeps): React.FC => {
const isAutoRefreshEnabled = () => !timeFilter.getRefreshInterval().pause; const isAutoRefreshEnabled = () => !timeFilter.getRefreshInterval().pause;
const isAutoRefreshEnabled$ = timeFilter const isAutoRefreshEnabled$ = timeFilter
.getRefreshIntervalUpdate$() .getRefreshIntervalUpdate$()
@ -53,7 +53,7 @@ export const createConnectedBackgroundSessionIndicator = ({
if (getCapabilitiesByAppId(application.capabilities, appId)?.storeSearchSession !== true) { if (getCapabilitiesByAppId(application.capabilities, appId)?.storeSearchSession !== true) {
disabled = true; disabled = true;
disabledReasonText = i18n.translate('xpack.data.backgroundSessionIndicator.noCapability', { disabledReasonText = i18n.translate('xpack.data.searchSessionIndicator.noCapability', {
defaultMessage: "You don't have permissions to send to background.", defaultMessage: "You don't have permissions to send to background.",
}); });
} }
@ -61,7 +61,7 @@ export const createConnectedBackgroundSessionIndicator = ({
if (autoRefreshEnabled) { if (autoRefreshEnabled) {
disabled = true; disabled = true;
disabledReasonText = i18n.translate( disabledReasonText = i18n.translate(
'xpack.data.backgroundSessionIndicator.disabledDueToAutoRefreshMessage', 'xpack.data.searchSessionIndicator.disabledDueToAutoRefreshMessage',
{ {
defaultMessage: 'Send to background is not available when auto refresh is enabled.', defaultMessage: 'Send to background is not available when auto refresh is enabled.',
} }
@ -71,7 +71,7 @@ export const createConnectedBackgroundSessionIndicator = ({
if (!state) return null; if (!state) return null;
return ( return (
<RedirectAppLinks application={application}> <RedirectAppLinks application={application}>
<BackgroundSessionIndicator <SearchSessionIndicator
state={state} state={state}
onContinueInBackground={() => { onContinueInBackground={() => {
sessionService.save(); sessionService.save();

View file

@ -5,6 +5,6 @@
*/ */
export { export {
BackgroundSessionIndicatorDeps, SearchSessionIndicatorDeps,
createConnectedBackgroundSessionIndicator, createConnectedSearchSessionIndicator,
} from './connected_background_session_indicator'; } from './connected_search_session_indicator';

View file

@ -4,14 +4,14 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
export enum BackgroundSessionViewState { export enum SearchSessionViewState {
/** /**
* Pending search request has not been sent to the background yet * Pending search request has not been sent to the background yet
*/ */
Loading = 'loading', Loading = 'loading',
/** /**
* No action was taken and the page completed loading without background session creation. * No action was taken and the page completed loading without search session creation.
*/ */
Completed = 'completed', Completed = 'completed',
@ -22,7 +22,7 @@ export enum BackgroundSessionViewState {
BackgroundLoading = 'backgroundLoading', BackgroundLoading = 'backgroundLoading',
/** /**
* Page load completed with background session created. * Page load completed with search session created.
*/ */
BackgroundCompleted = 'backgroundCompleted', BackgroundCompleted = 'backgroundCompleted',

View file

@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
export * from './connected_background_session_indicator'; export * from './connected_search_session_indicator';

View file

@ -6,8 +6,8 @@
import { EuiDelayRender, EuiLoadingSpinner } from '@elastic/eui'; import { EuiDelayRender, EuiLoadingSpinner } from '@elastic/eui';
import React from 'react'; import React from 'react';
import type { BackgroundSessionIndicatorProps } from './background_session_indicator'; import type { SearchSessionIndicatorProps } from './search_session_indicator';
export type { BackgroundSessionIndicatorProps }; export type { SearchSessionIndicatorProps };
const Fallback = () => ( const Fallback = () => (
<EuiDelayRender> <EuiDelayRender>
@ -15,9 +15,9 @@ const Fallback = () => (
</EuiDelayRender> </EuiDelayRender>
); );
const LazyBackgroundSessionIndicator = React.lazy(() => import('./background_session_indicator')); const LazySearchSessionIndicator = React.lazy(() => import('./search_session_indicator'));
export const BackgroundSessionIndicator = (props: BackgroundSessionIndicatorProps) => ( export const SearchSessionIndicator = (props: SearchSessionIndicatorProps) => (
<React.Suspense fallback={<Fallback />}> <React.Suspense fallback={<Fallback />}>
<LazyBackgroundSessionIndicator {...props} /> <LazySearchSessionIndicator {...props} />
</React.Suspense> </React.Suspense>
); );

View file

@ -1,14 +1,14 @@
.backgroundSessionIndicator { .searchSessionIndicator {
padding: 0 $euiSizeXS; padding: 0 $euiSizeXS;
} }
@include euiBreakpoint('xs', 's') { @include euiBreakpoint('xs', 's') {
.backgroundSessionIndicator__popoverContainer.euiFlexGroup--responsive .euiFlexItem { .searchSessionIndicator__popoverContainer.euiFlexGroup--responsive .euiFlexItem {
margin-bottom: $euiSizeXS !important; margin-bottom: $euiSizeXS !important;
} }
} }
.backgroundSessionIndicator__verticalDivider { .searchSessionIndicator__verticalDivider {
@include euiBreakpoint('xs', 's') { @include euiBreakpoint('xs', 's') {
margin-left: $euiSizeXS; margin-left: $euiSizeXS;
padding-left: $euiSizeXS; padding-left: $euiSizeXS;

View file

@ -0,0 +1,39 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import { storiesOf } from '@storybook/react';
import { SearchSessionIndicator } from './search_session_indicator';
import { SearchSessionState } from '../../../../../../../src/plugins/data/public';
storiesOf('components/SearchSessionIndicator', module).add('default', () => (
<>
<div>
<SearchSessionIndicator state={SearchSessionState.Loading} />
</div>
<div>
<SearchSessionIndicator state={SearchSessionState.Completed} />
</div>
<div>
<SearchSessionIndicator state={SearchSessionState.BackgroundLoading} />
</div>
<div>
<SearchSessionIndicator state={SearchSessionState.BackgroundCompleted} />
</div>
<div>
<SearchSessionIndicator state={SearchSessionState.Restored} />
</div>
<div>
<SearchSessionIndicator
state={SearchSessionState.Completed}
disabled={true}
disabledReasonText={
'Send to background capability is unavailable when auto-refresh is enabled'
}
/>
</div>
</>
));

View file

@ -7,9 +7,9 @@
import React, { ReactNode } from 'react'; import React, { ReactNode } from 'react';
import { screen, render } from '@testing-library/react'; import { screen, render } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { BackgroundSessionIndicator } from './background_session_indicator'; import { SearchSessionIndicator } from './search_session_indicator';
import { IntlProvider } from 'react-intl'; import { IntlProvider } from 'react-intl';
import { SessionState } from '../../../../../../../src/plugins/data/public'; import { SearchSessionState } from '../../../../../../../src/plugins/data/public';
function Container({ children }: { children?: ReactNode }) { function Container({ children }: { children?: ReactNode }) {
return <IntlProvider locale="en">{children}</IntlProvider>; return <IntlProvider locale="en">{children}</IntlProvider>;
@ -19,12 +19,12 @@ test('Loading state', async () => {
const onCancel = jest.fn(); const onCancel = jest.fn();
render( render(
<Container> <Container>
<BackgroundSessionIndicator state={SessionState.Loading} onCancel={onCancel} /> <SearchSessionIndicator state={SearchSessionState.Loading} onCancel={onCancel} />
</Container> </Container>
); );
await userEvent.click(screen.getByLabelText('Loading results')); await userEvent.click(screen.getByLabelText('Loading'));
await userEvent.click(screen.getByText('Cancel')); await userEvent.click(screen.getByText('Cancel session'));
expect(onCancel).toBeCalled(); expect(onCancel).toBeCalled();
}); });
@ -33,12 +33,12 @@ test('Completed state', async () => {
const onSave = jest.fn(); const onSave = jest.fn();
render( render(
<Container> <Container>
<BackgroundSessionIndicator state={SessionState.Completed} onSaveResults={onSave} /> <SearchSessionIndicator state={SearchSessionState.Completed} onSaveResults={onSave} />
</Container> </Container>
); );
await userEvent.click(screen.getByLabelText('Results loaded')); await userEvent.click(screen.getByLabelText('Loaded'));
await userEvent.click(screen.getByText('Save')); await userEvent.click(screen.getByText('Save session'));
expect(onSave).toBeCalled(); expect(onSave).toBeCalled();
}); });
@ -47,12 +47,12 @@ test('Loading in the background state', async () => {
const onCancel = jest.fn(); const onCancel = jest.fn();
render( render(
<Container> <Container>
<BackgroundSessionIndicator state={SessionState.BackgroundLoading} onCancel={onCancel} /> <SearchSessionIndicator state={SearchSessionState.BackgroundLoading} onCancel={onCancel} />
</Container> </Container>
); );
await userEvent.click(screen.getByLabelText('Loading results in the background')); await userEvent.click(screen.getByLabelText('Loading results in the background'));
await userEvent.click(screen.getByText('Cancel')); await userEvent.click(screen.getByText('Cancel session'));
expect(onCancel).toBeCalled(); expect(onCancel).toBeCalled();
}); });
@ -60,15 +60,15 @@ test('Loading in the background state', async () => {
test('BackgroundCompleted state', async () => { test('BackgroundCompleted state', async () => {
render( render(
<Container> <Container>
<BackgroundSessionIndicator <SearchSessionIndicator
state={SessionState.BackgroundCompleted} state={SearchSessionState.BackgroundCompleted}
viewBackgroundSessionsLink={'__link__'} viewSearchSessionsLink={'__link__'}
/> />
</Container> </Container>
); );
await userEvent.click(screen.getByLabelText('Results loaded in the background')); await userEvent.click(screen.getByLabelText('Results loaded in the background'));
expect(screen.getByRole('link', { name: 'View background sessions' }).getAttribute('href')).toBe( expect(screen.getByRole('link', { name: 'View all sessions' }).getAttribute('href')).toBe(
'__link__' '__link__'
); );
}); });
@ -77,7 +77,7 @@ test('Restored state', async () => {
const onRefresh = jest.fn(); const onRefresh = jest.fn();
render( render(
<Container> <Container>
<BackgroundSessionIndicator state={SessionState.Restored} onRefresh={onRefresh} /> <SearchSessionIndicator state={SearchSessionState.Restored} onRefresh={onRefresh} />
</Container> </Container>
); );
@ -91,7 +91,7 @@ test('Canceled state', async () => {
const onRefresh = jest.fn(); const onRefresh = jest.fn();
render( render(
<Container> <Container>
<BackgroundSessionIndicator state={SessionState.Canceled} onRefresh={onRefresh} /> <SearchSessionIndicator state={SearchSessionState.Canceled} onRefresh={onRefresh} />
</Container> </Container>
); );
@ -104,9 +104,9 @@ test('Canceled state', async () => {
test('Disabled state', async () => { test('Disabled state', async () => {
render( render(
<Container> <Container>
<BackgroundSessionIndicator state={SessionState.Loading} disabled={true} /> <SearchSessionIndicator state={SearchSessionState.Loading} disabled={true} />
</Container> </Container>
); );
expect(screen.getByTestId('backgroundSessionIndicator').querySelector('button')).toBeDisabled(); expect(screen.getByTestId('searchSessionIndicator').querySelector('button')).toBeDisabled();
}); });

View file

@ -20,31 +20,31 @@ import {
import { FormattedMessage } from '@kbn/i18n/react'; import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import './background_session_indicator.scss'; import './search_session_indicator.scss';
import { SessionState } from '../../../../../../../src/plugins/data/public/'; import { SearchSessionState } from '../../../../../../../src/plugins/data/public';
export interface BackgroundSessionIndicatorProps { export interface SearchSessionIndicatorProps {
state: SessionState; state: SearchSessionState;
onContinueInBackground?: () => void; onContinueInBackground?: () => void;
onCancel?: () => void; onCancel?: () => void;
viewBackgroundSessionsLink?: string; viewSearchSessionsLink?: string;
onSaveResults?: () => void; onSaveResults?: () => void;
onRefresh?: () => void; onRefresh?: () => void;
disabled?: boolean; disabled?: boolean;
disabledReasonText?: string; disabledReasonText?: string;
} }
type ActionButtonProps = BackgroundSessionIndicatorProps & { buttonProps: EuiButtonEmptyProps }; type ActionButtonProps = SearchSessionIndicatorProps & { buttonProps: EuiButtonEmptyProps };
const CancelButton = ({ onCancel = () => {}, buttonProps = {} }: ActionButtonProps) => ( const CancelButton = ({ onCancel = () => {}, buttonProps = {} }: ActionButtonProps) => (
<EuiButtonEmpty <EuiButtonEmpty
onClick={onCancel} onClick={onCancel}
data-test-subj={'backgroundSessionIndicatorCancelBtn'} data-test-subj={'searchSessionIndicatorCancelBtn'}
{...buttonProps} {...buttonProps}
> >
<FormattedMessage <FormattedMessage
id="xpack.data.backgroundSessionIndicator.cancelButtonText" id="xpack.data.searchSessionIndicator.cancelButtonText"
defaultMessage="Cancel" defaultMessage="Cancel session"
/> />
</EuiButtonEmpty> </EuiButtonEmpty>
); );
@ -55,28 +55,28 @@ const ContinueInBackgroundButton = ({
}: ActionButtonProps) => ( }: ActionButtonProps) => (
<EuiButtonEmpty <EuiButtonEmpty
onClick={onContinueInBackground} onClick={onContinueInBackground}
data-test-subj={'backgroundSessionIndicatorContinueInBackgroundBtn'} data-test-subj={'searchSessionIndicatorContinueInBackgroundBtn'}
{...buttonProps} {...buttonProps}
> >
<FormattedMessage <FormattedMessage
id="xpack.data.backgroundSessionIndicator.continueInBackgroundButtonText" id="xpack.data.searchSessionIndicator.continueInBackgroundButtonText"
defaultMessage="Continue in background" defaultMessage="Continue in background"
/> />
</EuiButtonEmpty> </EuiButtonEmpty>
); );
const ViewBackgroundSessionsButton = ({ const ViewAllSearchSessionsButton = ({
viewBackgroundSessionsLink = 'management', viewSearchSessionsLink = 'management',
buttonProps = {}, buttonProps = {},
}: ActionButtonProps) => ( }: ActionButtonProps) => (
<EuiButtonEmpty <EuiButtonEmpty
href={viewBackgroundSessionsLink} href={viewSearchSessionsLink}
data-test-subj={'backgroundSessionIndicatorViewBackgroundSessionsLink'} data-test-subj={'searchSessionIndicatorviewSearchSessionsLink'}
{...buttonProps} {...buttonProps}
> >
<FormattedMessage <FormattedMessage
id="xpack.data.backgroundSessionIndicator.viewBackgroundSessionsLinkText" id="xpack.data.searchSessionIndicator.viewSearchSessionsLinkText"
defaultMessage="View background sessions" defaultMessage="View all sessions"
/> />
</EuiButtonEmpty> </EuiButtonEmpty>
); );
@ -84,11 +84,11 @@ const ViewBackgroundSessionsButton = ({
const RefreshButton = ({ onRefresh = () => {}, buttonProps = {} }: ActionButtonProps) => ( const RefreshButton = ({ onRefresh = () => {}, buttonProps = {} }: ActionButtonProps) => (
<EuiButtonEmpty <EuiButtonEmpty
onClick={onRefresh} onClick={onRefresh}
data-test-subj={'backgroundSessionIndicatorRefreshBtn'} data-test-subj={'searchSessionIndicatorRefreshBtn'}
{...buttonProps} {...buttonProps}
> >
<FormattedMessage <FormattedMessage
id="xpack.data.backgroundSessionIndicator.refreshButtonText" id="xpack.data.searchSessionIndicator.refreshButtonText"
defaultMessage="Refresh" defaultMessage="Refresh"
/> />
</EuiButtonEmpty> </EuiButtonEmpty>
@ -97,18 +97,18 @@ const RefreshButton = ({ onRefresh = () => {}, buttonProps = {} }: ActionButtonP
const SaveButton = ({ onSaveResults = () => {}, buttonProps = {} }: ActionButtonProps) => ( const SaveButton = ({ onSaveResults = () => {}, buttonProps = {} }: ActionButtonProps) => (
<EuiButtonEmpty <EuiButtonEmpty
onClick={onSaveResults} onClick={onSaveResults}
data-test-subj={'backgroundSessionIndicatorSaveBtn'} data-test-subj={'searchSessionIndicatorSaveBtn'}
{...buttonProps} {...buttonProps}
> >
<FormattedMessage <FormattedMessage
id="xpack.data.backgroundSessionIndicator.saveButtonText" id="xpack.data.searchSessionIndicator.saveButtonText"
defaultMessage="Save" defaultMessage="Save session"
/> />
</EuiButtonEmpty> </EuiButtonEmpty>
); );
const backgroundSessionIndicatorViewStateToProps: { const searchSessionIndicatorViewStateToProps: {
[state in SessionState]: { [state in SearchSessionState]: {
button: Pick<EuiButtonIconProps, 'color' | 'iconType' | 'aria-label'> & { button: Pick<EuiButtonIconProps, 'color' | 'iconType' | 'aria-label'> & {
tooltipText: string; tooltipText: string;
}; };
@ -119,162 +119,151 @@ const backgroundSessionIndicatorViewStateToProps: {
}; };
} | null; } | null;
} = { } = {
[SessionState.None]: null, [SearchSessionState.None]: null,
[SessionState.Loading]: { [SearchSessionState.Loading]: {
button: { button: {
color: 'subdued', color: 'subdued',
iconType: 'clock', iconType: 'clock',
'aria-label': i18n.translate( 'aria-label': i18n.translate(
'xpack.data.backgroundSessionIndicator.loadingResultsIconAriaLabel', 'xpack.data.searchSessionIndicator.loadingResultsIconAriaLabel',
{ defaultMessage: 'Loading results' } { defaultMessage: 'Loading' }
), ),
tooltipText: i18n.translate( tooltipText: i18n.translate(
'xpack.data.backgroundSessionIndicator.loadingResultsIconTooltipText', 'xpack.data.searchSessionIndicator.loadingResultsIconTooltipText',
{ defaultMessage: 'Loading results' } { defaultMessage: 'Loading' }
), ),
}, },
popover: { popover: {
text: i18n.translate('xpack.data.backgroundSessionIndicator.loadingResultsText', { text: i18n.translate('xpack.data.searchSessionIndicator.loadingResultsText', {
defaultMessage: 'Loading', defaultMessage: 'Loading',
}), }),
primaryAction: CancelButton, primaryAction: CancelButton,
secondaryAction: ContinueInBackgroundButton, secondaryAction: ContinueInBackgroundButton,
}, },
}, },
[SessionState.Completed]: { [SearchSessionState.Completed]: {
button: { button: {
color: 'subdued', color: 'subdued',
iconType: 'checkInCircleFilled', iconType: 'checkInCircleFilled',
'aria-label': i18n.translate( 'aria-label': i18n.translate('xpack.data.searchSessionIndicator.resultsLoadedIconAriaLabel', {
'xpack.data.backgroundSessionIndicator.resultsLoadedIconAriaLabel', defaultMessage: 'Loaded',
{ }),
defaultMessage: 'Results loaded',
}
),
tooltipText: i18n.translate( tooltipText: i18n.translate(
'xpack.data.backgroundSessionIndicator.resultsLoadedIconTooltipText', 'xpack.data.searchSessionIndicator.resultsLoadedIconTooltipText',
{ {
defaultMessage: 'Results loaded', defaultMessage: 'Results loaded',
} }
), ),
}, },
popover: { popover: {
text: i18n.translate('xpack.data.backgroundSessionIndicator.resultsLoadedText', { text: i18n.translate('xpack.data.searchSessionIndicator.resultsLoadedText', {
defaultMessage: 'Results loaded', defaultMessage: 'Loaded',
}), }),
primaryAction: SaveButton, primaryAction: SaveButton,
secondaryAction: ViewBackgroundSessionsButton, secondaryAction: ViewAllSearchSessionsButton,
}, },
}, },
[SessionState.BackgroundLoading]: { [SearchSessionState.BackgroundLoading]: {
button: { button: {
iconType: EuiLoadingSpinner, iconType: EuiLoadingSpinner,
'aria-label': i18n.translate( 'aria-label': i18n.translate(
'xpack.data.backgroundSessionIndicator.loadingInTheBackgroundIconAriaLabel', 'xpack.data.searchSessionIndicator.loadingInTheBackgroundIconAriaLabel',
{ {
defaultMessage: 'Loading results in the background', defaultMessage: 'Loading results in the background',
} }
), ),
tooltipText: i18n.translate( tooltipText: i18n.translate(
'xpack.data.backgroundSessionIndicator.loadingInTheBackgroundIconTooltipText', 'xpack.data.searchSessionIndicator.loadingInTheBackgroundIconTooltipText',
{ {
defaultMessage: 'Loading results in the background', defaultMessage: 'Loading results in the background',
} }
), ),
}, },
popover: { popover: {
text: i18n.translate('xpack.data.backgroundSessionIndicator.loadingInTheBackgroundText', { text: i18n.translate('xpack.data.searchSessionIndicator.loadingInTheBackgroundText', {
defaultMessage: 'Loading in the background', defaultMessage: 'Loading in the background',
}), }),
primaryAction: CancelButton, primaryAction: CancelButton,
secondaryAction: ViewBackgroundSessionsButton, secondaryAction: ViewAllSearchSessionsButton,
}, },
}, },
[SessionState.BackgroundCompleted]: { [SearchSessionState.BackgroundCompleted]: {
button: { button: {
color: 'success', color: 'success',
iconType: 'checkInCircleFilled', iconType: 'checkInCircleFilled',
'aria-label': i18n.translate( 'aria-label': i18n.translate(
'xpack.data.backgroundSessionIndicator.resultLoadedInTheBackgroundIconAraText', 'xpack.data.searchSessionIndicator.resultLoadedInTheBackgroundIconAraText',
{ {
defaultMessage: 'Results loaded in the background', defaultMessage: 'Results loaded in the background',
} }
), ),
tooltipText: i18n.translate( tooltipText: i18n.translate(
'xpack.data.backgroundSessionIndicator.resultLoadedInTheBackgroundIconTooltipText', 'xpack.data.searchSessionIndicator.resultLoadedInTheBackgroundIconTooltipText',
{ {
defaultMessage: 'Results loaded in the background', defaultMessage: 'Results loaded in the background',
} }
), ),
}, },
popover: { popover: {
text: i18n.translate( text: i18n.translate('xpack.data.searchSessionIndicator.resultLoadedInTheBackgroundText', {
'xpack.data.backgroundSessionIndicator.resultLoadedInTheBackgroundText', defaultMessage: 'Loaded',
{ }),
defaultMessage: 'Results loaded', primaryAction: ViewAllSearchSessionsButton,
}
),
primaryAction: ViewBackgroundSessionsButton,
}, },
}, },
[SessionState.Restored]: { [SearchSessionState.Restored]: {
button: { button: {
color: 'warning', color: 'warning',
iconType: 'refresh', iconType: 'refresh',
'aria-label': i18n.translate( 'aria-label': i18n.translate(
'xpack.data.backgroundSessionIndicator.restoredResultsIconAriaLabel', 'xpack.data.searchSessionIndicator.restoredResultsIconAriaLabel',
{
defaultMessage: 'Results no longer current',
}
),
tooltipText: i18n.translate(
'xpack.data.backgroundSessionIndicator.restoredResultsTooltipText',
{ {
defaultMessage: 'Results no longer current', defaultMessage: 'Results no longer current',
} }
), ),
tooltipText: i18n.translate('xpack.data.searchSessionIndicator.restoredResultsTooltipText', {
defaultMessage: 'Results no longer current',
}),
}, },
popover: { popover: {
text: i18n.translate('xpack.data.backgroundSessionIndicator.restoredText', { text: i18n.translate('xpack.data.searchSessionIndicator.restoredText', {
defaultMessage: 'Results no longer current', defaultMessage: 'Results no longer current',
}), }),
primaryAction: RefreshButton, primaryAction: RefreshButton,
secondaryAction: ViewBackgroundSessionsButton, secondaryAction: ViewAllSearchSessionsButton,
}, },
}, },
[SessionState.Canceled]: { [SearchSessionState.Canceled]: {
button: { button: {
color: 'subdued', color: 'subdued',
iconType: 'refresh', iconType: 'refresh',
'aria-label': i18n.translate('xpack.data.backgroundSessionIndicator.canceledIconAriaLabel', { 'aria-label': i18n.translate('xpack.data.searchSessionIndicator.canceledIconAriaLabel', {
defaultMessage: 'Canceled', defaultMessage: 'Canceled',
}), }),
tooltipText: i18n.translate('xpack.data.backgroundSessionIndicator.canceledTooltipText', { tooltipText: i18n.translate('xpack.data.searchSessionIndicator.canceledTooltipText', {
defaultMessage: 'Search was canceled', defaultMessage: 'Search was canceled',
}), }),
}, },
popover: { popover: {
text: i18n.translate('xpack.data.backgroundSessionIndicator.canceledText', { text: i18n.translate('xpack.data.searchSessionIndicator.canceledText', {
defaultMessage: 'Search was canceled', defaultMessage: 'Search was canceled',
}), }),
primaryAction: RefreshButton, primaryAction: RefreshButton,
secondaryAction: ViewBackgroundSessionsButton, secondaryAction: ViewAllSearchSessionsButton,
}, },
}, },
}; };
const VerticalDivider: React.FC = () => ( const VerticalDivider: React.FC = () => <div className="searchSessionIndicator__verticalDivider" />;
<div className="backgroundSessionIndicator__verticalDivider" />
);
export const BackgroundSessionIndicator: React.FC<BackgroundSessionIndicatorProps> = (props) => { export const SearchSessionIndicator: React.FC<SearchSessionIndicatorProps> = (props) => {
const [isPopoverOpen, setIsPopoverOpen] = React.useState(false); const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);
const onButtonClick = () => setIsPopoverOpen((isOpen) => !isOpen); const onButtonClick = () => setIsPopoverOpen((isOpen) => !isOpen);
const closePopover = () => setIsPopoverOpen(false); const closePopover = () => setIsPopoverOpen(false);
if (!backgroundSessionIndicatorViewStateToProps[props.state]) return null; if (!searchSessionIndicatorViewStateToProps[props.state]) return null;
const { button, popover } = backgroundSessionIndicatorViewStateToProps[props.state]!; const { button, popover } = searchSessionIndicatorViewStateToProps[props.state]!;
return ( return (
<EuiPopover <EuiPopover
@ -283,8 +272,8 @@ export const BackgroundSessionIndicator: React.FC<BackgroundSessionIndicatorProp
closePopover={closePopover} closePopover={closePopover}
anchorPosition={'rightCenter'} anchorPosition={'rightCenter'}
panelPaddingSize={'s'} panelPaddingSize={'s'}
className="backgroundSessionIndicator" className="searchSessionIndicator"
data-test-subj={'backgroundSessionIndicator'} data-test-subj={'searchSessionIndicator'}
data-state={props.state} data-state={props.state}
button={ button={
<EuiToolTip content={props.disabled ? props.disabledReasonText : button.tooltipText}> <EuiToolTip content={props.disabled ? props.disabledReasonText : button.tooltipText}>
@ -302,8 +291,8 @@ export const BackgroundSessionIndicator: React.FC<BackgroundSessionIndicatorProp
responsive={true} responsive={true}
alignItems={'center'} alignItems={'center'}
gutterSize={'s'} gutterSize={'s'}
className="backgroundSessionIndicator__popoverContainer" className="searchSessionIndicator__popoverContainer"
data-test-subj={'backgroundSessionIndicatorPopoverContainer'} data-test-subj={'searchSessionIndicatorPopoverContainer'}
> >
<EuiFlexItem grow={true}> <EuiFlexItem grow={true}>
<EuiText size="s" color={'subdued'}> <EuiText size="s" color={'subdued'}>
@ -332,4 +321,4 @@ export const BackgroundSessionIndicator: React.FC<BackgroundSessionIndicatorProp
// React.lazy() needs default: // React.lazy() needs default:
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
export default BackgroundSessionIndicator; export default SearchSessionIndicator;

View file

@ -13,9 +13,9 @@ import {
import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/server'; import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/server';
import { ENHANCED_ES_SEARCH_STRATEGY, EQL_SEARCH_STRATEGY } from '../common'; import { ENHANCED_ES_SEARCH_STRATEGY, EQL_SEARCH_STRATEGY } from '../common';
import { registerSessionRoutes } from './routes'; import { registerSessionRoutes } from './routes';
import { backgroundSessionMapping } from './saved_objects'; import { searchSessionMapping } from './saved_objects';
import { import {
BackgroundSessionService, SearchSessionService,
enhancedEsSearchStrategyProvider, enhancedEsSearchStrategyProvider,
eqlSearchStrategyProvider, eqlSearchStrategyProvider,
} from './search'; } from './search';
@ -28,7 +28,7 @@ interface SetupDependencies {
export class EnhancedDataServerPlugin implements Plugin<void, void, SetupDependencies> { export class EnhancedDataServerPlugin implements Plugin<void, void, SetupDependencies> {
private readonly logger: Logger; private readonly logger: Logger;
private sessionService!: BackgroundSessionService; private sessionService!: SearchSessionService;
constructor(private initializerContext: PluginInitializerContext) { constructor(private initializerContext: PluginInitializerContext) {
this.logger = initializerContext.logger.get('data_enhanced'); this.logger = initializerContext.logger.get('data_enhanced');
@ -38,7 +38,7 @@ export class EnhancedDataServerPlugin implements Plugin<void, void, SetupDepende
const usage = deps.usageCollection ? usageProvider(core) : undefined; const usage = deps.usageCollection ? usageProvider(core) : undefined;
core.uiSettings.register(getUiSettings()); core.uiSettings.register(getUiSettings());
core.savedObjects.registerType(backgroundSessionMapping); core.savedObjects.registerType(searchSessionMapping);
deps.data.search.registerSearchStrategy( deps.data.search.registerSearchStrategy(
ENHANCED_ES_SEARCH_STRATEGY, ENHANCED_ES_SEARCH_STRATEGY,
@ -54,7 +54,7 @@ export class EnhancedDataServerPlugin implements Plugin<void, void, SetupDepende
eqlSearchStrategyProvider(this.logger) eqlSearchStrategyProvider(this.logger)
); );
this.sessionService = new BackgroundSessionService(this.logger); this.sessionService = new SearchSessionService(this.logger);
deps.data.__enhance({ deps.data.__enhance({
search: { search: {

View file

@ -4,4 +4,4 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
export * from './background_session'; export * from './search_session';

View file

@ -6,10 +6,10 @@
import { SavedObjectsType } from 'kibana/server'; import { SavedObjectsType } from 'kibana/server';
export const BACKGROUND_SESSION_TYPE = 'background-session'; export const SEARCH_SESSION_TYPE = 'search-session';
export const backgroundSessionMapping: SavedObjectsType = { export const searchSessionMapping: SavedObjectsType = {
name: BACKGROUND_SESSION_TYPE, name: SEARCH_SESSION_TYPE,
namespaceType: 'single', namespaceType: 'single',
hidden: true, hidden: true,
mappings: { mappings: {

View file

@ -8,11 +8,11 @@ import { BehaviorSubject, of } from 'rxjs';
import type { SavedObject, SavedObjectsClientContract } from 'kibana/server'; import type { SavedObject, SavedObjectsClientContract } from 'kibana/server';
import type { SearchStrategyDependencies } from '../../../../../../src/plugins/data/server'; import type { SearchStrategyDependencies } from '../../../../../../src/plugins/data/server';
import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks';
import { BackgroundSessionStatus } from '../../../common'; import { SearchSessionStatus } from '../../../common';
import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; import { SEARCH_SESSION_TYPE } from '../../saved_objects';
import { import {
BackgroundSessionDependencies, SearchSessionDependencies,
BackgroundSessionService, SearchSessionService,
INMEM_TRACKING_INTERVAL, INMEM_TRACKING_INTERVAL,
MAX_UPDATE_RETRIES, MAX_UPDATE_RETRIES,
SessionInfo, SessionInfo,
@ -24,9 +24,9 @@ import { ConfigSchema } from '../../../config';
const flushPromises = () => new Promise((resolve) => setImmediate(resolve)); const flushPromises = () => new Promise((resolve) => setImmediate(resolve));
describe('BackgroundSessionService', () => { describe('SearchSessionService', () => {
let savedObjectsClient: jest.Mocked<SavedObjectsClientContract>; let savedObjectsClient: jest.Mocked<SavedObjectsClientContract>;
let service: BackgroundSessionService; let service: SearchSessionService;
const MOCK_SESSION_ID = 'session-id-mock'; const MOCK_SESSION_ID = 'session-id-mock';
const MOCK_ASYNC_ID = '123456'; const MOCK_ASYNC_ID = '123456';
@ -93,7 +93,7 @@ describe('BackgroundSessionService', () => {
const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489';
const mockSavedObject: SavedObject = { const mockSavedObject: SavedObject = {
id: 'd7170a35-7e2c-48d6-8dec-9a056721b489', id: 'd7170a35-7e2c-48d6-8dec-9a056721b489',
type: BACKGROUND_SESSION_TYPE, type: SEARCH_SESSION_TYPE,
attributes: { attributes: {
name: 'my_name', name: 'my_name',
appId: 'my_app_id', appId: 'my_app_id',
@ -110,7 +110,7 @@ describe('BackgroundSessionService', () => {
warn: jest.fn(), warn: jest.fn(),
error: jest.fn(), error: jest.fn(),
}; };
service = new BackgroundSessionService(mockLogger); service = new SearchSessionService(mockLogger);
}); });
it('search throws if `name` is not provided', () => { it('search throws if `name` is not provided', () => {
@ -131,7 +131,7 @@ describe('BackgroundSessionService', () => {
const response = await service.get(sessionId, { savedObjectsClient }); const response = await service.get(sessionId, { savedObjectsClient });
expect(response).toBe(mockSavedObject); expect(response).toBe(mockSavedObject);
expect(savedObjectsClient.get).toHaveBeenCalledWith(BACKGROUND_SESSION_TYPE, sessionId); expect(savedObjectsClient.get).toHaveBeenCalledWith(SEARCH_SESSION_TYPE, sessionId);
}); });
it('find calls saved objects client', async () => { it('find calls saved objects client', async () => {
@ -153,7 +153,7 @@ describe('BackgroundSessionService', () => {
expect(response).toBe(mockResponse); expect(response).toBe(mockResponse);
expect(savedObjectsClient.find).toHaveBeenCalledWith({ expect(savedObjectsClient.find).toHaveBeenCalledWith({
...options, ...options,
type: BACKGROUND_SESSION_TYPE, type: SEARCH_SESSION_TYPE,
}); });
}); });
@ -169,7 +169,7 @@ describe('BackgroundSessionService', () => {
expect(response).toBe(mockUpdateSavedObject); expect(response).toBe(mockUpdateSavedObject);
expect(savedObjectsClient.update).toHaveBeenCalledWith( expect(savedObjectsClient.update).toHaveBeenCalledWith(
BACKGROUND_SESSION_TYPE, SEARCH_SESSION_TYPE,
sessionId, sessionId,
attributes attributes
); );
@ -181,14 +181,14 @@ describe('BackgroundSessionService', () => {
const response = await service.delete(sessionId, { savedObjectsClient }); const response = await service.delete(sessionId, { savedObjectsClient });
expect(response).toEqual({}); expect(response).toEqual({});
expect(savedObjectsClient.delete).toHaveBeenCalledWith(BACKGROUND_SESSION_TYPE, sessionId); expect(savedObjectsClient.delete).toHaveBeenCalledWith(SEARCH_SESSION_TYPE, sessionId);
}); });
describe('search', () => { describe('search', () => {
const mockSearch = jest.fn().mockReturnValue(of({})); const mockSearch = jest.fn().mockReturnValue(of({}));
const mockStrategy = { search: mockSearch }; const mockStrategy = { search: mockSearch };
const mockSearchDeps = {} as SearchStrategyDependencies; const mockSearchDeps = {} as SearchStrategyDependencies;
const mockDeps = {} as BackgroundSessionDependencies; const mockDeps = {} as SearchSessionDependencies;
beforeEach(() => { beforeEach(() => {
mockSearch.mockClear(); mockSearch.mockClear();
@ -300,14 +300,14 @@ describe('BackgroundSessionService', () => {
); );
expect(savedObjectsClient.create).toHaveBeenCalledWith( expect(savedObjectsClient.create).toHaveBeenCalledWith(
BACKGROUND_SESSION_TYPE, SEARCH_SESSION_TYPE,
{ {
name, name,
created, created,
expires, expires,
initialState: {}, initialState: {},
restoreState: {}, restoreState: {},
status: BackgroundSessionStatus.IN_PROGRESS, status: SearchSessionStatus.IN_PROGRESS,
idMapping: {}, idMapping: {},
appId, appId,
urlGeneratorId, urlGeneratorId,
@ -335,7 +335,7 @@ describe('BackgroundSessionService', () => {
{ savedObjectsClient } { savedObjectsClient }
); );
expect(savedObjectsClient.update).toHaveBeenCalledWith(BACKGROUND_SESSION_TYPE, sessionId, { expect(savedObjectsClient.update).toHaveBeenCalledWith(SEARCH_SESSION_TYPE, sessionId, {
idMapping: { idMapping: {
[requestHash]: { [requestHash]: {
id: searchId, id: searchId,
@ -385,7 +385,7 @@ describe('BackgroundSessionService', () => {
const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0';
const mockSession = { const mockSession = {
id: 'd7170a35-7e2c-48d6-8dec-9a056721b489', id: 'd7170a35-7e2c-48d6-8dec-9a056721b489',
type: BACKGROUND_SESSION_TYPE, type: SEARCH_SESSION_TYPE,
attributes: { attributes: {
name: 'my_name', name: 'my_name',
appId: 'my_app_id', appId: 'my_app_id',

View file

@ -30,12 +30,12 @@ import {
SearchStrategyDependencies, SearchStrategyDependencies,
} from '../../../../../../src/plugins/data/server'; } from '../../../../../../src/plugins/data/server';
import { import {
BackgroundSessionSavedObjectAttributes, SearchSessionSavedObjectAttributes,
BackgroundSessionFindOptions, SearchSessionFindOptions,
BackgroundSessionSearchInfo, SearchSessionRequestInfo,
BackgroundSessionStatus, SearchSessionStatus,
} from '../../../common'; } from '../../../common';
import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; import { SEARCH_SESSION_TYPE } from '../../saved_objects';
import { createRequestHash } from './utils'; import { createRequestHash } from './utils';
import { ConfigSchema } from '../../../config'; import { ConfigSchema } from '../../../config';
@ -45,17 +45,17 @@ export const INMEM_TRACKING_INTERVAL = 10 * 1000;
export const INMEM_TRACKING_TIMEOUT_SEC = 60; export const INMEM_TRACKING_TIMEOUT_SEC = 60;
export const MAX_UPDATE_RETRIES = 3; export const MAX_UPDATE_RETRIES = 3;
export interface BackgroundSessionDependencies { export interface SearchSessionDependencies {
savedObjectsClient: SavedObjectsClientContract; savedObjectsClient: SavedObjectsClientContract;
} }
export interface SessionInfo { export interface SessionInfo {
insertTime: Moment; insertTime: Moment;
retryCount: number; retryCount: number;
ids: Map<string, BackgroundSessionSearchInfo>; ids: Map<string, SearchSessionRequestInfo>;
} }
export class BackgroundSessionService implements ISessionService { export class SearchSessionService implements ISessionService {
/** /**
* Map of sessionId to { [requestHash]: searchId } * Map of sessionId to { [requestHash]: searchId }
* @private * @private
@ -79,7 +79,7 @@ export class BackgroundSessionService implements ISessionService {
const config = await config$.pipe(first()).toPromise(); const config = await config$.pipe(first()).toPromise();
if (config.search.sendToBackground.enabled) { if (config.search.sendToBackground.enabled) {
this.logger.debug(`setupMonitoring | Enabling monitoring`); this.logger.debug(`setupMonitoring | Enabling monitoring`);
const internalRepo = core.savedObjects.createInternalRepository([BACKGROUND_SESSION_TYPE]); const internalRepo = core.savedObjects.createInternalRepository([SEARCH_SESSION_TYPE]);
this.internalSavedObjectsClient = new SavedObjectsClient(internalRepo); this.internalSavedObjectsClient = new SavedObjectsClient(internalRepo);
this.monitorMappedIds(); this.monitorMappedIds();
} }
@ -92,7 +92,7 @@ export class BackgroundSessionService implements ISessionService {
private sessionIdsAsFilters(sessionIds: string[]): KueryNode { private sessionIdsAsFilters(sessionIds: string[]): KueryNode {
return nodeBuilder.or( return nodeBuilder.or(
sessionIds.map((id) => { sessionIds.map((id) => {
return nodeBuilder.is(`${BACKGROUND_SESSION_TYPE}.attributes.sessionId`, id); return nodeBuilder.is(`${SEARCH_SESSION_TYPE}.attributes.sessionId`, id);
}) })
); );
} }
@ -107,9 +107,9 @@ export class BackgroundSessionService implements ISessionService {
*/ */
private async getAllMappedSavedObjects() { private async getAllMappedSavedObjects() {
const filter = this.sessionIdsAsFilters(Array.from(this.sessionSearchMap.keys())); const filter = this.sessionIdsAsFilters(Array.from(this.sessionSearchMap.keys()));
const res = await this.internalSavedObjectsClient.find<BackgroundSessionSavedObjectAttributes>({ const res = await this.internalSavedObjectsClient.find<SearchSessionSavedObjectAttributes>({
perPage: INMEM_MAX_SESSIONS, // If there are more sessions in memory, they will be synced when some items are cleared out. perPage: INMEM_MAX_SESSIONS, // If there are more sessions in memory, they will be synced when some items are cleared out.
type: BACKGROUND_SESSION_TYPE, type: SEARCH_SESSION_TYPE,
filter, filter,
namespaces: ['*'], namespaces: ['*'],
}); });
@ -175,13 +175,13 @@ export class BackgroundSessionService implements ISessionService {
} }
private async updateAllSavedObjects( private async updateAllSavedObjects(
activeMappingObjects: Array<SavedObject<BackgroundSessionSavedObjectAttributes>> activeMappingObjects: Array<SavedObject<SearchSessionSavedObjectAttributes>>
) { ) {
if (!activeMappingObjects.length) return []; if (!activeMappingObjects.length) return [];
this.logger.debug(`updateAllSavedObjects | Updating ${activeMappingObjects.length} items`); this.logger.debug(`updateAllSavedObjects | Updating ${activeMappingObjects.length} items`);
const updatedSessions: Array< const updatedSessions: Array<
SavedObjectsBulkUpdateObject<BackgroundSessionSavedObjectAttributes> SavedObjectsBulkUpdateObject<SearchSessionSavedObjectAttributes>
> = activeMappingObjects > = activeMappingObjects
.filter((so) => !so.error) .filter((so) => !so.error)
.map((sessionSavedObject) => { .map((sessionSavedObject) => {
@ -197,7 +197,7 @@ export class BackgroundSessionService implements ISessionService {
}; };
}); });
const updateResults = await this.internalSavedObjectsClient.bulkUpdate<BackgroundSessionSavedObjectAttributes>( const updateResults = await this.internalSavedObjectsClient.bulkUpdate<SearchSessionSavedObjectAttributes>(
updatedSessions updatedSessions
); );
return updateResults.saved_objects; return updateResults.saved_objects;
@ -208,7 +208,7 @@ export class BackgroundSessionService implements ISessionService {
searchRequest: Request, searchRequest: Request,
options: ISearchOptions, options: ISearchOptions,
searchDeps: SearchStrategyDependencies, searchDeps: SearchStrategyDependencies,
deps: BackgroundSessionDependencies deps: SearchSessionDependencies
): Observable<Response> { ): Observable<Response> {
// If this is a restored background search session, look up the ID using the provided sessionId // If this is a restored background search session, look up the ID using the provided sessionId
const getSearchRequest = async () => const getSearchRequest = async () =>
@ -236,12 +236,12 @@ export class BackgroundSessionService implements ISessionService {
appId, appId,
created = new Date().toISOString(), created = new Date().toISOString(),
expires = new Date(Date.now() + DEFAULT_EXPIRATION).toISOString(), expires = new Date(Date.now() + DEFAULT_EXPIRATION).toISOString(),
status = BackgroundSessionStatus.IN_PROGRESS, status = SearchSessionStatus.IN_PROGRESS,
urlGeneratorId, urlGeneratorId,
initialState = {}, initialState = {},
restoreState = {}, restoreState = {},
}: Partial<BackgroundSessionSavedObjectAttributes>, }: Partial<SearchSessionSavedObjectAttributes>,
{ savedObjectsClient }: BackgroundSessionDependencies { savedObjectsClient }: SearchSessionDependencies
) => { ) => {
if (!name) throw new Error('Name is required'); if (!name) throw new Error('Name is required');
if (!appId) throw new Error('AppId is required'); if (!appId) throw new Error('AppId is required');
@ -261,8 +261,8 @@ export class BackgroundSessionService implements ISessionService {
appId, appId,
sessionId, sessionId,
}; };
const session = await savedObjectsClient.create<BackgroundSessionSavedObjectAttributes>( const session = await savedObjectsClient.create<SearchSessionSavedObjectAttributes>(
BACKGROUND_SESSION_TYPE, SEARCH_SESSION_TYPE,
attributes, attributes,
{ id: sessionId } { id: sessionId }
); );
@ -271,42 +271,42 @@ export class BackgroundSessionService implements ISessionService {
}; };
// TODO: Throw an error if this session doesn't belong to this user // TODO: Throw an error if this session doesn't belong to this user
public get = (sessionId: string, { savedObjectsClient }: BackgroundSessionDependencies) => { public get = (sessionId: string, { savedObjectsClient }: SearchSessionDependencies) => {
this.logger.debug(`get | ${sessionId}`); this.logger.debug(`get | ${sessionId}`);
return savedObjectsClient.get<BackgroundSessionSavedObjectAttributes>( return savedObjectsClient.get<SearchSessionSavedObjectAttributes>(
BACKGROUND_SESSION_TYPE, SEARCH_SESSION_TYPE,
sessionId sessionId
); );
}; };
// TODO: Throw an error if this session doesn't belong to this user // TODO: Throw an error if this session doesn't belong to this user
public find = ( public find = (
options: BackgroundSessionFindOptions, options: SearchSessionFindOptions,
{ savedObjectsClient }: BackgroundSessionDependencies { savedObjectsClient }: SearchSessionDependencies
) => { ) => {
return savedObjectsClient.find<BackgroundSessionSavedObjectAttributes>({ return savedObjectsClient.find<SearchSessionSavedObjectAttributes>({
...options, ...options,
type: BACKGROUND_SESSION_TYPE, type: SEARCH_SESSION_TYPE,
}); });
}; };
// TODO: Throw an error if this session doesn't belong to this user // TODO: Throw an error if this session doesn't belong to this user
public update = ( public update = (
sessionId: string, sessionId: string,
attributes: Partial<BackgroundSessionSavedObjectAttributes>, attributes: Partial<SearchSessionSavedObjectAttributes>,
{ savedObjectsClient }: BackgroundSessionDependencies { savedObjectsClient }: SearchSessionDependencies
) => { ) => {
this.logger.debug(`update | ${sessionId}`); this.logger.debug(`update | ${sessionId}`);
return savedObjectsClient.update<BackgroundSessionSavedObjectAttributes>( return savedObjectsClient.update<SearchSessionSavedObjectAttributes>(
BACKGROUND_SESSION_TYPE, SEARCH_SESSION_TYPE,
sessionId, sessionId,
attributes attributes
); );
}; };
// TODO: Throw an error if this session doesn't belong to this user // TODO: Throw an error if this session doesn't belong to this user
public delete = (sessionId: string, { savedObjectsClient }: BackgroundSessionDependencies) => { public delete = (sessionId: string, { savedObjectsClient }: SearchSessionDependencies) => {
return savedObjectsClient.delete(BACKGROUND_SESSION_TYPE, sessionId); return savedObjectsClient.delete(SEARCH_SESSION_TYPE, sessionId);
}; };
/** /**
@ -318,7 +318,7 @@ export class BackgroundSessionService implements ISessionService {
searchRequest: IKibanaSearchRequest, searchRequest: IKibanaSearchRequest,
searchId: string, searchId: string,
{ sessionId, isStored, strategy }: ISearchOptions, { sessionId, isStored, strategy }: ISearchOptions,
deps: BackgroundSessionDependencies deps: SearchSessionDependencies
) => { ) => {
if (!sessionId || !searchId) return; if (!sessionId || !searchId) return;
this.logger.debug(`trackId | ${sessionId} | ${searchId}`); this.logger.debug(`trackId | ${sessionId} | ${searchId}`);
@ -339,7 +339,7 @@ export class BackgroundSessionService implements ISessionService {
const map = this.sessionSearchMap.get(sessionId) ?? { const map = this.sessionSearchMap.get(sessionId) ?? {
insertTime: moment(), insertTime: moment(),
retryCount: 0, retryCount: 0,
ids: new Map<string, BackgroundSessionSearchInfo>(), ids: new Map<string, SearchSessionRequestInfo>(),
}; };
map.ids.set(requestHash, searchInfo); map.ids.set(requestHash, searchInfo);
this.sessionSearchMap.set(sessionId, map); this.sessionSearchMap.set(sessionId, map);
@ -354,7 +354,7 @@ export class BackgroundSessionService implements ISessionService {
public getId = async ( public getId = async (
searchRequest: IKibanaSearchRequest, searchRequest: IKibanaSearchRequest,
{ sessionId, isStored, isRestore }: ISearchOptions, { sessionId, isStored, isRestore }: ISearchOptions,
deps: BackgroundSessionDependencies deps: SearchSessionDependencies
) => { ) => {
if (!sessionId) { if (!sessionId) {
throw new Error('Session ID is required'); throw new Error('Session ID is required');
@ -376,7 +376,7 @@ export class BackgroundSessionService implements ISessionService {
public asScopedProvider = ({ savedObjects }: CoreStart) => { public asScopedProvider = ({ savedObjects }: CoreStart) => {
return (request: KibanaRequest) => { return (request: KibanaRequest) => {
const savedObjectsClient = savedObjects.getScopedClient(request, { const savedObjectsClient = savedObjects.getScopedClient(request, {
includedHiddenTypes: [BACKGROUND_SESSION_TYPE], includedHiddenTypes: [SEARCH_SESSION_TYPE],
}); });
const deps = { savedObjectsClient }; const deps = { savedObjectsClient };
return { return {
@ -384,11 +384,11 @@ export class BackgroundSessionService implements ISessionService {
strategy: ISearchStrategy<Request, Response>, strategy: ISearchStrategy<Request, Response>,
...args: Parameters<ISearchStrategy<Request, Response>['search']> ...args: Parameters<ISearchStrategy<Request, Response>['search']>
) => this.search(strategy, ...args, deps), ) => this.search(strategy, ...args, deps),
save: (sessionId: string, attributes: Partial<BackgroundSessionSavedObjectAttributes>) => save: (sessionId: string, attributes: Partial<SearchSessionSavedObjectAttributes>) =>
this.save(sessionId, attributes, deps), this.save(sessionId, attributes, deps),
get: (sessionId: string) => this.get(sessionId, deps), get: (sessionId: string) => this.get(sessionId, deps),
find: (options: BackgroundSessionFindOptions) => this.find(options, deps), find: (options: SearchSessionFindOptions) => this.find(options, deps),
update: (sessionId: string, attributes: Partial<BackgroundSessionSavedObjectAttributes>) => update: (sessionId: string, attributes: Partial<SearchSessionSavedObjectAttributes>) =>
this.update(sessionId, attributes, deps), this.update(sessionId, attributes, deps),
delete: (sessionId: string) => this.delete(sessionId, deps), delete: (sessionId: string) => this.delete(sessionId, deps),
}; };

View file

@ -73,7 +73,7 @@ Array [
"dashboard", "dashboard",
"query", "query",
"url", "url",
"background-session", "search-session",
], ],
"read": Array [ "read": Array [
"index-pattern", "index-pattern",
@ -207,7 +207,7 @@ Array [
"query", "query",
"index-pattern", "index-pattern",
"url", "url",
"background-session", "search-session",
], ],
"read": Array [], "read": Array [],
}, },
@ -559,7 +559,7 @@ Array [
"dashboard", "dashboard",
"query", "query",
"url", "url",
"background-session", "search-session",
], ],
"read": Array [ "read": Array [
"index-pattern", "index-pattern",
@ -693,7 +693,7 @@ Array [
"query", "query",
"index-pattern", "index-pattern",
"url", "url",
"background-session", "search-session",
], ],
"read": Array [], "read": Array [],
}, },

View file

@ -89,7 +89,7 @@ export const buildOSSFeatures = ({ savedObjectTypes, includeTimelion }: BuildOSS
), ),
includeIn: 'all', includeIn: 'all',
savedObject: { savedObject: {
all: ['background-session'], all: ['search-session'],
read: [], read: [],
}, },
ui: ['storeSearchSession'], ui: ['storeSearchSession'],
@ -254,7 +254,7 @@ export const buildOSSFeatures = ({ savedObjectTypes, includeTimelion }: BuildOSS
), ),
includeIn: 'all', includeIn: 'all',
savedObject: { savedObject: {
all: ['background-session'], all: ['search-session'],
read: [], read: [],
}, },
ui: ['storeSearchSession'], ui: ['storeSearchSession'],

View file

@ -7138,30 +7138,6 @@
"xpack.dashboardMode.uiSettings.dashboardsOnlyRolesTitle": "ダッシュボード専用ロール", "xpack.dashboardMode.uiSettings.dashboardsOnlyRolesTitle": "ダッシュボード専用ロール",
"xpack.data.advancedSettings.searchTimeout": "検索タイムアウト", "xpack.data.advancedSettings.searchTimeout": "検索タイムアウト",
"xpack.data.advancedSettings.searchTimeoutDesc": "検索セッションの最大タイムアウトを変更するか、0 に設定してタイムアウトを無効にすると、クエリは完了するまで実行されます。", "xpack.data.advancedSettings.searchTimeoutDesc": "検索セッションの最大タイムアウトを変更するか、0 に設定してタイムアウトを無効にすると、クエリは完了するまで実行されます。",
"xpack.data.backgroundSessionIndicator.cancelButtonText": "キャンセル",
"xpack.data.backgroundSessionIndicator.canceledIconAriaLabel": "キャンセル",
"xpack.data.backgroundSessionIndicator.canceledText": "検索がキャンセルされました",
"xpack.data.backgroundSessionIndicator.canceledTooltipText": "検索がキャンセルされました",
"xpack.data.backgroundSessionIndicator.continueInBackgroundButtonText": "バックグラウンドで続行",
"xpack.data.backgroundSessionIndicator.disabledDueToAutoRefreshMessage": "自動更新が有効な場合は、バックグラウンドに送信できません。",
"xpack.data.backgroundSessionIndicator.loadingInTheBackgroundIconAriaLabel": "バックグラウンドで結果を読み込んでいます",
"xpack.data.backgroundSessionIndicator.loadingInTheBackgroundIconTooltipText": "バックグラウンドで結果を読み込んでいます",
"xpack.data.backgroundSessionIndicator.loadingInTheBackgroundText": "バックグラウンドで読み込んでいます",
"xpack.data.backgroundSessionIndicator.loadingResultsIconAriaLabel": "結果を読み込み中",
"xpack.data.backgroundSessionIndicator.loadingResultsIconTooltipText": "結果を読み込み中",
"xpack.data.backgroundSessionIndicator.loadingResultsText": "読み込み中",
"xpack.data.backgroundSessionIndicator.refreshButtonText": "更新",
"xpack.data.backgroundSessionIndicator.restoredResultsIconAriaLabel": "結果が最新ではありません",
"xpack.data.backgroundSessionIndicator.restoredResultsTooltipText": "結果が最新ではありません",
"xpack.data.backgroundSessionIndicator.restoredText": "結果が最新ではありません",
"xpack.data.backgroundSessionIndicator.resultLoadedInTheBackgroundIconAraText": "結果がバックグラウンドで読み込まれました",
"xpack.data.backgroundSessionIndicator.resultLoadedInTheBackgroundIconTooltipText": "結果がバックグラウンドで読み込まれました",
"xpack.data.backgroundSessionIndicator.resultLoadedInTheBackgroundText": "結果が読み込まれました",
"xpack.data.backgroundSessionIndicator.resultsLoadedIconAriaLabel": "結果が読み込まれました",
"xpack.data.backgroundSessionIndicator.resultsLoadedIconTooltipText": "結果が読み込まれました",
"xpack.data.backgroundSessionIndicator.resultsLoadedText": "結果が読み込まれました",
"xpack.data.backgroundSessionIndicator.saveButtonText": "保存",
"xpack.data.backgroundSessionIndicator.viewBackgroundSessionsLinkText": "バックグラウンドセッションを表示",
"xpack.data.kueryAutocomplete.andOperatorDescription": "{bothArguments} が true であることを条件とする", "xpack.data.kueryAutocomplete.andOperatorDescription": "{bothArguments} が true であることを条件とする",
"xpack.data.kueryAutocomplete.andOperatorDescription.bothArgumentsText": "両方の引数", "xpack.data.kueryAutocomplete.andOperatorDescription.bothArgumentsText": "両方の引数",
"xpack.data.kueryAutocomplete.equalOperatorDescription": "一部の値に{equals}", "xpack.data.kueryAutocomplete.equalOperatorDescription": "一部の値に{equals}",

View file

@ -7157,30 +7157,6 @@
"xpack.dashboardMode.uiSettings.dashboardsOnlyRolesTitle": "仅限仪表板的角色", "xpack.dashboardMode.uiSettings.dashboardsOnlyRolesTitle": "仅限仪表板的角色",
"xpack.data.advancedSettings.searchTimeout": "搜索超时", "xpack.data.advancedSettings.searchTimeout": "搜索超时",
"xpack.data.advancedSettings.searchTimeoutDesc": "更改搜索会话的最大超时值,或设置为 0 以禁用超时,让查询运行至结束。", "xpack.data.advancedSettings.searchTimeoutDesc": "更改搜索会话的最大超时值,或设置为 0 以禁用超时,让查询运行至结束。",
"xpack.data.backgroundSessionIndicator.cancelButtonText": "取消",
"xpack.data.backgroundSessionIndicator.canceledIconAriaLabel": "已取消",
"xpack.data.backgroundSessionIndicator.canceledText": "搜索已取消",
"xpack.data.backgroundSessionIndicator.canceledTooltipText": "搜索已取消",
"xpack.data.backgroundSessionIndicator.continueInBackgroundButtonText": "在后台继续",
"xpack.data.backgroundSessionIndicator.disabledDueToAutoRefreshMessage": "启用自动刷新后,“发送到后台”不可用。",
"xpack.data.backgroundSessionIndicator.loadingInTheBackgroundIconAriaLabel": "正在后台加载结果",
"xpack.data.backgroundSessionIndicator.loadingInTheBackgroundIconTooltipText": "正在后台加载结果",
"xpack.data.backgroundSessionIndicator.loadingInTheBackgroundText": "正在后台加载",
"xpack.data.backgroundSessionIndicator.loadingResultsIconAriaLabel": "正在加载结果",
"xpack.data.backgroundSessionIndicator.loadingResultsIconTooltipText": "正在加载结果",
"xpack.data.backgroundSessionIndicator.loadingResultsText": "正在加载",
"xpack.data.backgroundSessionIndicator.refreshButtonText": "刷新",
"xpack.data.backgroundSessionIndicator.restoredResultsIconAriaLabel": "结果不再是最新",
"xpack.data.backgroundSessionIndicator.restoredResultsTooltipText": "结果不再是最新",
"xpack.data.backgroundSessionIndicator.restoredText": "结果不再是最新",
"xpack.data.backgroundSessionIndicator.resultLoadedInTheBackgroundIconAraText": "结果已后台加载",
"xpack.data.backgroundSessionIndicator.resultLoadedInTheBackgroundIconTooltipText": "结果已后台加载",
"xpack.data.backgroundSessionIndicator.resultLoadedInTheBackgroundText": "结果已加载",
"xpack.data.backgroundSessionIndicator.resultsLoadedIconAriaLabel": "结果已加载",
"xpack.data.backgroundSessionIndicator.resultsLoadedIconTooltipText": "结果已加载",
"xpack.data.backgroundSessionIndicator.resultsLoadedText": "结果已加载",
"xpack.data.backgroundSessionIndicator.saveButtonText": "保存",
"xpack.data.backgroundSessionIndicator.viewBackgroundSessionsLinkText": "查看后台会话",
"xpack.data.kueryAutocomplete.andOperatorDescription": "需要{bothArguments}为 true", "xpack.data.kueryAutocomplete.andOperatorDescription": "需要{bothArguments}为 true",
"xpack.data.kueryAutocomplete.andOperatorDescription.bothArgumentsText": "两个参数都", "xpack.data.kueryAutocomplete.andOperatorDescription.bothArgumentsText": "两个参数都",
"xpack.data.kueryAutocomplete.equalOperatorDescription": "{equals}某一值", "xpack.data.kueryAutocomplete.equalOperatorDescription": "{equals}某一值",

View file

@ -19,7 +19,7 @@
"application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd", "application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd",
"application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724",
"application_usage_transactional": "3d1b76c39bfb2cc8296b024d73854724", "application_usage_transactional": "3d1b76c39bfb2cc8296b024d73854724",
"background-session": "404e2e2355a045f400c393e751445b42", "search-session": "404e2e2355a045f400c393e751445b42",
"canvas-element": "7390014e1091044523666d97247392fc", "canvas-element": "7390014e1091044523666d97247392fc",
"canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231",
"canvas-workpad-template": "ae2673f678281e2c055d764b153e9715", "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715",
@ -297,7 +297,7 @@
"dynamic": "false", "dynamic": "false",
"type": "object" "type": "object"
}, },
"background-session": { "search-session": {
"properties": { "properties": {
"appId": { "appId": {
"type": "keyword" "type": "keyword"

View file

@ -19,7 +19,7 @@
"application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd", "application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd",
"application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724",
"application_usage_transactional": "3d1b76c39bfb2cc8296b024d73854724", "application_usage_transactional": "3d1b76c39bfb2cc8296b024d73854724",
"background-session": "721df406dbb7e35ac22e4df6c3ad2b2a", "search-session": "721df406dbb7e35ac22e4df6c3ad2b2a",
"canvas-element": "7390014e1091044523666d97247392fc", "canvas-element": "7390014e1091044523666d97247392fc",
"canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231",
"canvas-workpad-template": "ae2673f678281e2c055d764b153e9715", "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715",

View file

@ -7,8 +7,8 @@
import { FtrProviderContext } from '../ftr_provider_context'; import { FtrProviderContext } from '../ftr_provider_context';
import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper'; import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper';
const SEND_TO_BACKGROUND_TEST_SUBJ = 'backgroundSessionIndicator'; const SEND_TO_BACKGROUND_TEST_SUBJ = 'searchSessionIndicator';
const SEND_TO_BACKGROUND_POPOVER_CONTENT_TEST_SUBJ = 'backgroundSessionIndicatorPopoverContainer'; const SEND_TO_BACKGROUND_POPOVER_CONTENT_TEST_SUBJ = 'searchSessionIndicatorPopoverContainer';
type SessionStateType = type SessionStateType =
| 'none' | 'none'
@ -42,26 +42,26 @@ export function SendToBackgroundProvider({ getService }: FtrProviderContext) {
}); });
} }
public async viewBackgroundSessions() { public async viewSearchSessions() {
await this.ensurePopoverOpened(); await this.ensurePopoverOpened();
await testSubjects.click('backgroundSessionIndicatorViewBackgroundSessionsLink'); await testSubjects.click('searchSessionIndicatorviewSearchSessionsLink');
} }
public async save() { public async save() {
await this.ensurePopoverOpened(); await this.ensurePopoverOpened();
await testSubjects.click('backgroundSessionIndicatorSaveBtn'); await testSubjects.click('searchSessionIndicatorSaveBtn');
await this.ensurePopoverClosed(); await this.ensurePopoverClosed();
} }
public async cancel() { public async cancel() {
await this.ensurePopoverOpened(); await this.ensurePopoverOpened();
await testSubjects.click('backgroundSessionIndicatorCancelBtn'); await testSubjects.click('searchSessionIndicatorCancelBtn');
await this.ensurePopoverClosed(); await this.ensurePopoverClosed();
} }
public async refresh() { public async refresh() {
await this.ensurePopoverOpened(); await this.ensurePopoverOpened();
await testSubjects.click('backgroundSessionIndicatorRefreshBtn'); await testSubjects.click('searchSessionIndicatorRefreshBtn');
await this.ensurePopoverClosed(); await this.ensurePopoverClosed();
} }