Skip to content

Commit e6d6751

Browse files
authored
[data.search.SearchSource] Unify FetchHandler types & add onResponse/callMsearch. (#77430)
1 parent 98113ee commit e6d6751

16 files changed

+143
-96
lines changed

docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.serialize.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ Using `createSearchSource`<!-- -->, the instance can be re-created.
1515
```typescript
1616
serialize(): {
1717
searchSourceJSON: string;
18-
references: import("../../../../../core/public").SavedObjectReference[];
18+
references: import("../../../../../core/types").SavedObjectReference[];
1919
};
2020
```
2121
<b>Returns:</b>
2222

2323
`{
2424
searchSourceJSON: string;
25-
references: import("../../../../../core/public").SavedObjectReference[];
25+
references: import("../../../../../core/types").SavedObjectReference[];
2626
}`
2727

src/plugins/data/public/public.api.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import { ExpressionAstFunction } from 'src/plugins/expressions/common';
2828
import { ExpressionsSetup } from 'src/plugins/expressions/public';
2929
import { History } from 'history';
3030
import { Href } from 'history';
31-
import { HttpStart } from 'src/core/public';
3231
import { IconType } from '@elastic/eui';
3332
import { InjectedIntl } from '@kbn/i18n/react';
3433
import { ISearchOptions as ISearchOptions_2 } from 'src/plugins/data/public';
@@ -2018,7 +2017,7 @@ export class SearchSource {
20182017
onRequestStart(handler: (searchSource: SearchSource, options?: ISearchOptions) => Promise<unknown>): void;
20192018
serialize(): {
20202019
searchSourceJSON: string;
2021-
references: import("../../../../../core/public").SavedObjectReference[];
2020+
references: import("../../../../../core/types").SavedObjectReference[];
20222021
};
20232022
setField<K extends keyof SearchSourceFields>(field: K, value: SearchSourceFields[K]): this;
20242023
setFields(newFields: SearchSourceFields): this;

src/plugins/data/public/search/fetch/types.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
* under the License.
1818
*/
1919

20-
import { HttpStart } from 'src/core/public';
21-
import { BehaviorSubject } from 'rxjs';
20+
import { SearchResponse } from 'elasticsearch';
2221
import { GetConfigFn } from '../../../common';
22+
import { LegacyFetchHandlers } from '../legacy/types';
2323

2424
/**
2525
* @internal
@@ -31,9 +31,17 @@ import { GetConfigFn } from '../../../common';
3131
export type SearchRequest = Record<string, any>;
3232

3333
export interface FetchHandlers {
34-
config: { get: GetConfigFn };
35-
http: HttpStart;
36-
loadingCount$: BehaviorSubject<number>;
34+
getConfig: GetConfigFn;
35+
/**
36+
* Callback which can be used to hook into responses, modify them, or perform
37+
* side effects like displaying UI errors on the client.
38+
*/
39+
onResponse: (request: SearchRequest, response: SearchResponse<any>) => SearchResponse<any>;
40+
/**
41+
* These handlers are only used by the legacy defaultSearchStrategy and can be removed
42+
* once that strategy has been deprecated.
43+
*/
44+
legacy: LegacyFetchHandlers;
3745
}
3846

3947
export interface SearchError {

src/plugins/data/public/search/legacy/call_client.test.ts

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,13 @@
1717
* under the License.
1818
*/
1919

20-
import { coreMock } from '../../../../../core/public/mocks';
2120
import { callClient } from './call_client';
2221
import { SearchStrategySearchParams } from './types';
2322
import { defaultSearchStrategy } from './default_search_strategy';
2423
import { FetchHandlers } from '../fetch';
25-
import { handleResponse } from '../fetch/handle_response';
2624
import { BehaviorSubject } from 'rxjs';
2725

2826
const mockAbortFn = jest.fn();
29-
jest.mock('../fetch/handle_response', () => ({
30-
handleResponse: jest.fn((request, response) => response),
31-
}));
3227

3328
jest.mock('./default_search_strategy', () => {
3429
return {
@@ -50,32 +45,36 @@ jest.mock('./default_search_strategy', () => {
5045
});
5146

5247
describe('callClient', () => {
48+
const handleResponse = jest.fn().mockImplementation((req, res) => res);
49+
const handlers = {
50+
getConfig: jest.fn(),
51+
onResponse: handleResponse,
52+
legacy: {
53+
callMsearch: jest.fn(),
54+
loadingCount$: new BehaviorSubject(0),
55+
},
56+
} as FetchHandlers;
57+
5358
beforeEach(() => {
54-
(handleResponse as jest.Mock).mockClear();
59+
handleResponse.mockClear();
5560
});
5661

5762
test('Passes the additional arguments it is given to the search strategy', () => {
5863
const searchRequests = [{ _searchStrategyId: 0 }];
59-
const args = {
60-
http: coreMock.createStart().http,
61-
legacySearchService: {},
62-
config: { get: jest.fn() },
63-
loadingCount$: new BehaviorSubject(0),
64-
} as FetchHandlers;
6564

66-
callClient(searchRequests, [], args);
65+
callClient(searchRequests, [], handlers);
6766

6867
expect(defaultSearchStrategy.search).toBeCalled();
6968
expect((defaultSearchStrategy.search as any).mock.calls[0][0]).toEqual({
7069
searchRequests,
71-
...args,
70+
...handlers,
7271
});
7372
});
7473

7574
test('Returns the responses in the original order', async () => {
7675
const searchRequests = [{ _searchStrategyId: 1 }, { _searchStrategyId: 0 }];
7776

78-
const responses = await Promise.all(callClient(searchRequests, [], {} as FetchHandlers));
77+
const responses = await Promise.all(callClient(searchRequests, [], handlers));
7978

8079
expect(responses[0]).toEqual({ id: searchRequests[0]._searchStrategyId });
8180
expect(responses[1]).toEqual({ id: searchRequests[1]._searchStrategyId });
@@ -84,7 +83,7 @@ describe('callClient', () => {
8483
test('Calls handleResponse with each request and response', async () => {
8584
const searchRequests = [{ _searchStrategyId: 0 }, { _searchStrategyId: 1 }];
8685

87-
const responses = callClient(searchRequests, [], {} as FetchHandlers);
86+
const responses = callClient(searchRequests, [], handlers);
8887
await Promise.all(responses);
8988

9089
expect(handleResponse).toBeCalledTimes(2);
@@ -105,7 +104,7 @@ describe('callClient', () => {
105104
},
106105
];
107106

108-
callClient(searchRequests, requestOptions, {} as FetchHandlers);
107+
callClient(searchRequests, requestOptions, handlers);
109108
abortController.abort();
110109

111110
expect(mockAbortFn).toBeCalled();

src/plugins/data/public/search/legacy/call_client.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
import { SearchResponse } from 'elasticsearch';
2121
import { ISearchOptions } from 'src/plugins/data/common';
22-
import { FetchHandlers, handleResponse } from '../fetch';
22+
import { FetchHandlers } from '../fetch';
2323
import { defaultSearchStrategy } from './default_search_strategy';
2424
import { SearchRequest } from '../index';
2525

@@ -42,7 +42,7 @@ export function callClient(
4242
});
4343

4444
searchRequests.forEach((request, i) => {
45-
const response = searching.then((results) => handleResponse(request, results[i]));
45+
const response = searching.then((results) => fetchHandlers.onResponse(request, results[i]));
4646
const { abortSignal = null } = requestOptionsMap.get(request) || {};
4747
if (abortSignal) abortSignal.addEventListener('abort', abort);
4848
requestResponseMap.set(request, response);
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import { HttpStart } from 'src/core/public';
21+
import { LegacyFetchHandlers } from './types';
22+
23+
/**
24+
* Wrapper for calling the internal msearch endpoint from the client.
25+
* This is needed to abstract away differences in the http service
26+
* between client & server.
27+
*
28+
* @internal
29+
*/
30+
export function getCallMsearch({ http }: { http: HttpStart }): LegacyFetchHandlers['callMsearch'] {
31+
return async ({ body, signal }) => {
32+
return http.post('/internal/_msearch', {
33+
body: JSON.stringify(body),
34+
signal,
35+
});
36+
};
37+
}

src/plugins/data/public/search/legacy/default_search_strategy.test.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@
1919

2020
import { HttpStart } from 'src/core/public';
2121
import { coreMock } from '../../../../../core/public/mocks';
22+
import { getCallMsearch } from './call_msearch';
2223
import { defaultSearchStrategy } from './default_search_strategy';
23-
import { SearchStrategySearchParams } from './types';
24+
import { LegacyFetchHandlers, SearchStrategySearchParams } from './types';
2425
import { BehaviorSubject } from 'rxjs';
2526

2627
const { search } = defaultSearchStrategy;
@@ -44,11 +45,12 @@ describe('defaultSearchStrategy', function () {
4445
index: { title: 'foo' },
4546
},
4647
],
47-
http,
48-
config: {
49-
get: jest.fn(),
50-
},
51-
loadingCount$: new BehaviorSubject(0) as any,
48+
getConfig: jest.fn(),
49+
onResponse: (req, res) => res,
50+
legacy: {
51+
callMsearch: getCallMsearch({ http }),
52+
loadingCount$: new BehaviorSubject(0) as any,
53+
} as jest.Mocked<LegacyFetchHandlers>,
5254
};
5355
});
5456

src/plugins/data/public/search/legacy/default_search_strategy.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@ export const defaultSearchStrategy: SearchStrategyProvider = {
2929
},
3030
};
3131

32-
function msearch({ searchRequests, config, http, loadingCount$ }: SearchStrategySearchParams) {
32+
function msearch({ searchRequests, getConfig, legacy }: SearchStrategySearchParams) {
33+
const { callMsearch, loadingCount$ } = legacy;
34+
3335
const requests = searchRequests.map(({ index, body }) => {
3436
return {
3537
header: {
3638
index: index.title || index,
37-
preference: getPreference(config.get),
39+
preference: getPreference(getConfig),
3840
},
3941
body,
4042
};
@@ -55,12 +57,11 @@ function msearch({ searchRequests, config, http, loadingCount$ }: SearchStrategy
5557
}
5658
};
5759

58-
const searching = http
59-
.post('/internal/_msearch', {
60-
body: JSON.stringify({ searches: requests }),
61-
signal: abortController.signal,
62-
})
63-
.then(({ body }) => body?.responses)
60+
const searching = callMsearch({
61+
body: { searches: requests },
62+
signal: abortController.signal,
63+
})
64+
.then((res: any) => res?.body?.responses)
6465
.finally(() => cleanup());
6566

6667
return {

src/plugins/data/public/search/legacy/fetch_soon.test.ts

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -67,25 +67,21 @@ describe('fetchSoon', () => {
6767
});
6868

6969
test('should execute asap if config is set to not batch searches', () => {
70-
const config = {
71-
get: getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: false }),
72-
};
70+
const getConfig = getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: false });
7371
const request = {};
7472
const options = {};
7573

76-
fetchSoon(request, options, { config } as FetchHandlers);
74+
fetchSoon(request, options, { getConfig } as FetchHandlers);
7775

7876
expect(callClient).toBeCalled();
7977
});
8078

8179
test('should delay by 50ms if config is set to batch searches', () => {
82-
const config = {
83-
get: getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true }),
84-
};
80+
const getConfig = getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true });
8581
const request = {};
8682
const options = {};
8783

88-
fetchSoon(request, options, { config } as FetchHandlers);
84+
fetchSoon(request, options, { getConfig } as FetchHandlers);
8985

9086
expect(callClient).not.toBeCalled();
9187
jest.advanceTimersByTime(0);
@@ -95,14 +91,12 @@ describe('fetchSoon', () => {
9591
});
9692

9793
test('should send a batch of requests to callClient', () => {
98-
const config = {
99-
get: getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true }),
100-
};
94+
const getConfig = getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true });
10195
const requests = [{ foo: 1 }, { foo: 2 }];
10296
const options = [{ bar: 1 }, { bar: 2 }];
10397

10498
requests.forEach((request, i) => {
105-
fetchSoon(request, options[i] as ISearchOptions, { config } as FetchHandlers);
99+
fetchSoon(request, options[i] as ISearchOptions, { getConfig } as FetchHandlers);
106100
});
107101

108102
jest.advanceTimersByTime(50);
@@ -112,13 +106,11 @@ describe('fetchSoon', () => {
112106
});
113107

114108
test('should return the response to the corresponding call for multiple batched requests', async () => {
115-
const config = {
116-
get: getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true }),
117-
};
109+
const getConfig = getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true });
118110
const requests = [{ _mockResponseId: 'foo' }, { _mockResponseId: 'bar' }];
119111

120112
const promises = requests.map((request) => {
121-
return fetchSoon(request, {}, { config } as FetchHandlers);
113+
return fetchSoon(request, {}, { getConfig } as FetchHandlers);
122114
});
123115
jest.advanceTimersByTime(50);
124116
const results = await Promise.all(promises);
@@ -127,18 +119,16 @@ describe('fetchSoon', () => {
127119
});
128120

129121
test('should wait for the previous batch to start before starting a new batch', () => {
130-
const config = {
131-
get: getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true }),
132-
};
122+
const getConfig = getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true });
133123
const firstBatch = [{ foo: 1 }, { foo: 2 }];
134124
const secondBatch = [{ bar: 1 }, { bar: 2 }];
135125

136126
firstBatch.forEach((request) => {
137-
fetchSoon(request, {}, { config } as FetchHandlers);
127+
fetchSoon(request, {}, { getConfig } as FetchHandlers);
138128
});
139129
jest.advanceTimersByTime(50);
140130
secondBatch.forEach((request) => {
141-
fetchSoon(request, {}, { config } as FetchHandlers);
131+
fetchSoon(request, {}, { getConfig } as FetchHandlers);
142132
});
143133

144134
expect(callClient).toBeCalledTimes(1);

src/plugins/data/public/search/legacy/fetch_soon.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export async function fetchSoon(
3232
options: ISearchOptions,
3333
fetchHandlers: FetchHandlers
3434
) {
35-
const msToDelay = fetchHandlers.config.get(UI_SETTINGS.COURIER_BATCH_SEARCHES) ? 50 : 0;
35+
const msToDelay = fetchHandlers.getConfig(UI_SETTINGS.COURIER_BATCH_SEARCHES) ? 50 : 0;
3636
return delayedFetch(request, options, fetchHandlers, msToDelay);
3737
}
3838

0 commit comments

Comments
 (0)