Data plugin: Suggested enhance pattern (#74505)
* improve test stability * Enhance pattern * fix tests * fix test * Rename enhance to __enhance * Deleted unnecessary attribute * ISearchInterceptor interface * docs * Clean up internal docs * jest Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
b249af128e
commit
290f9bfde2
|
@ -1,13 +0,0 @@
|
|||
<!-- 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;
|
||||
```
|
|
@ -2,12 +2,16 @@
|
|||
|
||||
[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
|
||||
## SearchInterceptor.getPendingCount$() method
|
||||
|
||||
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$: () => Observable<number>;
|
||||
getPendingCount$(): Observable<number>;
|
||||
```
|
||||
<b>Returns:</b>
|
||||
|
||||
`Observable<number>`
|
||||
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
<!-- 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;
|
||||
```
|
|
@ -1,13 +0,0 @@
|
|||
<!-- 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;
|
||||
```
|
|
@ -20,22 +20,15 @@ export declare class SearchInterceptor
|
|||
|
||||
| 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. |
|
||||
| [deps](./kibana-plugin-plugins-data-public.searchinterceptor.deps.md) | | <code>SearchInterceptorDeps</code> | |
|
||||
| [getPendingCount$](./kibana-plugin-plugins-data-public.searchinterceptor.getpendingcount_.md) | | <code>() => 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). |
|
||||
| [pendingCount](./kibana-plugin-plugins-data-public.searchinterceptor.pendingcount.md) | | <code>number</code> | The number of pending search requests. |
|
||||
| [pendingCount$](./kibana-plugin-plugins-data-public.searchinterceptor.pendingcount_.md) | | <code>BehaviorSubject<number></code> | Observable that emits when the number of pending requests changes. |
|
||||
| [requestTimeout](./kibana-plugin-plugins-data-public.searchinterceptor.requesttimeout.md) | | <code>number | undefined</code> | |
|
||||
| [showToast](./kibana-plugin-plugins-data-public.searchinterceptor.showtoast.md) | | <code>() => void</code> | |
|
||||
| [timeoutSubscriptions](./kibana-plugin-plugins-data-public.searchinterceptor.timeoutsubscriptions.md) | | <code>Subscription</code> | The subscriptions from scheduling the automatic timeout for each request. |
|
||||
|
||||
## Methods
|
||||
|
||||
| Method | Modifiers | Description |
|
||||
| --- | --- | --- |
|
||||
| [getPendingCount$()](./kibana-plugin-plugins-data-public.searchinterceptor.getpendingcount_.md) | | 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. |
|
||||
| [runSearch(request, signal, strategy)](./kibana-plugin-plugins-data-public.searchinterceptor.runsearch.md) | | |
|
||||
| [search(request, options)](./kibana-plugin-plugins-data-public.searchinterceptor.search.md) | | 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. |
|
||||
| [search(request, options)](./kibana-plugin-plugins-data-public.searchinterceptor.search.md) | | 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 <code>pendingCount$</code> when the request is started/finalized. |
|
||||
| [setupTimers(options)](./kibana-plugin-plugins-data-public.searchinterceptor.setuptimers.md) | | |
|
||||
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
<!-- 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) > [pendingCount](./kibana-plugin-plugins-data-public.searchinterceptor.pendingcount.md)
|
||||
|
||||
## SearchInterceptor.pendingCount property
|
||||
|
||||
The number of pending search requests.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
protected pendingCount: number;
|
||||
```
|
|
@ -1,13 +0,0 @@
|
|||
<!-- 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) > [pendingCount$](./kibana-plugin-plugins-data-public.searchinterceptor.pendingcount_.md)
|
||||
|
||||
## SearchInterceptor.pendingCount$ property
|
||||
|
||||
Observable that emits when the number of pending requests changes.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
protected pendingCount$: BehaviorSubject<number>;
|
||||
```
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
## SearchInterceptor.search() method
|
||||
|
||||
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.
|
||||
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 `pendingCount$` when the request is started/finalized.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
<!-- 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;
|
||||
```
|
|
@ -1,13 +0,0 @@
|
|||
<!-- 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: Subscription;
|
||||
```
|
|
@ -7,5 +7,5 @@
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
http: CoreStart['http'];
|
||||
http: CoreSetup['http'];
|
||||
```
|
||||
|
|
|
@ -14,9 +14,9 @@ export interface SearchInterceptorDeps
|
|||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [application](./kibana-plugin-plugins-data-public.searchinterceptordeps.application.md) | <code>ApplicationStart</code> | |
|
||||
| [http](./kibana-plugin-plugins-data-public.searchinterceptordeps.http.md) | <code>CoreStart['http']</code> | |
|
||||
| [toasts](./kibana-plugin-plugins-data-public.searchinterceptordeps.toasts.md) | <code>ToastsStart</code> | |
|
||||
| [uiSettings](./kibana-plugin-plugins-data-public.searchinterceptordeps.uisettings.md) | <code>CoreStart['uiSettings']</code> | |
|
||||
| [http](./kibana-plugin-plugins-data-public.searchinterceptordeps.http.md) | <code>CoreSetup['http']</code> | |
|
||||
| [startServices](./kibana-plugin-plugins-data-public.searchinterceptordeps.startservices.md) | <code>Promise<[CoreStart, any, unknown]></code> | |
|
||||
| [toasts](./kibana-plugin-plugins-data-public.searchinterceptordeps.toasts.md) | <code>ToastsSetup</code> | |
|
||||
| [uiSettings](./kibana-plugin-plugins-data-public.searchinterceptordeps.uisettings.md) | <code>CoreSetup['uiSettings']</code> | |
|
||||
| [usageCollector](./kibana-plugin-plugins-data-public.searchinterceptordeps.usagecollector.md) | <code>SearchUsageCollector</code> | |
|
||||
|
||||
|
|
|
@ -1,11 +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) > [SearchInterceptorDeps](./kibana-plugin-plugins-data-public.searchinterceptordeps.md) > [application](./kibana-plugin-plugins-data-public.searchinterceptordeps.application.md)
|
||||
[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchInterceptorDeps](./kibana-plugin-plugins-data-public.searchinterceptordeps.md) > [startServices](./kibana-plugin-plugins-data-public.searchinterceptordeps.startservices.md)
|
||||
|
||||
## SearchInterceptorDeps.application property
|
||||
## SearchInterceptorDeps.startServices property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
application: ApplicationStart;
|
||||
startServices: Promise<[CoreStart, any, unknown]>;
|
||||
```
|
|
@ -7,5 +7,5 @@
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
toasts: ToastsStart;
|
||||
toasts: ToastsSetup;
|
||||
```
|
||||
|
|
|
@ -7,5 +7,5 @@
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
uiSettings: CoreStart['uiSettings'];
|
||||
uiSettings: CoreSetup['uiSettings'];
|
||||
```
|
||||
|
|
|
@ -44,6 +44,7 @@ const createSetupContract = (): Setup => {
|
|||
search: searchServiceMock.createSetupContract(),
|
||||
fieldFormats: fieldFormatsServiceMock.createSetupContract(),
|
||||
query: querySetupMock,
|
||||
__enhance: jest.fn(),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ import {
|
|||
DataSetupDependencies,
|
||||
DataStartDependencies,
|
||||
InternalStartServices,
|
||||
DataPublicPluginEnhancements,
|
||||
} from './types';
|
||||
import { AutocompleteService } from './autocomplete';
|
||||
import { SearchService } from './search/search_service';
|
||||
|
@ -156,16 +157,21 @@ export class DataPublicPlugin
|
|||
}))
|
||||
);
|
||||
|
||||
const searchService = this.searchService.setup(core, {
|
||||
expressions,
|
||||
usageCollection,
|
||||
getInternalStartServices,
|
||||
packageInfo: this.packageInfo,
|
||||
});
|
||||
|
||||
return {
|
||||
autocomplete: this.autocomplete.setup(core),
|
||||
search: this.searchService.setup(core, {
|
||||
expressions,
|
||||
usageCollection,
|
||||
getInternalStartServices,
|
||||
packageInfo: this.packageInfo,
|
||||
}),
|
||||
search: searchService,
|
||||
fieldFormats: this.fieldFormatsService.setup(core),
|
||||
query: queryService,
|
||||
__enhance: (enhancements: DataPublicPluginEnhancements) => {
|
||||
searchService.__enhance(enhancements.search);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -8,12 +8,12 @@ import { $Values } from '@kbn/utility-types';
|
|||
import _ from 'lodash';
|
||||
import { Action } from 'history';
|
||||
import { ApiResponse } from '@elastic/elasticsearch/lib/Transport';
|
||||
import { ApplicationStart } from 'kibana/public';
|
||||
import { Assign } from '@kbn/utility-types';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import Boom from 'boom';
|
||||
import { Component } from 'react';
|
||||
import { CoreSetup } from 'src/core/public';
|
||||
import { CoreSetup as CoreSetup_2 } from 'kibana/public';
|
||||
import { CoreStart } from 'kibana/public';
|
||||
import { CoreStart as CoreStart_2 } from 'src/core/public';
|
||||
import { Ensure } from '@kbn/utility-types';
|
||||
|
@ -65,7 +65,7 @@ import { SerializedFieldFormat as SerializedFieldFormat_2 } from 'src/plugins/ex
|
|||
import { Subscription } from 'rxjs';
|
||||
import { Toast } from 'kibana/public';
|
||||
import { ToastInputFields } from 'src/core/public/notifications';
|
||||
import { ToastsStart } from 'kibana/public';
|
||||
import { ToastsSetup } from 'kibana/public';
|
||||
import { TransportRequestOptions } from '@elastic/elasticsearch/lib/Transport';
|
||||
import { TransportRequestParams } from '@elastic/elasticsearch/lib/Transport';
|
||||
import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport';
|
||||
|
@ -222,6 +222,10 @@ export type CustomFilter = Filter & {
|
|||
//
|
||||
// @public (undocumented)
|
||||
export interface DataPublicPluginSetup {
|
||||
// Warning: (ae-forgotten-export) The symbol "DataPublicPluginEnhancements" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @internal (undocumented)
|
||||
__enhance: (enhancements: DataPublicPluginEnhancements) => void;
|
||||
// Warning: (ae-forgotten-export) The symbol "AutocompleteSetup" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// (undocumented)
|
||||
|
@ -1714,15 +1718,19 @@ export interface SearchError {
|
|||
// @public (undocumented)
|
||||
export class SearchInterceptor {
|
||||
constructor(deps: SearchInterceptorDeps, requestTimeout?: number | undefined);
|
||||
// @internal
|
||||
protected abortController: AbortController;
|
||||
// @internal (undocumented)
|
||||
protected application: CoreStart['application'];
|
||||
// (undocumented)
|
||||
protected readonly deps: SearchInterceptorDeps;
|
||||
getPendingCount$: () => Observable<number>;
|
||||
// (undocumented)
|
||||
getPendingCount$(): Observable<number>;
|
||||
// @internal (undocumented)
|
||||
protected hideToast: () => void;
|
||||
// @internal
|
||||
protected longRunningToast?: Toast;
|
||||
// @internal
|
||||
protected pendingCount$: BehaviorSubject<number>;
|
||||
protected pendingCount: number;
|
||||
// (undocumented)
|
||||
protected readonly requestTimeout?: number | undefined;
|
||||
// (undocumented)
|
||||
|
@ -1733,8 +1741,9 @@ export class SearchInterceptor {
|
|||
combinedSignal: AbortSignal;
|
||||
cleanup: () => void;
|
||||
};
|
||||
// (undocumented)
|
||||
// @internal (undocumented)
|
||||
protected showToast: () => void;
|
||||
// @internal
|
||||
protected timeoutSubscriptions: Subscription;
|
||||
}
|
||||
|
||||
|
@ -1743,13 +1752,13 @@ export class SearchInterceptor {
|
|||
// @public (undocumented)
|
||||
export interface SearchInterceptorDeps {
|
||||
// (undocumented)
|
||||
application: ApplicationStart;
|
||||
http: CoreSetup_2['http'];
|
||||
// (undocumented)
|
||||
http: CoreStart['http'];
|
||||
startServices: Promise<[CoreStart, any, unknown]>;
|
||||
// (undocumented)
|
||||
toasts: ToastsStart;
|
||||
toasts: ToastsSetup;
|
||||
// (undocumented)
|
||||
uiSettings: CoreStart['uiSettings'];
|
||||
uiSettings: CoreSetup_2['uiSettings'];
|
||||
// Warning: (ae-forgotten-export) The symbol "SearchUsageCollector" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// (undocumented)
|
||||
|
@ -1980,9 +1989,9 @@ export const UI_SETTINGS: {
|
|||
// src/plugins/data/public/index.ts:393:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/index.ts:396: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/types.ts:54:5 - (ae-forgotten-export) The symbol "createFiltersFromValueClickAction" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/types.ts:55:5 - (ae-forgotten-export) The symbol "createFiltersFromRangeSelectAction" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/types.ts:63:5 - (ae-forgotten-export) The symbol "IndexPatternSelectProps" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/types.ts:62:5 - (ae-forgotten-export) The symbol "createFiltersFromValueClickAction" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/types.ts:63:5 - (ae-forgotten-export) The symbol "createFiltersFromRangeSelectAction" needs to be exported by the entry point index.d.ts
|
||||
// src/plugins/data/public/types.ts:71:5 - (ae-forgotten-export) The symbol "IndexPatternSelectProps" needs to be exported by the entry point index.d.ts
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
|
|
|
@ -21,7 +21,14 @@ export * from './aggs';
|
|||
export * from './expressions';
|
||||
export * from './tabify';
|
||||
|
||||
export { ISearch, ISearchOptions, ISearchGeneric, ISearchSetup, ISearchStart } from './types';
|
||||
export {
|
||||
ISearch,
|
||||
ISearchOptions,
|
||||
ISearchGeneric,
|
||||
ISearchSetup,
|
||||
ISearchStart,
|
||||
SearchEnhancements,
|
||||
} from './types';
|
||||
|
||||
export { IEsSearchResponse, IEsSearchRequest, ES_SEARCH_STRATEGY } from '../../common/search';
|
||||
|
||||
|
|
|
@ -26,13 +26,13 @@ export * from './search_source/mocks';
|
|||
function createSetupContract(): jest.Mocked<ISearchSetup> {
|
||||
return {
|
||||
aggs: searchAggsSetupMock(),
|
||||
__enhance: jest.fn(),
|
||||
};
|
||||
}
|
||||
|
||||
function createStartContract(): jest.Mocked<ISearchStart> {
|
||||
return {
|
||||
aggs: searchAggsStartMock(),
|
||||
setInterceptor: jest.fn(),
|
||||
search: jest.fn(),
|
||||
searchSource: searchSourceMock,
|
||||
__LEGACY: {
|
||||
|
|
|
@ -17,27 +17,27 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { CoreStart } from '../../../../core/public';
|
||||
import { CoreSetup } from '../../../../core/public';
|
||||
import { coreMock } from '../../../../core/public/mocks';
|
||||
import { IEsSearchRequest } from '../../common/search';
|
||||
import { SearchInterceptor } from './search_interceptor';
|
||||
import { AbortError } from '../../common';
|
||||
|
||||
let searchInterceptor: SearchInterceptor;
|
||||
let mockCoreStart: MockedKeys<CoreStart>;
|
||||
let mockCoreSetup: MockedKeys<CoreSetup>;
|
||||
|
||||
const flushPromises = () => new Promise((resolve) => setImmediate(resolve));
|
||||
jest.useFakeTimers();
|
||||
|
||||
describe('SearchInterceptor', () => {
|
||||
beforeEach(() => {
|
||||
mockCoreStart = coreMock.createStart();
|
||||
mockCoreSetup = coreMock.createSetup();
|
||||
searchInterceptor = new SearchInterceptor(
|
||||
{
|
||||
toasts: mockCoreStart.notifications.toasts,
|
||||
application: mockCoreStart.application,
|
||||
uiSettings: mockCoreStart.uiSettings,
|
||||
http: mockCoreStart.http,
|
||||
toasts: mockCoreSetup.notifications.toasts,
|
||||
startServices: mockCoreSetup.getStartServices(),
|
||||
uiSettings: mockCoreSetup.uiSettings,
|
||||
http: mockCoreSetup.http,
|
||||
},
|
||||
1000
|
||||
);
|
||||
|
@ -46,7 +46,7 @@ describe('SearchInterceptor', () => {
|
|||
describe('search', () => {
|
||||
test('Observable should resolve if fetch is successful', async () => {
|
||||
const mockResponse: any = { result: 200 };
|
||||
mockCoreStart.http.fetch.mockResolvedValueOnce(mockResponse);
|
||||
mockCoreSetup.http.fetch.mockResolvedValueOnce(mockResponse);
|
||||
const mockRequest: IEsSearchRequest = {
|
||||
params: {},
|
||||
};
|
||||
|
@ -58,7 +58,7 @@ describe('SearchInterceptor', () => {
|
|||
|
||||
test('Observable should fail if fetch has an error', async () => {
|
||||
const mockResponse: any = { result: 500 };
|
||||
mockCoreStart.http.fetch.mockRejectedValueOnce(mockResponse);
|
||||
mockCoreSetup.http.fetch.mockRejectedValueOnce(mockResponse);
|
||||
const mockRequest: IEsSearchRequest = {
|
||||
params: {},
|
||||
};
|
||||
|
@ -72,7 +72,7 @@ describe('SearchInterceptor', () => {
|
|||
});
|
||||
|
||||
test('Observable should fail if fetch times out (test merged signal)', async () => {
|
||||
mockCoreStart.http.fetch.mockImplementationOnce((options: any) => {
|
||||
mockCoreSetup.http.fetch.mockImplementationOnce((options: any) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
options.signal.addEventListener('abort', () => {
|
||||
reject(new AbortError());
|
||||
|
@ -100,7 +100,7 @@ describe('SearchInterceptor', () => {
|
|||
|
||||
test('Observable should fail if user aborts (test merged signal)', async () => {
|
||||
const abortController = new AbortController();
|
||||
mockCoreStart.http.fetch.mockImplementationOnce((options: any) => {
|
||||
mockCoreSetup.http.fetch.mockImplementationOnce((options: any) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
options.signal.addEventListener('abort', () => {
|
||||
reject(new AbortError());
|
||||
|
@ -136,7 +136,7 @@ describe('SearchInterceptor', () => {
|
|||
|
||||
const error = (e: any) => {
|
||||
expect(e).toBeInstanceOf(AbortError);
|
||||
expect(mockCoreStart.http.fetch).not.toBeCalled();
|
||||
expect(mockCoreSetup.http.fetch).not.toBeCalled();
|
||||
done();
|
||||
};
|
||||
response.subscribe({ error });
|
||||
|
@ -150,7 +150,7 @@ describe('SearchInterceptor', () => {
|
|||
pendingCount$.subscribe(pendingNext);
|
||||
|
||||
const mockResponse: any = { result: 200 };
|
||||
mockCoreStart.http.fetch.mockResolvedValue(mockResponse);
|
||||
mockCoreSetup.http.fetch.mockResolvedValue(mockResponse);
|
||||
const mockRequest: IEsSearchRequest = {
|
||||
params: {},
|
||||
};
|
||||
|
@ -169,7 +169,7 @@ describe('SearchInterceptor', () => {
|
|||
pendingCount$.subscribe(pendingNext);
|
||||
|
||||
const mockResponse: any = { result: 500 };
|
||||
mockCoreStart.http.fetch.mockRejectedValue(mockResponse);
|
||||
mockCoreSetup.http.fetch.mockRejectedValue(mockResponse);
|
||||
const mockRequest: IEsSearchRequest = {
|
||||
params: {},
|
||||
};
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
import { trimEnd } from 'lodash';
|
||||
import { BehaviorSubject, throwError, timer, Subscription, defer, from, Observable } from 'rxjs';
|
||||
import { finalize, filter } from 'rxjs/operators';
|
||||
import { ApplicationStart, Toast, ToastsStart, CoreStart } from 'kibana/public';
|
||||
import { Toast, CoreStart, ToastsSetup, CoreSetup } from 'kibana/public';
|
||||
import { getCombinedSignal, AbortError } from '../../common/utils';
|
||||
import { IEsSearchRequest, IEsSearchResponse, ES_SEARCH_STRATEGY } from '../../common/search';
|
||||
import { ISearchOptions } from './types';
|
||||
|
@ -30,39 +30,43 @@ import { SearchUsageCollector } from './collectors';
|
|||
const LONG_QUERY_NOTIFICATION_DELAY = 10000;
|
||||
|
||||
export interface SearchInterceptorDeps {
|
||||
toasts: ToastsStart;
|
||||
application: ApplicationStart;
|
||||
http: CoreStart['http'];
|
||||
uiSettings: CoreStart['uiSettings'];
|
||||
toasts: ToastsSetup;
|
||||
http: CoreSetup['http'];
|
||||
uiSettings: CoreSetup['uiSettings'];
|
||||
startServices: Promise<[CoreStart, any, unknown]>;
|
||||
usageCollector?: SearchUsageCollector;
|
||||
}
|
||||
|
||||
export class SearchInterceptor {
|
||||
/**
|
||||
* `abortController` used to signal all searches to abort.
|
||||
* @internal
|
||||
*/
|
||||
protected abortController = new AbortController();
|
||||
|
||||
/**
|
||||
* The number of pending search requests.
|
||||
*/
|
||||
protected pendingCount = 0;
|
||||
|
||||
/**
|
||||
* Observable that emits when the number of pending requests changes.
|
||||
* @internal
|
||||
*/
|
||||
protected pendingCount$ = new BehaviorSubject(this.pendingCount);
|
||||
protected pendingCount$ = new BehaviorSubject(0);
|
||||
|
||||
/**
|
||||
* The subscriptions from scheduling the automatic timeout for each request.
|
||||
* @internal
|
||||
*/
|
||||
protected timeoutSubscriptions: Subscription = new Subscription();
|
||||
|
||||
/**
|
||||
* The current long-running toast (if there is one).
|
||||
* @internal
|
||||
*/
|
||||
protected longRunningToast?: Toast;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected application!: CoreStart['application'];
|
||||
|
||||
/**
|
||||
* This class should be instantiated with a `requestTimeout` corresponding with how many ms after
|
||||
* requests are initiated that they should automatically cancel.
|
||||
|
@ -76,6 +80,10 @@ export class SearchInterceptor {
|
|||
) {
|
||||
this.deps.http.addLoadingCountSource(this.pendingCount$);
|
||||
|
||||
this.deps.startServices.then(([coreStart]) => {
|
||||
this.application = coreStart.application;
|
||||
});
|
||||
|
||||
// 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$()
|
||||
|
@ -87,9 +95,9 @@ export class SearchInterceptor {
|
|||
* 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.
|
||||
*/
|
||||
public getPendingCount$ = () => {
|
||||
public getPendingCount$() {
|
||||
return this.pendingCount$.asObservable();
|
||||
};
|
||||
}
|
||||
|
||||
protected runSearch(
|
||||
request: IEsSearchRequest,
|
||||
|
@ -112,7 +120,7 @@ export class SearchInterceptor {
|
|||
/**
|
||||
* 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.
|
||||
* `AbortSignal` is aborted. Updates `pendingCount$` when the request is started/finalized.
|
||||
*/
|
||||
public search(
|
||||
request: IEsSearchRequest,
|
||||
|
@ -125,11 +133,11 @@ export class SearchInterceptor {
|
|||
}
|
||||
|
||||
const { combinedSignal, cleanup } = this.setupTimers(options);
|
||||
this.pendingCount$.next(++this.pendingCount);
|
||||
this.pendingCount$.next(this.pendingCount$.getValue() + 1);
|
||||
|
||||
return this.runSearch(request, combinedSignal, options?.strategy).pipe(
|
||||
finalize(() => {
|
||||
this.pendingCount$.next(--this.pendingCount);
|
||||
this.pendingCount$.next(this.pendingCount$.getValue() - 1);
|
||||
cleanup();
|
||||
})
|
||||
);
|
||||
|
@ -173,13 +181,16 @@ export class SearchInterceptor {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected showToast = () => {
|
||||
if (this.longRunningToast) return;
|
||||
this.longRunningToast = this.deps.toasts.addInfo(
|
||||
{
|
||||
title: 'Your query is taking a while',
|
||||
text: getLongQueryNotification({
|
||||
application: this.deps.application,
|
||||
application: this.application,
|
||||
}),
|
||||
},
|
||||
{
|
||||
|
@ -188,6 +199,9 @@ export class SearchInterceptor {
|
|||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected hideToast = () => {
|
||||
if (this.longRunningToast) {
|
||||
this.deps.toasts.remove(this.longRunningToast);
|
||||
|
@ -198,3 +212,5 @@ export class SearchInterceptor {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
export type ISearchInterceptor = PublicMethodsOf<SearchInterceptor>;
|
||||
|
|
|
@ -41,6 +41,7 @@ describe('Search service', () => {
|
|||
expressions: expressionsPluginMock.createSetupContract(),
|
||||
} as any);
|
||||
expect(setup).toHaveProperty('aggs');
|
||||
expect(setup).toHaveProperty('__enhance');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -49,7 +50,6 @@ describe('Search service', () => {
|
|||
const start = searchService.start(mockCoreStart, {
|
||||
indexPatterns: {},
|
||||
} as any);
|
||||
expect(start).toHaveProperty('setInterceptor');
|
||||
expect(start).toHaveProperty('search');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
import { Plugin, CoreSetup, CoreStart, PackageInfo } from '../../../../core/public';
|
||||
import { ISearchSetup, ISearchStart } from './types';
|
||||
import { ISearchSetup, ISearchStart, SearchEnhancements } from './types';
|
||||
import { ExpressionsSetup } from '../../../../plugins/expressions/public';
|
||||
|
||||
import { createSearchSource, SearchSource, SearchSourceDependencies } from './search_source';
|
||||
|
@ -28,7 +28,7 @@ import { calculateBounds, TimeRange } from '../../common/query';
|
|||
|
||||
import { IndexPatternsContract } from '../index_patterns/index_patterns';
|
||||
import { GetInternalStartServicesFn } from '../types';
|
||||
import { SearchInterceptor } from './search_interceptor';
|
||||
import { ISearchInterceptor, SearchInterceptor } from './search_interceptor';
|
||||
import {
|
||||
getAggTypes,
|
||||
getAggTypesFunctions,
|
||||
|
@ -54,7 +54,7 @@ interface SearchServiceStartDependencies {
|
|||
export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
|
||||
private esClient?: LegacyApiCaller;
|
||||
private readonly aggTypesRegistry = new AggTypesRegistry();
|
||||
private searchInterceptor!: SearchInterceptor;
|
||||
private searchInterceptor!: ISearchInterceptor;
|
||||
private usageCollector?: SearchUsageCollector;
|
||||
|
||||
/**
|
||||
|
@ -91,15 +91,6 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
|
|||
const aggFunctions = getAggTypesFunctions();
|
||||
aggFunctions.forEach((fn) => expressions.registerFunction(fn));
|
||||
|
||||
return {
|
||||
aggs: {
|
||||
calculateAutoTimeExpression: getCalculateAutoTimeExpression(core.uiSettings),
|
||||
types: aggTypesSetup,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public start(core: CoreStart, dependencies: SearchServiceStartDependencies): ISearchStart {
|
||||
/**
|
||||
* A global object that intercepts all searches and provides convenience methods for cancelling
|
||||
* all pending search requests, as well as getting the number of pending search requests.
|
||||
|
@ -109,14 +100,27 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
|
|||
this.searchInterceptor = new SearchInterceptor(
|
||||
{
|
||||
toasts: core.notifications.toasts,
|
||||
application: core.application,
|
||||
http: core.http,
|
||||
uiSettings: core.uiSettings,
|
||||
startServices: core.getStartServices(),
|
||||
usageCollector: this.usageCollector!,
|
||||
},
|
||||
core.injectedMetadata.getInjectedVar('esRequestTimeout') as number
|
||||
);
|
||||
|
||||
return {
|
||||
usageCollector: this.usageCollector!,
|
||||
__enhance: (enhancements: SearchEnhancements) => {
|
||||
this.searchInterceptor = enhancements.searchInterceptor;
|
||||
},
|
||||
aggs: {
|
||||
calculateAutoTimeExpression: getCalculateAutoTimeExpression(core.uiSettings),
|
||||
types: aggTypesSetup,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public start(core: CoreStart, dependencies: SearchServiceStartDependencies): ISearchStart {
|
||||
const aggTypesStart = this.aggTypesRegistry.start();
|
||||
|
||||
const search: ISearchGeneric = (request, options) => {
|
||||
|
@ -145,17 +149,12 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
|
|||
types: aggTypesStart,
|
||||
},
|
||||
search,
|
||||
usageCollector: this.usageCollector!,
|
||||
searchSource: {
|
||||
create: createSearchSource(dependencies.indexPatterns, searchSourceDependencies),
|
||||
createEmpty: () => {
|
||||
return new SearchSource({}, searchSourceDependencies);
|
||||
},
|
||||
},
|
||||
setInterceptor: (searchInterceptor: SearchInterceptor) => {
|
||||
// TODO: should an intercepror have a destroy method?
|
||||
this.searchInterceptor = searchInterceptor;
|
||||
},
|
||||
__LEGACY: legacySearch,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import { Observable } from 'rxjs';
|
|||
import { PackageInfo } from 'kibana/server';
|
||||
import { SearchAggsSetup, SearchAggsStart } from './aggs';
|
||||
import { LegacyApiCaller } from './legacy/es_client';
|
||||
import { SearchInterceptor } from './search_interceptor';
|
||||
import { ISearchInterceptor } from './search_interceptor';
|
||||
import { ISearchSource, SearchSourceFields } from './search_source';
|
||||
import { SearchUsageCollector } from './collectors';
|
||||
import {
|
||||
|
@ -54,23 +54,33 @@ export interface ISearchStartLegacy {
|
|||
esClient: LegacyApiCaller;
|
||||
}
|
||||
|
||||
export interface SearchEnhancements {
|
||||
searchInterceptor: ISearchInterceptor;
|
||||
}
|
||||
/**
|
||||
* The setup contract exposed by the Search plugin exposes the search strategy extension
|
||||
* point.
|
||||
*/
|
||||
export interface ISearchSetup {
|
||||
aggs: SearchAggsSetup;
|
||||
usageCollector?: SearchUsageCollector;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
__enhance: (enhancements: SearchEnhancements) => void;
|
||||
}
|
||||
|
||||
export interface ISearchStart {
|
||||
aggs: SearchAggsStart;
|
||||
setInterceptor: (searchInterceptor: SearchInterceptor) => void;
|
||||
search: ISearchGeneric;
|
||||
searchSource: {
|
||||
create: (fields?: SearchSourceFields) => Promise<ISearchSource>;
|
||||
createEmpty: () => ISearchSource;
|
||||
};
|
||||
usageCollector?: SearchUsageCollector;
|
||||
/**
|
||||
* @deprecated
|
||||
* @internal
|
||||
*/
|
||||
__LEGACY: ISearchStartLegacy;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,13 +25,17 @@ import { UiActionsSetup, UiActionsStart } from 'src/plugins/ui_actions/public';
|
|||
import { AutocompleteSetup, AutocompleteStart } from './autocomplete';
|
||||
import { FieldFormatsSetup, FieldFormatsStart } from './field_formats';
|
||||
import { createFiltersFromRangeSelectAction, createFiltersFromValueClickAction } from './actions';
|
||||
import { ISearchSetup, ISearchStart } from './search';
|
||||
import { ISearchSetup, ISearchStart, SearchEnhancements } from './search';
|
||||
import { QuerySetup, QueryStart } from './query';
|
||||
import { IndexPatternSelectProps } from './ui/index_pattern_select';
|
||||
import { IndexPatternsContract } from './index_patterns';
|
||||
import { StatefulSearchBarProps } from './ui/search_bar/create_search_bar';
|
||||
import { UsageCollectionSetup } from '../../usage_collection/public';
|
||||
|
||||
export interface DataPublicPluginEnhancements {
|
||||
search: SearchEnhancements;
|
||||
}
|
||||
|
||||
export interface DataSetupDependencies {
|
||||
expressions: ExpressionsSetup;
|
||||
uiActions: UiActionsSetup;
|
||||
|
@ -47,6 +51,10 @@ export interface DataPublicPluginSetup {
|
|||
search: ISearchSetup;
|
||||
fieldFormats: FieldFormatsSetup;
|
||||
query: QuerySetup;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
__enhance: (enhancements: DataPublicPluginEnhancements) => void;
|
||||
}
|
||||
|
||||
export interface DataPublicPluginStart {
|
||||
|
|
|
@ -31,20 +31,26 @@ export class DataEnhancedPlugin
|
|||
KUERY_LANGUAGE_NAME,
|
||||
setupKqlQuerySuggestionProvider(core)
|
||||
);
|
||||
|
||||
const enhancedSearchInterceptor = new EnhancedSearchInterceptor(
|
||||
{
|
||||
toasts: core.notifications.toasts,
|
||||
http: core.http,
|
||||
uiSettings: core.uiSettings,
|
||||
startServices: core.getStartServices(),
|
||||
usageCollector: data.search.usageCollector,
|
||||
},
|
||||
core.injectedMetadata.getInjectedVar('esRequestTimeout') as number
|
||||
);
|
||||
|
||||
data.__enhance({
|
||||
search: {
|
||||
searchInterceptor: enhancedSearchInterceptor,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public start(core: CoreStart, plugins: DataEnhancedStartDependencies) {
|
||||
setAutocompleteService(plugins.data.autocomplete);
|
||||
const enhancedSearchInterceptor = new EnhancedSearchInterceptor(
|
||||
{
|
||||
toasts: core.notifications.toasts,
|
||||
application: core.application,
|
||||
http: core.http,
|
||||
uiSettings: core.uiSettings,
|
||||
usageCollector: plugins.data.search.usageCollector,
|
||||
},
|
||||
core.injectedMetadata.getInjectedVar('esRequestTimeout') as number
|
||||
);
|
||||
plugins.data.search.setInterceptor(enhancedSearchInterceptor);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { coreMock } from '../../../../../src/core/public/mocks';
|
||||
import { EnhancedSearchInterceptor } from './search_interceptor';
|
||||
import { CoreStart } from 'kibana/public';
|
||||
import { CoreSetup, CoreStart } from 'kibana/public';
|
||||
import { AbortError } from '../../../../../src/plugins/data/common';
|
||||
|
||||
const timeTravel = (msToRun = 0) => {
|
||||
|
@ -19,13 +19,14 @@ const error = jest.fn();
|
|||
const complete = jest.fn();
|
||||
|
||||
let searchInterceptor: EnhancedSearchInterceptor;
|
||||
let mockCoreSetup: MockedKeys<CoreSetup>;
|
||||
let mockCoreStart: MockedKeys<CoreStart>;
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
function mockFetchImplementation(responses: any[]) {
|
||||
let i = 0;
|
||||
mockCoreStart.http.fetch.mockImplementation(() => {
|
||||
mockCoreSetup.http.fetch.mockImplementation(() => {
|
||||
const { time = 0, value = {}, isError = false } = responses[i++];
|
||||
return new Promise((resolve, reject) =>
|
||||
setTimeout(() => {
|
||||
|
@ -39,6 +40,7 @@ describe('EnhancedSearchInterceptor', () => {
|
|||
let mockUsageCollector: any;
|
||||
|
||||
beforeEach(() => {
|
||||
mockCoreSetup = coreMock.createSetup();
|
||||
mockCoreStart = coreMock.createStart();
|
||||
|
||||
next.mockClear();
|
||||
|
@ -54,12 +56,20 @@ describe('EnhancedSearchInterceptor', () => {
|
|||
trackLongQueryRunBeyondTimeout: jest.fn(),
|
||||
};
|
||||
|
||||
const mockPromise = new Promise((resolve) => {
|
||||
resolve([
|
||||
{
|
||||
application: mockCoreStart.application,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
searchInterceptor = new EnhancedSearchInterceptor(
|
||||
{
|
||||
toasts: mockCoreStart.notifications.toasts,
|
||||
application: mockCoreStart.application,
|
||||
http: mockCoreStart.http,
|
||||
uiSettings: mockCoreStart.uiSettings,
|
||||
toasts: mockCoreSetup.notifications.toasts,
|
||||
startServices: mockPromise as any,
|
||||
http: mockCoreSetup.http,
|
||||
uiSettings: mockCoreSetup.uiSettings,
|
||||
usageCollector: mockUsageCollector,
|
||||
},
|
||||
1000
|
||||
|
@ -229,8 +239,8 @@ describe('EnhancedSearchInterceptor', () => {
|
|||
expect(error).toHaveBeenCalled();
|
||||
expect(error.mock.calls[0][0]).toBeInstanceOf(AbortError);
|
||||
|
||||
expect(mockCoreStart.http.fetch).toHaveBeenCalledTimes(2);
|
||||
expect(mockCoreStart.http.delete).toHaveBeenCalled();
|
||||
expect(mockCoreSetup.http.fetch).toHaveBeenCalledTimes(2);
|
||||
expect(mockCoreSetup.http.delete).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should not DELETE a running async search on async timeout prior to first response', async () => {
|
||||
|
@ -253,8 +263,8 @@ describe('EnhancedSearchInterceptor', () => {
|
|||
|
||||
expect(error).toHaveBeenCalled();
|
||||
expect(error.mock.calls[0][0]).toBeInstanceOf(AbortError);
|
||||
expect(mockCoreStart.http.fetch).toHaveBeenCalled();
|
||||
expect(mockCoreStart.http.delete).not.toHaveBeenCalled();
|
||||
expect(mockCoreSetup.http.fetch).toHaveBeenCalled();
|
||||
expect(mockCoreSetup.http.delete).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should DELETE a running async search on async timeout after first response', async () => {
|
||||
|
@ -285,16 +295,16 @@ describe('EnhancedSearchInterceptor', () => {
|
|||
|
||||
expect(next).toHaveBeenCalled();
|
||||
expect(error).not.toHaveBeenCalled();
|
||||
expect(mockCoreStart.http.fetch).toHaveBeenCalled();
|
||||
expect(mockCoreStart.http.delete).not.toHaveBeenCalled();
|
||||
expect(mockCoreSetup.http.fetch).toHaveBeenCalled();
|
||||
expect(mockCoreSetup.http.delete).not.toHaveBeenCalled();
|
||||
|
||||
// Long enough to reach the timeout but not long enough to reach the next response
|
||||
await timeTravel(1000);
|
||||
|
||||
expect(error).toHaveBeenCalled();
|
||||
expect(error.mock.calls[0][0]).toBeInstanceOf(AbortError);
|
||||
expect(mockCoreStart.http.fetch).toHaveBeenCalledTimes(2);
|
||||
expect(mockCoreStart.http.delete).toHaveBeenCalled();
|
||||
expect(mockCoreSetup.http.fetch).toHaveBeenCalledTimes(2);
|
||||
expect(mockCoreSetup.http.delete).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should DELETE a running async search on async timeout on error from fetch', async () => {
|
||||
|
@ -327,16 +337,16 @@ describe('EnhancedSearchInterceptor', () => {
|
|||
|
||||
expect(next).toHaveBeenCalled();
|
||||
expect(error).not.toHaveBeenCalled();
|
||||
expect(mockCoreStart.http.fetch).toHaveBeenCalled();
|
||||
expect(mockCoreStart.http.delete).not.toHaveBeenCalled();
|
||||
expect(mockCoreSetup.http.fetch).toHaveBeenCalled();
|
||||
expect(mockCoreSetup.http.delete).not.toHaveBeenCalled();
|
||||
|
||||
// Long enough to reach the timeout but not long enough to reach the next response
|
||||
await timeTravel(10);
|
||||
|
||||
expect(error).toHaveBeenCalled();
|
||||
expect(error.mock.calls[0][0]).toBe(responses[1].value);
|
||||
expect(mockCoreStart.http.fetch).toHaveBeenCalledTimes(2);
|
||||
expect(mockCoreStart.http.delete).toHaveBeenCalled();
|
||||
expect(mockCoreSetup.http.fetch).toHaveBeenCalledTimes(2);
|
||||
expect(mockCoreSetup.http.delete).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -367,7 +377,7 @@ describe('EnhancedSearchInterceptor', () => {
|
|||
|
||||
await timeTravel();
|
||||
|
||||
const areAllRequestsAborted = mockCoreStart.http.fetch.mock.calls.every(
|
||||
const areAllRequestsAborted = mockCoreSetup.http.fetch.mock.calls.every(
|
||||
([{ signal }]) => signal?.aborted
|
||||
);
|
||||
expect(areAllRequestsAborted).toBe(true);
|
||||
|
|
|
@ -20,8 +20,7 @@ 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 deps `SearchInterceptorDeps`
|
||||
* @param requestTimeout Usually config value `elasticsearch.requestTimeout`
|
||||
*/
|
||||
constructor(deps: SearchInterceptorDeps, requestTimeout?: number) {
|
||||
|
@ -78,7 +77,7 @@ export class EnhancedSearchInterceptor extends SearchInterceptor {
|
|||
const { combinedSignal, cleanup } = this.setupTimers(options);
|
||||
const aborted$ = from(toPromise(combinedSignal));
|
||||
|
||||
this.pendingCount$.next(++this.pendingCount);
|
||||
this.pendingCount$.next(this.pendingCount$.getValue() + 1);
|
||||
|
||||
return this.runSearch(request, combinedSignal, options?.strategy).pipe(
|
||||
expand((response) => {
|
||||
|
@ -113,7 +112,7 @@ export class EnhancedSearchInterceptor extends SearchInterceptor {
|
|||
},
|
||||
}),
|
||||
finalize(() => {
|
||||
this.pendingCount$.next(--this.pendingCount);
|
||||
this.pendingCount$.next(this.pendingCount$.getValue() - 1);
|
||||
cleanup();
|
||||
})
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue