[Search] Remove msearch from searchsource (#96936)
* Move inspector adapter integration into search source * docs and ts * Move other bucket to search source * test ts + delete unused tabilfy function * hierarchical param in aggconfig. ts improvements more inspector tests * fix jest * separate inspect more tests * jest * inspector * Error handling and more tests * put the fun in functional tests * delete client side legacy msearch code * ts * override to sync search in search source * delete more legacy code * ts * delete moarrrr
This commit is contained in:
parent
87fa92bf3a
commit
b2a9dd12a5
|
@ -11,7 +11,6 @@ import { SearchSourceDependencies } from './search_source';
|
|||
import { IIndexPattern } from '../../index_patterns';
|
||||
import { IndexPatternsContract } from '../../index_patterns/index_patterns';
|
||||
import { Filter } from '../../es_query/filters';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
describe('createSearchSource', () => {
|
||||
const indexPatternMock: IIndexPattern = {} as IIndexPattern;
|
||||
|
@ -24,10 +23,6 @@ describe('createSearchSource', () => {
|
|||
getConfig: jest.fn(),
|
||||
search: jest.fn(),
|
||||
onResponse: (req, res) => res,
|
||||
legacy: {
|
||||
callMsearch: jest.fn(),
|
||||
loadingCount$: new BehaviorSubject(0),
|
||||
},
|
||||
};
|
||||
|
||||
indexPatternContractMock = ({
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
*/
|
||||
|
||||
import type { estypes } from '@elastic/elasticsearch';
|
||||
import { LegacyFetchHandlers } from '../legacy/types';
|
||||
import { GetConfigFn } from '../../../types';
|
||||
|
||||
/**
|
||||
|
@ -29,11 +28,6 @@ export interface FetchHandlers {
|
|||
request: SearchRequest,
|
||||
response: estypes.SearchResponse<any>
|
||||
) => estypes.SearchResponse<any>;
|
||||
/**
|
||||
* These handlers are only used by the legacy defaultSearchStrategy and can be removed
|
||||
* once that strategy has been deprecated.
|
||||
*/
|
||||
legacy: LegacyFetchHandlers;
|
||||
}
|
||||
|
||||
export interface SearchError {
|
||||
|
|
|
@ -1,102 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { callClient } from './call_client';
|
||||
import { SearchStrategySearchParams } from './types';
|
||||
import { defaultSearchStrategy } from './default_search_strategy';
|
||||
import { FetchHandlers } from '../fetch';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
const mockAbortFn = jest.fn();
|
||||
|
||||
jest.mock('./default_search_strategy', () => {
|
||||
return {
|
||||
defaultSearchStrategy: {
|
||||
search: jest.fn(({ searchRequests }: SearchStrategySearchParams) => {
|
||||
return {
|
||||
searching: Promise.resolve(
|
||||
searchRequests.map((req) => {
|
||||
return {
|
||||
id: req._searchStrategyId,
|
||||
};
|
||||
})
|
||||
),
|
||||
abort: mockAbortFn,
|
||||
};
|
||||
}),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe('callClient', () => {
|
||||
const handleResponse = jest.fn().mockImplementation((req, res) => res);
|
||||
const handlers = {
|
||||
getConfig: jest.fn(),
|
||||
onResponse: handleResponse,
|
||||
legacy: {
|
||||
callMsearch: jest.fn(),
|
||||
loadingCount$: new BehaviorSubject(0),
|
||||
},
|
||||
} as FetchHandlers;
|
||||
|
||||
beforeEach(() => {
|
||||
handleResponse.mockClear();
|
||||
});
|
||||
|
||||
test('Passes the additional arguments it is given to the search strategy', () => {
|
||||
const searchRequests = [{ _searchStrategyId: 0 }];
|
||||
|
||||
callClient(searchRequests, [], handlers);
|
||||
|
||||
expect(defaultSearchStrategy.search).toBeCalled();
|
||||
expect((defaultSearchStrategy.search as any).mock.calls[0][0]).toEqual({
|
||||
searchRequests,
|
||||
...handlers,
|
||||
});
|
||||
});
|
||||
|
||||
test('Returns the responses in the original order', async () => {
|
||||
const searchRequests = [{ _searchStrategyId: 1 }, { _searchStrategyId: 0 }];
|
||||
|
||||
const responses = await Promise.all(callClient(searchRequests, [], handlers));
|
||||
|
||||
expect(responses[0]).toEqual({ id: searchRequests[0]._searchStrategyId });
|
||||
expect(responses[1]).toEqual({ id: searchRequests[1]._searchStrategyId });
|
||||
});
|
||||
|
||||
test('Calls handleResponse with each request and response', async () => {
|
||||
const searchRequests = [{ _searchStrategyId: 0 }, { _searchStrategyId: 1 }];
|
||||
|
||||
const responses = callClient(searchRequests, [], handlers);
|
||||
await Promise.all(responses);
|
||||
|
||||
expect(handleResponse).toBeCalledTimes(2);
|
||||
expect(handleResponse).toBeCalledWith(searchRequests[0], {
|
||||
id: searchRequests[0]._searchStrategyId,
|
||||
});
|
||||
expect(handleResponse).toBeCalledWith(searchRequests[1], {
|
||||
id: searchRequests[1]._searchStrategyId,
|
||||
});
|
||||
});
|
||||
|
||||
test('If passed an abortSignal, calls abort on the strategy if the signal is aborted', () => {
|
||||
const searchRequests = [{ _searchStrategyId: 0 }, { _searchStrategyId: 1 }];
|
||||
const abortController = new AbortController();
|
||||
const requestOptions = [
|
||||
{
|
||||
abortSignal: abortController.signal,
|
||||
},
|
||||
];
|
||||
|
||||
callClient(searchRequests, requestOptions, handlers);
|
||||
abortController.abort();
|
||||
|
||||
expect(mockAbortFn).toBeCalled();
|
||||
// expect(mockAbortFns[1]).not.toBeCalled();
|
||||
});
|
||||
});
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { estypes } from '@elastic/elasticsearch';
|
||||
import { FetchHandlers, SearchRequest } from '../fetch';
|
||||
import { defaultSearchStrategy } from './default_search_strategy';
|
||||
import { ISearchOptions } from '../../index';
|
||||
|
||||
export function callClient(
|
||||
searchRequests: SearchRequest[],
|
||||
requestsOptions: ISearchOptions[] = [],
|
||||
fetchHandlers: FetchHandlers
|
||||
) {
|
||||
// Correlate the options with the request that they're associated with
|
||||
const requestOptionEntries: Array<
|
||||
[SearchRequest, ISearchOptions]
|
||||
> = searchRequests.map((request, i) => [request, requestsOptions[i]]);
|
||||
const requestOptionsMap = new Map<SearchRequest, ISearchOptions>(requestOptionEntries);
|
||||
const requestResponseMap = new Map<SearchRequest, Promise<estypes.SearchResponse<any>>>();
|
||||
|
||||
const { searching, abort } = defaultSearchStrategy.search({
|
||||
searchRequests,
|
||||
...fetchHandlers,
|
||||
});
|
||||
|
||||
searchRequests.forEach((request, i) => {
|
||||
const response = searching.then((results) => fetchHandlers.onResponse(request, results[i]));
|
||||
const { abortSignal = null } = requestOptionsMap.get(request) || {};
|
||||
if (abortSignal) abortSignal.addEventListener('abort', abort);
|
||||
requestResponseMap.set(request, response);
|
||||
});
|
||||
return searchRequests.map((request) => requestResponseMap.get(request)!);
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { MockedKeys } from '@kbn/utility-types/jest';
|
||||
import { defaultSearchStrategy } from './default_search_strategy';
|
||||
import { LegacyFetchHandlers, SearchStrategySearchParams } from './types';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
const { search } = defaultSearchStrategy;
|
||||
|
||||
describe('defaultSearchStrategy', () => {
|
||||
describe('search', () => {
|
||||
let searchArgs: MockedKeys<SearchStrategySearchParams>;
|
||||
|
||||
beforeEach(() => {
|
||||
searchArgs = {
|
||||
searchRequests: [
|
||||
{
|
||||
index: { title: 'foo' },
|
||||
body: {},
|
||||
},
|
||||
],
|
||||
getConfig: jest.fn(),
|
||||
onResponse: (req, res) => res,
|
||||
legacy: {
|
||||
callMsearch: jest.fn().mockResolvedValue(undefined),
|
||||
loadingCount$: new BehaviorSubject(0) as any,
|
||||
} as jest.Mocked<LegacyFetchHandlers>,
|
||||
};
|
||||
});
|
||||
|
||||
test('calls callMsearch with the correct arguments', async () => {
|
||||
await search({ ...searchArgs });
|
||||
expect(searchArgs.legacy.callMsearch.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"body": Object {
|
||||
"searches": Array [
|
||||
Object {
|
||||
"body": Object {},
|
||||
"header": Object {
|
||||
"index": "foo",
|
||||
"preference": undefined,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
"signal": AbortSignal {},
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { getPreference } from '../fetch';
|
||||
import { SearchStrategyProvider, SearchStrategySearchParams } from './types';
|
||||
|
||||
// @deprecated
|
||||
export const defaultSearchStrategy: SearchStrategyProvider = {
|
||||
id: 'default',
|
||||
|
||||
search: (params) => {
|
||||
return msearch(params);
|
||||
},
|
||||
};
|
||||
|
||||
function msearch({ searchRequests, getConfig, legacy }: SearchStrategySearchParams) {
|
||||
const { callMsearch, loadingCount$ } = legacy;
|
||||
|
||||
const requests = searchRequests.map(({ index, body }) => {
|
||||
return {
|
||||
header: {
|
||||
index: index.title || index,
|
||||
preference: getPreference(getConfig),
|
||||
},
|
||||
body,
|
||||
};
|
||||
});
|
||||
|
||||
const abortController = new AbortController();
|
||||
let resolved = false;
|
||||
|
||||
// Start LoadingIndicator
|
||||
loadingCount$.next(loadingCount$.getValue() + 1);
|
||||
|
||||
const cleanup = () => {
|
||||
if (!resolved) {
|
||||
resolved = true;
|
||||
// Decrement loading counter & cleanup BehaviorSubject
|
||||
loadingCount$.next(loadingCount$.getValue() - 1);
|
||||
loadingCount$.complete();
|
||||
}
|
||||
};
|
||||
|
||||
const searching = callMsearch({
|
||||
body: { searches: requests },
|
||||
signal: abortController.signal,
|
||||
})
|
||||
.then((res: any) => res?.body?.responses)
|
||||
.finally(() => cleanup());
|
||||
|
||||
return {
|
||||
abort: () => {
|
||||
abortController.abort();
|
||||
cleanup();
|
||||
},
|
||||
searching,
|
||||
};
|
||||
}
|
|
@ -1,132 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { SearchResponse } from 'elasticsearch';
|
||||
import { UI_SETTINGS } from '../../../constants';
|
||||
import { GetConfigFn } from '../../../types';
|
||||
import { FetchHandlers, SearchRequest } from '../fetch';
|
||||
import { ISearchOptions } from '../../index';
|
||||
import { callClient } from './call_client';
|
||||
import { fetchSoon } from './fetch_soon';
|
||||
|
||||
function getConfigStub(config: any = {}): GetConfigFn {
|
||||
return (key) => config[key];
|
||||
}
|
||||
|
||||
const mockResponses: Record<string, SearchResponse<any>> = {
|
||||
foo: {
|
||||
took: 1,
|
||||
timed_out: false,
|
||||
} as SearchResponse<any>,
|
||||
bar: {
|
||||
took: 2,
|
||||
timed_out: false,
|
||||
} as SearchResponse<any>,
|
||||
baz: {
|
||||
took: 3,
|
||||
timed_out: false,
|
||||
} as SearchResponse<any>,
|
||||
};
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
jest.mock('./call_client', () => ({
|
||||
callClient: jest.fn((requests: SearchRequest[]) => {
|
||||
// Allow a request object to specify which mockResponse it wants to receive (_mockResponseId)
|
||||
// in addition to how long to simulate waiting before returning a response (_waitMs)
|
||||
const responses = requests.map((request) => {
|
||||
const waitMs = requests.reduce((total, { _waitMs }) => total + _waitMs || 0, 0);
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(mockResponses[request._mockResponseId]);
|
||||
}, waitMs);
|
||||
});
|
||||
});
|
||||
return Promise.resolve(responses);
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('fetchSoon', () => {
|
||||
beforeEach(() => {
|
||||
(callClient as jest.Mock).mockClear();
|
||||
});
|
||||
|
||||
test('should execute asap if config is set to not batch searches', () => {
|
||||
const getConfig = getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: false });
|
||||
const request = {};
|
||||
const options = {};
|
||||
|
||||
fetchSoon(request, options, { getConfig } as FetchHandlers);
|
||||
|
||||
expect(callClient).toBeCalled();
|
||||
});
|
||||
|
||||
test('should delay by 50ms if config is set to batch searches', () => {
|
||||
const getConfig = getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true });
|
||||
const request = {};
|
||||
const options = {};
|
||||
|
||||
fetchSoon(request, options, { getConfig } as FetchHandlers);
|
||||
|
||||
expect(callClient).not.toBeCalled();
|
||||
jest.advanceTimersByTime(0);
|
||||
expect(callClient).not.toBeCalled();
|
||||
jest.advanceTimersByTime(50);
|
||||
expect(callClient).toBeCalled();
|
||||
});
|
||||
|
||||
test('should send a batch of requests to callClient', () => {
|
||||
const getConfig = getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true });
|
||||
const requests = [{ foo: 1 }, { foo: 2 }];
|
||||
const options = [{ bar: 1 }, { bar: 2 }];
|
||||
|
||||
requests.forEach((request, i) => {
|
||||
fetchSoon(request, options[i] as ISearchOptions, { getConfig } as FetchHandlers);
|
||||
});
|
||||
|
||||
jest.advanceTimersByTime(50);
|
||||
expect(callClient).toBeCalledTimes(1);
|
||||
expect((callClient as jest.Mock).mock.calls[0][0]).toEqual(requests);
|
||||
expect((callClient as jest.Mock).mock.calls[0][1]).toEqual(options);
|
||||
});
|
||||
|
||||
test('should return the response to the corresponding call for multiple batched requests', async () => {
|
||||
const getConfig = getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true });
|
||||
const requests = [{ _mockResponseId: 'foo' }, { _mockResponseId: 'bar' }];
|
||||
|
||||
const promises = requests.map((request) => {
|
||||
return fetchSoon(request, {}, { getConfig } as FetchHandlers);
|
||||
});
|
||||
jest.advanceTimersByTime(50);
|
||||
const results = await Promise.all(promises);
|
||||
|
||||
expect(results).toEqual([mockResponses.foo, mockResponses.bar]);
|
||||
});
|
||||
|
||||
test('should wait for the previous batch to start before starting a new batch', () => {
|
||||
const getConfig = getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true });
|
||||
const firstBatch = [{ foo: 1 }, { foo: 2 }];
|
||||
const secondBatch = [{ bar: 1 }, { bar: 2 }];
|
||||
|
||||
firstBatch.forEach((request) => {
|
||||
fetchSoon(request, {}, { getConfig } as FetchHandlers);
|
||||
});
|
||||
jest.advanceTimersByTime(50);
|
||||
secondBatch.forEach((request) => {
|
||||
fetchSoon(request, {}, { getConfig } as FetchHandlers);
|
||||
});
|
||||
|
||||
expect(callClient).toBeCalledTimes(1);
|
||||
expect((callClient as jest.Mock).mock.calls[0][0]).toEqual(firstBatch);
|
||||
|
||||
jest.advanceTimersByTime(50);
|
||||
|
||||
expect(callClient).toBeCalledTimes(2);
|
||||
expect((callClient as jest.Mock).mock.calls[1][0]).toEqual(secondBatch);
|
||||
});
|
||||
});
|
|
@ -1,83 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { estypes } from '@elastic/elasticsearch';
|
||||
import { UI_SETTINGS } from '../../../constants';
|
||||
import { FetchHandlers, SearchRequest } from '../fetch';
|
||||
import { ISearchOptions } from '../../index';
|
||||
import { callClient } from './call_client';
|
||||
|
||||
/**
|
||||
* This function introduces a slight delay in the request process to allow multiple requests to queue
|
||||
* up (e.g. when a dashboard is loading).
|
||||
*/
|
||||
export async function fetchSoon(
|
||||
request: SearchRequest,
|
||||
options: ISearchOptions,
|
||||
fetchHandlers: FetchHandlers
|
||||
) {
|
||||
const msToDelay = fetchHandlers.getConfig(UI_SETTINGS.COURIER_BATCH_SEARCHES) ? 50 : 0;
|
||||
return delayedFetch(request, options, fetchHandlers, msToDelay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delays executing a function for a given amount of time, and returns a promise that resolves
|
||||
* with the result.
|
||||
* @param fn The function to invoke
|
||||
* @param ms The number of milliseconds to wait
|
||||
* @return Promise<any> A promise that resolves with the result of executing the function
|
||||
*/
|
||||
function delay<T>(fn: (...args: any) => T, ms: number): Promise<T> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve(fn()), ms);
|
||||
});
|
||||
}
|
||||
|
||||
// The current batch/queue of requests to fetch
|
||||
let requestsToFetch: SearchRequest[] = [];
|
||||
let requestOptions: ISearchOptions[] = [];
|
||||
|
||||
// The in-progress fetch (if there is one)
|
||||
let fetchInProgress: any = null;
|
||||
|
||||
/**
|
||||
* Delay fetching for a given amount of time, while batching up the requests to be fetched.
|
||||
* Returns a promise that resolves with the response for the given request.
|
||||
* @param request The request to fetch
|
||||
* @param ms The number of milliseconds to wait (and batch requests)
|
||||
* @return Promise<SearchResponse> The response for the given request
|
||||
*/
|
||||
async function delayedFetch(
|
||||
request: SearchRequest,
|
||||
options: ISearchOptions,
|
||||
fetchHandlers: FetchHandlers,
|
||||
ms: number
|
||||
): Promise<estypes.SearchResponse<any>> {
|
||||
if (ms === 0) {
|
||||
return callClient([request], [options], fetchHandlers)[0] as Promise<
|
||||
estypes.SearchResponse<any>
|
||||
>;
|
||||
}
|
||||
|
||||
const i = requestsToFetch.length;
|
||||
requestsToFetch = [...requestsToFetch, request];
|
||||
requestOptions = [...requestOptions, options];
|
||||
|
||||
// Note: the typescript here only worked because `SearchResponse` was `any`
|
||||
// Since this code is legacy, I'm leaving the any here.
|
||||
const responses: any[] = await (fetchInProgress =
|
||||
fetchInProgress ||
|
||||
delay(() => {
|
||||
const response = callClient(requestsToFetch, requestOptions, fetchHandlers);
|
||||
requestsToFetch = [];
|
||||
requestOptions = [];
|
||||
fetchInProgress = null;
|
||||
return response;
|
||||
}, ms));
|
||||
return responses[i];
|
||||
}
|
|
@ -6,5 +6,4 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { fetchSoon } from './fetch_soon';
|
||||
export * from './types';
|
||||
|
|
|
@ -6,9 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import type { estypes, ApiResponse } from '@elastic/elasticsearch';
|
||||
import { FetchHandlers, SearchRequest } from '../fetch';
|
||||
|
||||
interface MsearchHeaders {
|
||||
index: string;
|
||||
|
@ -29,27 +27,3 @@ export interface MsearchRequestBody {
|
|||
export interface MsearchResponse {
|
||||
body: ApiResponse<{ responses: Array<estypes.SearchResponse<any>> }>;
|
||||
}
|
||||
|
||||
// @internal
|
||||
export interface LegacyFetchHandlers {
|
||||
callMsearch: (params: {
|
||||
body: MsearchRequestBody;
|
||||
signal: AbortSignal;
|
||||
}) => Promise<MsearchResponse>;
|
||||
loadingCount$: BehaviorSubject<number>;
|
||||
}
|
||||
|
||||
export interface SearchStrategySearchParams extends FetchHandlers {
|
||||
searchRequests: SearchRequest[];
|
||||
}
|
||||
|
||||
// @deprecated
|
||||
export interface SearchStrategyProvider {
|
||||
id: string;
|
||||
search: (params: SearchStrategySearchParams) => SearchStrategyResponse;
|
||||
}
|
||||
|
||||
export interface SearchStrategyResponse<T = any> {
|
||||
searching: Promise<Array<estypes.SearchResponse<T>>>;
|
||||
abort: () => void;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { BehaviorSubject, of } from 'rxjs';
|
||||
import { of } from 'rxjs';
|
||||
import type { MockedKeys } from '@kbn/utility-types/jest';
|
||||
import { uiSettingsServiceMock } from '../../../../../core/public/mocks';
|
||||
|
||||
|
@ -47,8 +47,4 @@ export const createSearchSourceMock = (fields?: SearchSourceFields) =>
|
|||
getConfig: uiSettingsServiceMock.createStartContract().get,
|
||||
search: jest.fn(),
|
||||
onResponse: jest.fn().mockImplementation((req, res) => res),
|
||||
legacy: {
|
||||
callMsearch: jest.fn(),
|
||||
loadingCount$: new BehaviorSubject(0),
|
||||
},
|
||||
});
|
||||
|
|
|
@ -6,20 +6,15 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { BehaviorSubject, of } from 'rxjs';
|
||||
import { of } from 'rxjs';
|
||||
import { IndexPattern } from '../../index_patterns';
|
||||
import { GetConfigFn } from '../../types';
|
||||
import { fetchSoon } from './legacy';
|
||||
import { SearchSource, SearchSourceDependencies, SortDirection } from './';
|
||||
import { AggConfigs, AggTypesRegistryStart } from '../../';
|
||||
import { AggConfigs, AggTypesRegistryStart, ES_SEARCH_STRATEGY } from '../../';
|
||||
import { mockAggTypesRegistry } from '../aggs/test_helpers';
|
||||
import { RequestResponder } from 'src/plugins/inspector/common';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
|
||||
jest.mock('./legacy', () => ({
|
||||
fetchSoon: jest.fn().mockResolvedValue({}),
|
||||
}));
|
||||
|
||||
const getComputedFields = () => ({
|
||||
storedFields: [],
|
||||
scriptFields: {},
|
||||
|
@ -89,10 +84,6 @@ describe('SearchSource', () => {
|
|||
getConfig: getConfigMock,
|
||||
search: mockSearchMethod,
|
||||
onResponse: (req, res) => res,
|
||||
legacy: {
|
||||
callMsearch: jest.fn(),
|
||||
loadingCount$: new BehaviorSubject(0),
|
||||
},
|
||||
};
|
||||
|
||||
searchSource = new SearchSource({}, searchSourceDependencies);
|
||||
|
@ -869,7 +860,7 @@ describe('SearchSource', () => {
|
|||
});
|
||||
|
||||
describe('fetch$', () => {
|
||||
describe('#legacy fetch()', () => {
|
||||
describe('#legacy COURIER_BATCH_SEARCHES', () => {
|
||||
beforeEach(() => {
|
||||
searchSourceDependencies = {
|
||||
...searchSourceDependencies,
|
||||
|
@ -879,11 +870,22 @@ describe('SearchSource', () => {
|
|||
};
|
||||
});
|
||||
|
||||
test('should call msearch', async () => {
|
||||
test('should override to use sync search if not set', async () => {
|
||||
searchSource = new SearchSource({ index: indexPattern }, searchSourceDependencies);
|
||||
const options = {};
|
||||
await searchSource.fetch$(options).toPromise();
|
||||
expect(fetchSoon).toBeCalledTimes(1);
|
||||
|
||||
const [, callOptions] = mockSearchMethod.mock.calls[0];
|
||||
expect(callOptions.strategy).toBe(ES_SEARCH_STRATEGY);
|
||||
});
|
||||
|
||||
test('should not override strategy if set ', async () => {
|
||||
searchSource = new SearchSource({ index: indexPattern }, searchSourceDependencies);
|
||||
const options = { strategy: 'banana' };
|
||||
await searchSource.fetch$(options).toPromise();
|
||||
|
||||
const [, callOptions] = mockSearchMethod.mock.calls[0];
|
||||
expect(callOptions.strategy).toBe('banana');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ import { estypes } from '@elastic/elasticsearch';
|
|||
import { normalizeSortRequest } from './normalize_sort_request';
|
||||
import { fieldWildcardFilter } from '../../../../kibana_utils/common';
|
||||
import { IIndexPattern, IndexPattern, IndexPatternField } from '../../index_patterns';
|
||||
import { AggConfigs, ISearchGeneric, ISearchOptions } from '../..';
|
||||
import { AggConfigs, ES_SEARCH_STRATEGY, ISearchGeneric, ISearchOptions } from '../..';
|
||||
import type {
|
||||
ISearchSource,
|
||||
SearchFieldValue,
|
||||
|
@ -95,7 +95,6 @@ import {
|
|||
IKibanaSearchResponse,
|
||||
} from '../../../common';
|
||||
import { getHighlightRequest } from '../../../common/field_formats';
|
||||
import { fetchSoon } from './legacy';
|
||||
import { extractReferences } from './extract_references';
|
||||
|
||||
/** @internal */
|
||||
|
@ -274,6 +273,13 @@ export class SearchSource {
|
|||
*/
|
||||
fetch$(options: ISearchOptions = {}) {
|
||||
const { getConfig } = this.dependencies;
|
||||
const syncSearchByDefault = getConfig(UI_SETTINGS.COURIER_BATCH_SEARCHES);
|
||||
|
||||
// Use the sync search strategy if legacy search is enabled.
|
||||
// This still uses bfetch for batching.
|
||||
if (!options?.strategy && syncSearchByDefault) {
|
||||
options.strategy = ES_SEARCH_STRATEGY;
|
||||
}
|
||||
|
||||
const s$ = defer(() => this.requestIsStarting(options)).pipe(
|
||||
switchMap(() => {
|
||||
|
@ -283,9 +289,7 @@ export class SearchSource {
|
|||
options.indexPattern = searchRequest.index;
|
||||
}
|
||||
|
||||
return getConfig(UI_SETTINGS.COURIER_BATCH_SEARCHES)
|
||||
? from(this.legacyFetch(searchRequest, options))
|
||||
: this.fetchSearch$(searchRequest, options);
|
||||
return this.fetchSearch$(searchRequest, options);
|
||||
}),
|
||||
tap((response) => {
|
||||
// TODO: Remove casting when https://github.com/elastic/elasticsearch-js/issues/1287 is resolved
|
||||
|
@ -477,27 +481,6 @@ export class SearchSource {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a search using the search service
|
||||
* @return {Promise<SearchResponse<unknown>>}
|
||||
*/
|
||||
private async legacyFetch(searchRequest: SearchRequest, options: ISearchOptions) {
|
||||
const { getConfig, legacy, onResponse } = this.dependencies;
|
||||
|
||||
return await fetchSoon(
|
||||
searchRequest,
|
||||
{
|
||||
...(this.searchStrategyId && { searchStrategyId: this.searchStrategyId }),
|
||||
...options,
|
||||
},
|
||||
{
|
||||
getConfig,
|
||||
onResponse,
|
||||
legacy,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by requests of this search source when they are started
|
||||
* @param options
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { IndexPatternsContract } from '../../index_patterns/index_patterns';
|
||||
import { SearchSourceService, SearchSourceDependencies } from './';
|
||||
|
||||
|
@ -19,10 +18,6 @@ describe('SearchSource service', () => {
|
|||
getConfig: jest.fn(),
|
||||
search: jest.fn(),
|
||||
onResponse: jest.fn(),
|
||||
legacy: {
|
||||
callMsearch: jest.fn(),
|
||||
loadingCount$: new BehaviorSubject(0),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -7,8 +7,7 @@
|
|||
import { $Values } from '@kbn/utility-types';
|
||||
import { Action } from 'history';
|
||||
import { Adapters as Adapters_2 } from 'src/plugins/inspector/common';
|
||||
import { ApiResponse } from '@elastic/elasticsearch';
|
||||
import { ApiResponse as ApiResponse_2 } from '@elastic/elasticsearch/lib/Transport';
|
||||
import { ApiResponse } from '@elastic/elasticsearch/lib/Transport';
|
||||
import { ApplicationStart } from 'kibana/public';
|
||||
import { Assign } from '@kbn/utility-types';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { HttpStart } from 'src/core/public';
|
||||
import { coreMock } from '../../../../../core/public/mocks';
|
||||
import { getCallMsearch } from './call_msearch';
|
||||
|
||||
describe('callMsearch', () => {
|
||||
const msearchMock = jest.fn().mockResolvedValue({ body: { responses: [] } });
|
||||
let http: jest.Mocked<HttpStart>;
|
||||
|
||||
beforeEach(() => {
|
||||
msearchMock.mockClear();
|
||||
http = coreMock.createStart().http;
|
||||
http.post.mockResolvedValue(msearchMock);
|
||||
});
|
||||
|
||||
test('calls http.post with the correct arguments', async () => {
|
||||
const searches = [{ header: { index: 'foo' }, body: {} }];
|
||||
const callMsearch = getCallMsearch({ http });
|
||||
await callMsearch({
|
||||
body: { searches },
|
||||
signal: new AbortController().signal,
|
||||
});
|
||||
|
||||
expect(http.post.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
"/internal/_msearch",
|
||||
Object {
|
||||
"body": "{\\"searches\\":[{\\"header\\":{\\"index\\":\\"foo\\"},\\"body\\":{}}]}",
|
||||
"signal": AbortSignal {},
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { HttpStart } from 'src/core/public';
|
||||
import { LegacyFetchHandlers } from '../../../common/search/search_source';
|
||||
|
||||
/**
|
||||
* Wrapper for calling the internal msearch endpoint from the client.
|
||||
* This is needed to abstract away differences in the http service
|
||||
* between client & server.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export function getCallMsearch({ http }: { http: HttpStart }): LegacyFetchHandlers['callMsearch'] {
|
||||
return async ({ body, signal }) => {
|
||||
return http.post('/internal/_msearch', {
|
||||
body: JSON.stringify(body),
|
||||
signal,
|
||||
});
|
||||
};
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export * from './call_msearch';
|
|
@ -35,7 +35,6 @@ import {
|
|||
phraseFilterFunction,
|
||||
esRawResponse,
|
||||
} from '../../common/search';
|
||||
import { getCallMsearch } from './legacy';
|
||||
import { AggsService, AggsStartDependencies } from './aggs';
|
||||
import { IndexPatternsContract } from '../index_patterns/index_patterns';
|
||||
import { ISearchInterceptor, SearchInterceptor } from './search_interceptor';
|
||||
|
@ -157,10 +156,10 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
|
|||
}
|
||||
|
||||
public start(
|
||||
{ application, http, notifications, uiSettings }: CoreStart,
|
||||
{ http, uiSettings }: CoreStart,
|
||||
{ fieldFormats, indexPatterns }: SearchServiceStartDependencies
|
||||
): ISearchStart {
|
||||
const search = ((request, options) => {
|
||||
const search = ((request, options = {}) => {
|
||||
return this.searchInterceptor.search(request, options);
|
||||
}) as ISearchGeneric;
|
||||
|
||||
|
@ -171,10 +170,6 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
|
|||
getConfig: uiSettings.get.bind(uiSettings),
|
||||
search,
|
||||
onResponse: handleResponse,
|
||||
legacy: {
|
||||
callMsearch: getCallMsearch({ http }),
|
||||
loadingCount$,
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { BehaviorSubject, from, Observable, throwError } from 'rxjs';
|
||||
import { from, Observable, throwError } from 'rxjs';
|
||||
import { pick } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
|
@ -36,7 +36,7 @@ import { AggsService } from './aggs';
|
|||
|
||||
import { FieldFormatsStart } from '../field_formats';
|
||||
import { IndexPatternsServiceStart } from '../index_patterns';
|
||||
import { getCallMsearch, registerMsearchRoute, registerSearchRoute } from './routes';
|
||||
import { registerMsearchRoute, registerSearchRoute } from './routes';
|
||||
import { ES_SEARCH_STRATEGY, esSearchStrategyProvider } from './strategies/es_search';
|
||||
import { DataPluginStart, DataPluginStartDependencies } from '../plugin';
|
||||
import { UsageCollectionSetup } from '../../../usage_collection/server';
|
||||
|
@ -237,14 +237,6 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
|
|||
getConfig: <T = any>(key: string): T => uiSettingsCache[key],
|
||||
search: this.asScoped(request).search,
|
||||
onResponse: (req, res) => res,
|
||||
legacy: {
|
||||
callMsearch: getCallMsearch({
|
||||
esClient,
|
||||
globalConfig$: this.initializerContext.config.legacy.globalConfig$,
|
||||
uiSettings: uiSettingsClient,
|
||||
}),
|
||||
loadingCount$: new BehaviorSubject(0),
|
||||
},
|
||||
};
|
||||
|
||||
return this.searchSourceService.start(scopedIndexPatterns, searchSourceDependencies);
|
||||
|
|
|
@ -6,9 +6,7 @@
|
|||
|
||||
import { $Values } from '@kbn/utility-types';
|
||||
import { Adapters } from 'src/plugins/inspector/common';
|
||||
import { ApiResponse } from '@elastic/elasticsearch';
|
||||
import { Assign } from '@kbn/utility-types';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { BfetchServerSetup } from 'src/plugins/bfetch/server';
|
||||
import { ConfigDeprecationProvider } from '@kbn/config';
|
||||
import { CoreSetup } from 'src/core/server';
|
||||
|
|
|
@ -276,12 +276,12 @@ export function getUiSettings(): Record<string, UiSettingsParams<unknown>> {
|
|||
},
|
||||
[UI_SETTINGS.COURIER_BATCH_SEARCHES]: {
|
||||
name: i18n.translate('data.advancedSettings.courier.batchSearchesTitle', {
|
||||
defaultMessage: 'Use legacy search',
|
||||
defaultMessage: 'Use sync search',
|
||||
}),
|
||||
value: false,
|
||||
type: 'boolean',
|
||||
description: i18n.translate('data.advancedSettings.courier.batchSearchesText', {
|
||||
defaultMessage: `Kibana uses a new search and batching infrastructure.
|
||||
defaultMessage: `Kibana uses a new asynchronous search and infrastructure.
|
||||
Enable this option if you prefer to fallback to the legacy synchronous behavior`,
|
||||
}),
|
||||
deprecation: {
|
||||
|
|
Loading…
Reference in a new issue