mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
* notifications ui * increase timeout to 10s * trigger notification from search interceptor * added an enhanced interceptor * added an enhanced interceptor * docs * docs * fix ts * Fix jest tests for interceptor * update docs * docs * Fix handling syntax error in discover * docs and translations * fix scripted fields err Co-authored-by: Lukas Olson <olson.lukas@gmail.com> Co-authored-by: Lukas Olson <olson.lukas@gmail.com>
This commit is contained in:
parent
e34cce58dd
commit
5e94e89640
28 changed files with 620 additions and 123 deletions
|
@ -18,7 +18,9 @@
|
|||
| [IndexPatternSelect](./kibana-plugin-plugins-data-public.indexpatternselect.md) | |
|
||||
| [OptionedParamType](./kibana-plugin-plugins-data-public.optionedparamtype.md) | |
|
||||
| [Plugin](./kibana-plugin-plugins-data-public.plugin.md) | |
|
||||
| [RequestTimeoutError](./kibana-plugin-plugins-data-public.requesttimeouterror.md) | Class used to signify that a request timed out. Useful for applications to conditionally handle this type of error differently than other errors. |
|
||||
| [SearchError](./kibana-plugin-plugins-data-public.searcherror.md) | |
|
||||
| [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) | |
|
||||
| [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) | |
|
||||
| [TimeHistory](./kibana-plugin-plugins-data-public.timehistory.md) | |
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [RequestTimeoutError](./kibana-plugin-plugins-data-public.requesttimeouterror.md) > [(constructor)](./kibana-plugin-plugins-data-public.requesttimeouterror._constructor_.md)
|
||||
|
||||
## RequestTimeoutError.(constructor)
|
||||
|
||||
Constructs a new instance of the `RequestTimeoutError` class
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
constructor(message?: string);
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| message | <code>string</code> | |
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [RequestTimeoutError](./kibana-plugin-plugins-data-public.requesttimeouterror.md)
|
||||
|
||||
## RequestTimeoutError class
|
||||
|
||||
Class used to signify that a request timed out. Useful for applications to conditionally handle this type of error differently than other errors.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare class RequestTimeoutError extends Error
|
||||
```
|
||||
|
||||
## Constructors
|
||||
|
||||
| Constructor | Modifiers | Description |
|
||||
| --- | --- | --- |
|
||||
| [(constructor)(message)](./kibana-plugin-plugins-data-public.requesttimeouterror._constructor_.md) | | Constructs a new instance of the <code>RequestTimeoutError</code> class |
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [(constructor)](./kibana-plugin-plugins-data-public.searchinterceptor._constructor_.md)
|
||||
|
||||
## SearchInterceptor.(constructor)
|
||||
|
||||
This class should be instantiated with a `requestTimeout` corresponding with how many ms after requests are initiated that they should automatically cancel.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
constructor(toasts: ToastsStart, application: ApplicationStart, requestTimeout?: number | undefined);
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| toasts | <code>ToastsStart</code> | |
|
||||
| application | <code>ApplicationStart</code> | |
|
||||
| requestTimeout | <code>number | undefined</code> | |
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [abortController](./kibana-plugin-plugins-data-public.searchinterceptor.abortcontroller.md)
|
||||
|
||||
## SearchInterceptor.abortController property
|
||||
|
||||
`abortController` used to signal all searches to abort.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
protected abortController: AbortController;
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [application](./kibana-plugin-plugins-data-public.searchinterceptor.application.md)
|
||||
|
||||
## SearchInterceptor.application property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
protected readonly application: ApplicationStart;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [getPendingCount$](./kibana-plugin-plugins-data-public.searchinterceptor.getpendingcount_.md)
|
||||
|
||||
## SearchInterceptor.getPendingCount$ property
|
||||
|
||||
Returns an `Observable` over the current number of pending searches. This could mean that one of the search requests is still in flight, or that it has only received partial responses.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
getPendingCount$: () => import("rxjs").Observable<number>;
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [hideToast](./kibana-plugin-plugins-data-public.searchinterceptor.hidetoast.md)
|
||||
|
||||
## SearchInterceptor.hideToast property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
protected hideToast: () => void;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [longRunningToast](./kibana-plugin-plugins-data-public.searchinterceptor.longrunningtoast.md)
|
||||
|
||||
## SearchInterceptor.longRunningToast property
|
||||
|
||||
The current long-running toast (if there is one).
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
protected longRunningToast?: Toast;
|
||||
```
|
|
@ -0,0 +1,33 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md)
|
||||
|
||||
## SearchInterceptor class
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare class SearchInterceptor
|
||||
```
|
||||
|
||||
## Constructors
|
||||
|
||||
| Constructor | Modifiers | Description |
|
||||
| --- | --- | --- |
|
||||
| [(constructor)(toasts, application, requestTimeout)](./kibana-plugin-plugins-data-public.searchinterceptor._constructor_.md) | | This class should be instantiated with a <code>requestTimeout</code> corresponding with how many ms after requests are initiated that they should automatically cancel. |
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Modifiers | Type | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| [abortController](./kibana-plugin-plugins-data-public.searchinterceptor.abortcontroller.md) | | <code>AbortController</code> | <code>abortController</code> used to signal all searches to abort. |
|
||||
| [application](./kibana-plugin-plugins-data-public.searchinterceptor.application.md) | | <code>ApplicationStart</code> | |
|
||||
| [getPendingCount$](./kibana-plugin-plugins-data-public.searchinterceptor.getpendingcount_.md) | | <code>() => import("rxjs").Observable<number></code> | Returns an <code>Observable</code> over the current number of pending searches. This could mean that one of the search requests is still in flight, or that it has only received partial responses. |
|
||||
| [hideToast](./kibana-plugin-plugins-data-public.searchinterceptor.hidetoast.md) | | <code>() => void</code> | |
|
||||
| [longRunningToast](./kibana-plugin-plugins-data-public.searchinterceptor.longrunningtoast.md) | | <code>Toast</code> | The current long-running toast (if there is one). |
|
||||
| [requestTimeout](./kibana-plugin-plugins-data-public.searchinterceptor.requesttimeout.md) | | <code>number | undefined</code> | |
|
||||
| [search](./kibana-plugin-plugins-data-public.searchinterceptor.search.md) | | <code>(search: ISearchGeneric, request: IKibanaSearchRequest, options?: ISearchOptions | undefined) => import("rxjs").Observable<import("../../common/search").IEsSearchResponse<unknown>></code> | Searches using the given <code>search</code> method. Overrides the <code>AbortSignal</code> with one that will abort either when <code>cancelPending</code> is called, when the request times out, or when the original <code>AbortSignal</code> is aborted. Updates the <code>pendingCount</code> when the request is started/finalized. |
|
||||
| [showToast](./kibana-plugin-plugins-data-public.searchinterceptor.showtoast.md) | | <code>() => void</code> | |
|
||||
| [timeoutSubscriptions](./kibana-plugin-plugins-data-public.searchinterceptor.timeoutsubscriptions.md) | | <code>Set<Subscription></code> | The subscriptions from scheduling the automatic timeout for each request. |
|
||||
| [toasts](./kibana-plugin-plugins-data-public.searchinterceptor.toasts.md) | | <code>ToastsStart</code> | |
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [requestTimeout](./kibana-plugin-plugins-data-public.searchinterceptor.requesttimeout.md)
|
||||
|
||||
## SearchInterceptor.requestTimeout property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
protected readonly requestTimeout?: number | undefined;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [search](./kibana-plugin-plugins-data-public.searchinterceptor.search.md)
|
||||
|
||||
## SearchInterceptor.search property
|
||||
|
||||
Searches using the given `search` method. Overrides the `AbortSignal` with one that will abort either when `cancelPending` is called, when the request times out, or when the original `AbortSignal` is aborted. Updates the `pendingCount` when the request is started/finalized.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
search: (search: ISearchGeneric, request: IKibanaSearchRequest, options?: ISearchOptions | undefined) => import("rxjs").Observable<import("../../common/search").IEsSearchResponse<unknown>>;
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [showToast](./kibana-plugin-plugins-data-public.searchinterceptor.showtoast.md)
|
||||
|
||||
## SearchInterceptor.showToast property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
protected showToast: () => void;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [timeoutSubscriptions](./kibana-plugin-plugins-data-public.searchinterceptor.timeoutsubscriptions.md)
|
||||
|
||||
## SearchInterceptor.timeoutSubscriptions property
|
||||
|
||||
The subscriptions from scheduling the automatic timeout for each request.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
protected timeoutSubscriptions: Set<Subscription>;
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) > [toasts](./kibana-plugin-plugins-data-public.searchinterceptor.toasts.md)
|
||||
|
||||
## SearchInterceptor.toasts property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
protected readonly toasts: ToastsStart;
|
||||
```
|
|
@ -374,6 +374,8 @@ export {
|
|||
TabbedAggColumn,
|
||||
TabbedAggRow,
|
||||
TabbedTable,
|
||||
SearchInterceptor,
|
||||
RequestTimeoutError,
|
||||
} from './search';
|
||||
|
||||
// Search namespace
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import { $Values } from '@kbn/utility-types';
|
||||
import _ from 'lodash';
|
||||
import { Action } from 'history';
|
||||
import { ApplicationStart } from 'kibana/public';
|
||||
import { Assign } from '@kbn/utility-types';
|
||||
import { Breadcrumb } from '@elastic/eui';
|
||||
import { Component } from 'react';
|
||||
|
@ -48,6 +49,9 @@ import { SavedObjectsClientContract } from 'src/core/public';
|
|||
import { SearchParams } from 'elasticsearch';
|
||||
import { SearchResponse as SearchResponse_2 } from 'elasticsearch';
|
||||
import { SimpleSavedObject } from 'src/core/public';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { Toast } from 'kibana/public';
|
||||
import { ToastsStart } from 'kibana/public';
|
||||
import { UiActionsSetup } from 'src/plugins/ui_actions/public';
|
||||
import { UiActionsStart } from 'src/plugins/ui_actions/public';
|
||||
import { Unit } from '@elastic/datemath';
|
||||
|
@ -1475,6 +1479,13 @@ export interface RefreshInterval {
|
|||
value: number;
|
||||
}
|
||||
|
||||
// Warning: (ae-missing-release-tag) "RequestTimeoutError" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public
|
||||
export class RequestTimeoutError extends Error {
|
||||
constructor(message?: string);
|
||||
}
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SavedQuery" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
|
@ -1592,6 +1603,28 @@ export class SearchError extends Error {
|
|||
type: string;
|
||||
}
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SearchInterceptor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export class SearchInterceptor {
|
||||
constructor(toasts: ToastsStart, application: ApplicationStart, requestTimeout?: number | undefined);
|
||||
protected abortController: AbortController;
|
||||
// (undocumented)
|
||||
protected readonly application: ApplicationStart;
|
||||
getPendingCount$: () => import("rxjs").Observable<number>;
|
||||
// (undocumented)
|
||||
protected hideToast: () => void;
|
||||
protected longRunningToast?: Toast;
|
||||
// (undocumented)
|
||||
protected readonly requestTimeout?: number | undefined;
|
||||
search: (search: ISearchGeneric, request: IKibanaSearchRequest, options?: ISearchOptions | undefined) => import("rxjs").Observable<import("../../common/search").IEsSearchResponse<unknown>>;
|
||||
// (undocumented)
|
||||
protected showToast: () => void;
|
||||
protected timeoutSubscriptions: Set<Subscription>;
|
||||
// (undocumented)
|
||||
protected readonly toasts: ToastsStart;
|
||||
}
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SearchRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
|
@ -1858,21 +1891,21 @@ export type TSearchStrategyProvider<T extends TStrategyTypes> = (context: ISearc
|
|||
// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "getRoutes" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:380:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:380:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:380:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:380:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:385:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:386:1 - (ae-forgotten-export) The symbol "convertDateRangeToString" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:388:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:397:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:398:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:402:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:403:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:406:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:407:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:410:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:382:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:382:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:382:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:382:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:387:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:388:1 - (ae-forgotten-export) The symbol "convertDateRangeToString" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:390:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:404:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:405:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:408:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:409:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:412: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:33:33 - (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:37:1 - (ae-forgotten-export) The symbol "QueryStateChange" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/types.ts:52:5 - (ae-forgotten-export) The symbol "createFiltersFromEvent" needs to be exported by the entry point index.d.ts
|
||||
|
|
|
@ -57,5 +57,6 @@ export {
|
|||
} from './search_source';
|
||||
|
||||
export { SearchInterceptor } from './search_interceptor';
|
||||
export { RequestTimeoutError } from './request_timeout_error';
|
||||
|
||||
export { FetchOptions } from './fetch';
|
||||
|
|
61
src/plugins/data/public/search/long_query_notification.tsx
Normal file
61
src/plugins/data/public/search/long_query_notification.tsx
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
import { ApplicationStart } from 'kibana/public';
|
||||
import { toMountPoint } from '../../../kibana_react/public';
|
||||
|
||||
interface Props {
|
||||
application: ApplicationStart;
|
||||
}
|
||||
|
||||
export function getLongQueryNotification(props: Props) {
|
||||
return toMountPoint(<LongQueryNotification application={props.application} />);
|
||||
}
|
||||
|
||||
export function LongQueryNotification(props: Props) {
|
||||
return (
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id="data.query.queryBar.longQueryMessage"
|
||||
defaultMessage="With an upgraded license, you can ensure requests have enough time to complete."
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexGroup justifyContent="flexEnd" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
size="s"
|
||||
onClick={async () => {
|
||||
await props.application.navigateToApp(
|
||||
'kibana#/management/elasticsearch/license_management'
|
||||
);
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="data.query.queryBar.licenseOptions"
|
||||
defaultMessage="Go to license options"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -31,10 +31,8 @@ export const searchSetupMock = {
|
|||
|
||||
export const searchStartMock: jest.Mocked<ISearchStart> = {
|
||||
aggs: searchAggsStartMock(),
|
||||
setInterceptor: jest.fn(),
|
||||
search: jest.fn(),
|
||||
cancel: jest.fn(),
|
||||
getPendingCount$: jest.fn(),
|
||||
runBeyondTimeout: jest.fn(),
|
||||
__LEGACY: {
|
||||
AggConfig: jest.fn() as any,
|
||||
AggType: jest.fn(),
|
||||
|
|
|
@ -18,27 +18,38 @@
|
|||
*/
|
||||
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { CoreStart } from '../../../../core/public';
|
||||
import { coreMock } from '../../../../core/public/mocks';
|
||||
import { IKibanaSearchRequest } from '../../common/search';
|
||||
import { RequestTimeoutError } from './request_timeout_error';
|
||||
import { SearchInterceptor } from './search_interceptor';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
const flushPromises = () => new Promise(resolve => setImmediate(resolve));
|
||||
const mockSearch = jest.fn();
|
||||
let searchInterceptor: SearchInterceptor;
|
||||
let mockCoreStart: MockedKeys<CoreStart>;
|
||||
|
||||
describe('SearchInterceptor', () => {
|
||||
beforeEach(() => {
|
||||
mockCoreStart = coreMock.createStart();
|
||||
mockSearch.mockClear();
|
||||
searchInterceptor = new SearchInterceptor(1000);
|
||||
searchInterceptor = new SearchInterceptor(
|
||||
mockCoreStart.notifications.toasts,
|
||||
mockCoreStart.application,
|
||||
1000
|
||||
);
|
||||
});
|
||||
|
||||
describe('search', () => {
|
||||
test('should invoke `search` with the request', () => {
|
||||
mockSearch.mockReturnValue(new Observable());
|
||||
const mockResponse = new Subject();
|
||||
mockSearch.mockReturnValue(mockResponse.asObservable());
|
||||
const mockRequest: IKibanaSearchRequest = {};
|
||||
searchInterceptor.search(mockSearch, mockRequest);
|
||||
const response = searchInterceptor.search(mockSearch, mockRequest);
|
||||
mockResponse.complete();
|
||||
|
||||
response.subscribe();
|
||||
expect(mockSearch.mock.calls[0][0]).toBe(mockRequest);
|
||||
});
|
||||
|
||||
|
@ -92,44 +103,6 @@ describe('SearchInterceptor', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('cancelPending', () => {
|
||||
test('should abort all pending requests', async () => {
|
||||
mockSearch.mockReturnValue(new Observable());
|
||||
|
||||
searchInterceptor.search(mockSearch, {});
|
||||
searchInterceptor.search(mockSearch, {});
|
||||
searchInterceptor.cancelPending();
|
||||
|
||||
await flushPromises();
|
||||
|
||||
const areAllRequestsAborted = mockSearch.mock.calls.every(([, { signal }]) => signal.aborted);
|
||||
expect(areAllRequestsAborted).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('runBeyondTimeout', () => {
|
||||
test('should prevent the request from timing out', () => {
|
||||
const mockResponse = new Subject();
|
||||
mockSearch.mockReturnValue(mockResponse.asObservable());
|
||||
const response = searchInterceptor.search(mockSearch, {});
|
||||
|
||||
setTimeout(searchInterceptor.runBeyondTimeout, 500);
|
||||
setTimeout(() => mockResponse.next('hi'), 250);
|
||||
setTimeout(() => mockResponse.complete(), 2000);
|
||||
|
||||
const next = jest.fn();
|
||||
const complete = jest.fn();
|
||||
const error = jest.fn();
|
||||
response.subscribe({ next, error, complete });
|
||||
|
||||
jest.advanceTimersByTime(2000);
|
||||
|
||||
expect(next).toHaveBeenCalledWith('hi');
|
||||
expect(error).not.toHaveBeenCalled();
|
||||
expect(complete).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPendingCount$', () => {
|
||||
test('should observe the number of pending requests', () => {
|
||||
let i = 0;
|
||||
|
|
|
@ -17,51 +17,59 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { BehaviorSubject, fromEvent, throwError } from 'rxjs';
|
||||
import { mergeMap, takeUntil, finalize } from 'rxjs/operators';
|
||||
import { BehaviorSubject, throwError, timer, Subscription, defer, fromEvent } from 'rxjs';
|
||||
import { takeUntil, finalize, filter, mergeMapTo } from 'rxjs/operators';
|
||||
import { ApplicationStart, Toast, ToastsStart } from 'kibana/public';
|
||||
import { getCombinedSignal } from '../../common/utils';
|
||||
import { IKibanaSearchRequest } from '../../common/search';
|
||||
import { ISearchGeneric, ISearchOptions } from './i_search';
|
||||
import { RequestTimeoutError } from './request_timeout_error';
|
||||
import { getLongQueryNotification } from './long_query_notification';
|
||||
|
||||
export class SearchInterceptor {
|
||||
/**
|
||||
* `abortController` used to signal all searches to abort.
|
||||
*/
|
||||
private abortController = new AbortController();
|
||||
protected abortController = new AbortController();
|
||||
|
||||
/**
|
||||
* The number of pending search requests.
|
||||
*/
|
||||
private pendingCount = 0;
|
||||
|
||||
/**
|
||||
* Observable that emits when the number of pending requests changes.
|
||||
*/
|
||||
private pendingCount$ = new BehaviorSubject(0);
|
||||
private pendingCount$ = new BehaviorSubject(this.pendingCount);
|
||||
|
||||
/**
|
||||
* The IDs from `setTimeout` when scheduling the automatic timeout for each request.
|
||||
* The subscriptions from scheduling the automatic timeout for each request.
|
||||
*/
|
||||
private timeoutIds: Set<number> = new Set();
|
||||
protected timeoutSubscriptions: Set<Subscription> = new Set();
|
||||
|
||||
/**
|
||||
* The current long-running toast (if there is one).
|
||||
*/
|
||||
protected longRunningToast?: Toast;
|
||||
|
||||
/**
|
||||
* This class should be instantiated with a `requestTimeout` corresponding with how many ms after
|
||||
* requests are initiated that they should automatically cancel.
|
||||
* @param toasts The `core.notifications.toasts` service
|
||||
* @param application The `core.application` service
|
||||
* @param requestTimeout Usually config value `elasticsearch.requestTimeout`
|
||||
*/
|
||||
constructor(private readonly requestTimeout?: number) {}
|
||||
|
||||
/**
|
||||
* Abort our `AbortController`, which in turn aborts any intercepted searches.
|
||||
*/
|
||||
public cancelPending = () => {
|
||||
this.abortController.abort();
|
||||
this.abortController = new AbortController();
|
||||
};
|
||||
|
||||
/**
|
||||
* Un-schedule timing out all of the searches intercepted.
|
||||
*/
|
||||
public runBeyondTimeout = () => {
|
||||
this.timeoutIds.forEach(clearTimeout);
|
||||
this.timeoutIds.clear();
|
||||
};
|
||||
constructor(
|
||||
protected readonly toasts: ToastsStart,
|
||||
protected readonly application: ApplicationStart,
|
||||
protected readonly requestTimeout?: number
|
||||
) {
|
||||
// When search requests go out, a notification is scheduled allowing users to continue the
|
||||
// request past the timeout. When all search requests complete, we remove the notification.
|
||||
this.getPendingCount$()
|
||||
.pipe(filter(count => count === 0))
|
||||
.subscribe(this.hideToast);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an `Observable` over the current number of pending searches. This could mean that one
|
||||
|
@ -81,41 +89,66 @@ export class SearchInterceptor {
|
|||
request: IKibanaSearchRequest,
|
||||
options?: ISearchOptions
|
||||
) => {
|
||||
// Schedule this request to automatically timeout after some interval
|
||||
const timeoutController = new AbortController();
|
||||
const { signal: timeoutSignal } = timeoutController;
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
timeoutController.abort();
|
||||
}, this.requestTimeout);
|
||||
this.addTimeoutId(timeoutId);
|
||||
// Defer the following logic until `subscribe` is actually called
|
||||
return defer(() => {
|
||||
this.pendingCount$.next(++this.pendingCount);
|
||||
|
||||
// Get a combined `AbortSignal` that will be aborted whenever the first of the following occurs:
|
||||
// 1. The user manually aborts (via `cancelPending`)
|
||||
// 2. The request times out
|
||||
// 3. The passed-in signal aborts (e.g. when re-fetching, or whenever the app determines)
|
||||
const signals = [this.abortController.signal, timeoutSignal, options?.signal].filter(
|
||||
Boolean
|
||||
) as AbortSignal[];
|
||||
const combinedSignal = getCombinedSignal(signals);
|
||||
// Schedule this request to automatically timeout after some interval
|
||||
const timeoutController = new AbortController();
|
||||
const { signal: timeoutSignal } = timeoutController;
|
||||
const timeout$ = timer(this.requestTimeout);
|
||||
const subscription = timeout$.subscribe(() => timeoutController.abort());
|
||||
this.timeoutSubscriptions.add(subscription);
|
||||
|
||||
// If the request timed out, throw a `RequestTimeoutError`
|
||||
const timeoutError$ = fromEvent(timeoutSignal, 'abort').pipe(
|
||||
mergeMap(() => throwError(new RequestTimeoutError()))
|
||||
);
|
||||
// If the request timed out, throw a `RequestTimeoutError`
|
||||
const timeoutError$ = fromEvent(timeoutSignal, 'abort').pipe(
|
||||
mergeMapTo(throwError(new RequestTimeoutError()))
|
||||
);
|
||||
|
||||
return search(request as any, { ...options, signal: combinedSignal }).pipe(
|
||||
takeUntil(timeoutError$),
|
||||
finalize(() => this.removeTimeoutId(timeoutId))
|
||||
// Schedule the notification to allow users to cancel or wait beyond the timeout
|
||||
const notificationSubscription = timer(10000).subscribe(this.showToast);
|
||||
|
||||
// Get a combined `AbortSignal` that will be aborted whenever the first of the following occurs:
|
||||
// 1. The user manually aborts (via `cancelPending`)
|
||||
// 2. The request times out
|
||||
// 3. The passed-in signal aborts (e.g. when re-fetching, or whenever the app determines)
|
||||
const signals = [
|
||||
this.abortController.signal,
|
||||
timeoutSignal,
|
||||
...(options?.signal ? [options.signal] : []),
|
||||
];
|
||||
const combinedSignal = getCombinedSignal(signals);
|
||||
|
||||
return search(request as any, { ...options, signal: combinedSignal }).pipe(
|
||||
takeUntil(timeoutError$),
|
||||
finalize(() => {
|
||||
this.pendingCount$.next(--this.pendingCount);
|
||||
this.timeoutSubscriptions.delete(subscription);
|
||||
notificationSubscription.unsubscribe();
|
||||
})
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
protected showToast = () => {
|
||||
if (this.longRunningToast) return;
|
||||
this.longRunningToast = this.toasts.addInfo(
|
||||
{
|
||||
title: 'Your query is taking awhile',
|
||||
text: getLongQueryNotification({
|
||||
application: this.application,
|
||||
}),
|
||||
},
|
||||
{
|
||||
toastLifeTimeMs: Infinity,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
private addTimeoutId(id: number) {
|
||||
this.timeoutIds.add(id);
|
||||
this.pendingCount$.next(this.timeoutIds.size);
|
||||
}
|
||||
|
||||
private removeTimeoutId(id: number) {
|
||||
this.timeoutIds.delete(id);
|
||||
this.pendingCount$.next(this.timeoutIds.size);
|
||||
}
|
||||
protected hideToast = () => {
|
||||
if (this.longRunningToast) {
|
||||
this.toasts.remove(this.longRunningToast);
|
||||
delete this.longRunningToast;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
|
|||
|
||||
private esClient?: LegacyApiCaller;
|
||||
private readonly aggTypesRegistry = new AggTypesRegistry();
|
||||
private searchInterceptor!: SearchInterceptor;
|
||||
|
||||
private registerSearchStrategyProvider = <T extends TStrategyTypes>(
|
||||
name: T,
|
||||
|
@ -98,7 +99,9 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
|
|||
* TODO: Make this modular so that apps can opt in/out of search collection, or even provide
|
||||
* their own search collector instances
|
||||
*/
|
||||
const searchInterceptor = new SearchInterceptor(
|
||||
this.searchInterceptor = new SearchInterceptor(
|
||||
core.notifications.toasts,
|
||||
core.application,
|
||||
core.injectedMetadata.getInjectedVar('esRequestTimeout') as number
|
||||
);
|
||||
|
||||
|
@ -114,16 +117,17 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
|
|||
},
|
||||
types: aggTypesStart,
|
||||
},
|
||||
cancel: () => searchInterceptor.cancelPending(),
|
||||
getPendingCount$: () => searchInterceptor.getPendingCount$(),
|
||||
runBeyondTimeout: () => searchInterceptor.runBeyondTimeout(),
|
||||
search: (request, options, strategyName) => {
|
||||
const strategyProvider = this.getSearchStrategy(strategyName || DEFAULT_SEARCH_STRATEGY);
|
||||
const { search } = strategyProvider({
|
||||
core,
|
||||
getSearchStrategy: this.getSearchStrategy,
|
||||
});
|
||||
return searchInterceptor.search(search as any, request, options);
|
||||
return this.searchInterceptor.search(search as any, request, options);
|
||||
},
|
||||
setInterceptor: (searchInterceptor: SearchInterceptor) => {
|
||||
// TODO: should an intercepror have a destroy method?
|
||||
this.searchInterceptor = searchInterceptor;
|
||||
},
|
||||
__LEGACY: {
|
||||
esClient: this.esClient!,
|
||||
|
|
|
@ -17,12 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
import { CoreStart } from 'kibana/public';
|
||||
import { SearchAggsSetup, SearchAggsStart, SearchAggsStartLegacy } from './aggs';
|
||||
import { ISearch, ISearchGeneric } from './i_search';
|
||||
import { TStrategyTypes } from './strategy_types';
|
||||
import { LegacyApiCaller } from './es_client';
|
||||
import { SearchInterceptor } from './search_interceptor';
|
||||
|
||||
export interface ISearchContext {
|
||||
core: CoreStart;
|
||||
|
@ -87,9 +87,7 @@ export interface ISearchSetup {
|
|||
|
||||
export interface ISearchStart {
|
||||
aggs: SearchAggsStart;
|
||||
cancel: () => void;
|
||||
getPendingCount$: () => Observable<number>;
|
||||
runBeyondTimeout: () => void;
|
||||
setInterceptor: (searchInterceptor: SearchInterceptor) => void;
|
||||
search: ISearchGeneric;
|
||||
__LEGACY: ISearchStartLegacy & SearchAggsStartLegacy;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
asyncSearchStrategyProvider,
|
||||
enhancedEsSearchStrategyProvider,
|
||||
} from './search';
|
||||
import { EnhancedSearchInterceptor } from './search/search_interceptor';
|
||||
|
||||
export interface DataEnhancedSetupDependencies {
|
||||
data: DataPublicPluginSetup;
|
||||
|
@ -45,5 +46,11 @@ export class DataEnhancedPlugin implements Plugin {
|
|||
|
||||
public start(core: CoreStart, plugins: DataEnhancedStartDependencies) {
|
||||
setAutocompleteService(plugins.data.autocomplete);
|
||||
const enhancedSearchInterceptor = new EnhancedSearchInterceptor(
|
||||
core.notifications.toasts,
|
||||
core.application,
|
||||
core.injectedMetadata.getInjectedVar('esRequestTimeout') as number
|
||||
);
|
||||
plugins.data.search.setInterceptor(enhancedSearchInterceptor);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
import { toMountPoint } from '../../../../../src/plugins/kibana_react/public';
|
||||
|
||||
interface Props {
|
||||
cancel: () => void;
|
||||
runBeyondTimeout: () => void;
|
||||
}
|
||||
|
||||
export function getLongQueryNotification(props: Props) {
|
||||
return toMountPoint(
|
||||
<LongQueryNotification cancel={props.cancel} runBeyondTimeout={props.runBeyondTimeout} />
|
||||
);
|
||||
}
|
||||
|
||||
export function LongQueryNotification(props: Props) {
|
||||
return (
|
||||
<div>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexGroup justifyContent="flexEnd" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty size="s" onClick={props.cancel}>
|
||||
<FormattedMessage
|
||||
id="xpack.data.query.queryBar.cancelLongQuery"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton size="s" fill onClick={props.runBeyondTimeout}>
|
||||
<FormattedMessage
|
||||
id="xpack.data.query.queryBar.runBeyond"
|
||||
defaultMessage="Run beyond timeout"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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 { Observable, Subject } from 'rxjs';
|
||||
import { coreMock } from '../../../../../src/core/public/mocks';
|
||||
import { EnhancedSearchInterceptor } from './search_interceptor';
|
||||
import { CoreStart } from 'kibana/public';
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
const flushPromises = () => new Promise(resolve => setImmediate(resolve));
|
||||
const mockSearch = jest.fn();
|
||||
let searchInterceptor: EnhancedSearchInterceptor;
|
||||
let mockCoreStart: MockedKeys<CoreStart>;
|
||||
|
||||
describe('EnhancedSearchInterceptor', () => {
|
||||
beforeEach(() => {
|
||||
mockCoreStart = coreMock.createStart();
|
||||
mockSearch.mockClear();
|
||||
searchInterceptor = new EnhancedSearchInterceptor(
|
||||
mockCoreStart.notifications.toasts,
|
||||
mockCoreStart.application,
|
||||
1000
|
||||
);
|
||||
});
|
||||
|
||||
describe('cancelPending', () => {
|
||||
test('should abort all pending requests', async () => {
|
||||
mockSearch.mockReturnValue(new Observable());
|
||||
|
||||
searchInterceptor.search(mockSearch, {});
|
||||
searchInterceptor.search(mockSearch, {});
|
||||
searchInterceptor.cancelPending();
|
||||
|
||||
await flushPromises();
|
||||
|
||||
const areAllRequestsAborted = mockSearch.mock.calls.every(([, { signal }]) => signal.aborted);
|
||||
expect(areAllRequestsAborted).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('runBeyondTimeout', () => {
|
||||
test('should prevent the request from timing out', () => {
|
||||
const mockResponse = new Subject();
|
||||
mockSearch.mockReturnValue(mockResponse.asObservable());
|
||||
const response = searchInterceptor.search(mockSearch, {});
|
||||
|
||||
setTimeout(searchInterceptor.runBeyondTimeout, 500);
|
||||
setTimeout(() => mockResponse.next('hi'), 250);
|
||||
setTimeout(() => mockResponse.complete(), 2000);
|
||||
|
||||
const next = jest.fn();
|
||||
const complete = jest.fn();
|
||||
const error = jest.fn();
|
||||
response.subscribe({ next, error, complete });
|
||||
|
||||
jest.advanceTimersByTime(2000);
|
||||
|
||||
expect(next).toHaveBeenCalledWith('hi');
|
||||
expect(error).not.toHaveBeenCalled();
|
||||
expect(complete).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 { ApplicationStart, ToastsStart } from 'kibana/public';
|
||||
import { getLongQueryNotification } from './long_query_notification';
|
||||
import { SearchInterceptor } from '../../../../../src/plugins/data/public';
|
||||
|
||||
export class EnhancedSearchInterceptor extends SearchInterceptor {
|
||||
/**
|
||||
* This class should be instantiated with a `requestTimeout` corresponding with how many ms after
|
||||
* requests are initiated that they should automatically cancel.
|
||||
* @param toasts The `core.notifications.toasts` service
|
||||
* @param application The `core.application` service
|
||||
* @param requestTimeout Usually config value `elasticsearch.requestTimeout`
|
||||
*/
|
||||
constructor(toasts: ToastsStart, application: ApplicationStart, requestTimeout?: number) {
|
||||
super(toasts, application, requestTimeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort our `AbortController`, which in turn aborts any intercepted searches.
|
||||
*/
|
||||
public cancelPending = () => {
|
||||
this.hideToast();
|
||||
this.abortController.abort();
|
||||
this.abortController = new AbortController();
|
||||
};
|
||||
|
||||
/**
|
||||
* Un-schedule timing out all of the searches intercepted.
|
||||
*/
|
||||
public runBeyondTimeout = () => {
|
||||
this.hideToast();
|
||||
this.timeoutSubscriptions.forEach(subscription => subscription.unsubscribe());
|
||||
this.timeoutSubscriptions.clear();
|
||||
};
|
||||
|
||||
protected showToast = () => {
|
||||
if (this.longRunningToast) return;
|
||||
this.longRunningToast = this.toasts.addInfo(
|
||||
{
|
||||
title: 'Your query is taking awhile',
|
||||
text: getLongQueryNotification({
|
||||
cancel: this.cancelPending,
|
||||
runBeyondTimeout: this.runBeyondTimeout,
|
||||
}),
|
||||
},
|
||||
{
|
||||
toastLifeTimeMs: Infinity,
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue