diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index 5dceb2cacac9d..c31031a5c657e 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -71,7 +71,6 @@ export class DataPublicPlugin public setup( core: CoreSetup, { - bfetch, expressions, uiActions, usageCollection, @@ -85,7 +84,6 @@ export class DataPublicPlugin setTheme(core.theme); const searchService = this.searchService.setup(core, { - bfetch, usageCollection, expressions, management, diff --git a/src/plugins/data/public/search/search_interceptor/search_interceptor.test.ts b/src/plugins/data/public/search/search_interceptor/search_interceptor.test.ts index 9352b1cc4a230..4a1f2be310361 100644 --- a/src/plugins/data/public/search/search_interceptor/search_interceptor.test.ts +++ b/src/plugins/data/public/search/search_interceptor/search_interceptor.test.ts @@ -8,15 +8,17 @@ */ import type { MockedKeys } from '@kbn/utility-types-jest'; -import { CoreSetup, CoreStart } from '@kbn/core/public'; +import { CoreSetup, CoreStart, HttpFetchOptions, HttpHandler } from '@kbn/core/public'; import { coreMock } from '@kbn/core/public/mocks'; -import { IEsSearchRequest } from '@kbn/search-types'; +import { + IEsSearchRequest, + IKibanaSearchRequest, + type IKibanaSearchResponse, +} from '@kbn/search-types'; import { SearchInterceptor } from './search_interceptor'; import { AbortError } from '@kbn/kibana-utils-plugin/public'; import { EsError, type IEsError } from '@kbn/search-errors'; import { ISessionService, SearchSessionState } from '..'; -import { bfetchPluginMock } from '@kbn/bfetch-plugin/public/mocks'; -import { BfetchPublicSetup } from '@kbn/bfetch-plugin/public'; import * as searchPhaseException from '../../../common/search/test_data/search_phase_execution_exception.json'; import * as resourceNotFoundException from '../../../common/search/test_data/resource_not_found_exception.json'; @@ -45,8 +47,6 @@ import { SearchSessionIncompleteWarning } from './search_session_incomplete_warn import { getMockSearchConfig } from '../../../config.mock'; let searchInterceptor: SearchInterceptor; -let bfetchSetup: jest.Mocked; -let fetchMock: jest.Mock; const flushPromises = () => new Promise((resolve) => jest.requireActual('timers').setImmediate(resolve)); @@ -63,10 +63,11 @@ const next = jest.fn(); const error = jest.fn(); const complete = jest.fn(); -function mockFetchImplementation(responses: any[]) { +function getHttpMock(responses: any[]) { let i = 0; - fetchMock.mockImplementation((r, abortSignal) => { - if (!r.request.id) i = 0; + return ((path: string, options?: HttpFetchOptions) => { + const request = JSON.parse(options?.body as string) as IKibanaSearchRequest; + if (!request.id) i = 0; const { time = 0, value = {}, isError = false } = responses[i++]; value.meta = { size: 10, @@ -76,14 +77,46 @@ function mockFetchImplementation(responses: any[]) { return (isError ? reject : resolve)(value); }, time); - if (abortSignal) { - if (abortSignal.aborted) reject(new AbortError()); - abortSignal.addEventListener('abort', () => { + if (options?.signal) { + if (options?.signal.aborted) reject(new AbortError()); + options?.signal.addEventListener('abort', () => { reject(new AbortError()); }); } }); - }); + }) as HttpHandler; +} + +function getMockSearchResponse( + { id, isPartial, isRunning, rawResponse }: IKibanaSearchResponse = { + rawResponse: {}, + } +) { + const body = { + ...(id ? { id } : {}), + is_partial: isPartial ?? false, + is_running: isRunning ?? false, + response: { + took: 2, + timed_out: false, + _shards: { + total: 12, + successful: 12, + skipped: 11, + failed: 0, + }, + hits: { + total: { + value: 61, + relation: 'eq', + }, + max_score: null, + hits: [], + }, + ...rawResponse, + }, + }; + return { body }; } describe('SearchInterceptor', () => { @@ -102,7 +135,7 @@ describe('SearchInterceptor', () => { state$: sessionState$, }; - fetchMock = jest.fn(); + mockCoreSetup.http.post = jest.fn(); mockCoreSetup.uiSettings.get.mockImplementation((name: string) => { switch (name) { case UI_SETTINGS.SEARCH_TIMEOUT: @@ -117,17 +150,11 @@ describe('SearchInterceptor', () => { complete.mockClear(); jest.clearAllTimers(); - const bfetchMock = bfetchPluginMock.createSetupContract(); - bfetchMock.batchedFunction.mockReturnValue(fetchMock); - const inspectorServiceMock = { open: () => {}, } as unknown as InspectorStart; - bfetchSetup = bfetchPluginMock.createSetupContract(); - bfetchSetup.batchedFunction.mockReturnValue(fetchMock); searchInterceptor = new SearchInterceptor({ - bfetch: bfetchSetup, toasts: mockCoreSetup.notifications.toasts, startServices: new Promise((resolve) => { resolve([ @@ -184,30 +211,48 @@ describe('SearchInterceptor', () => { describe('search', () => { test('Observable should resolve if fetch is successful', async () => { - const mockResponse: any = { rawResponse: {} }; - fetchMock.mockResolvedValueOnce(mockResponse); + mockCoreSetup.http.post.mockResolvedValueOnce(getMockSearchResponse()); const mockRequest: IEsSearchRequest = { params: {}, }; const response = searchInterceptor.search(mockRequest); - await expect(response.toPromise()).resolves.toBe(mockResponse); + await expect(response.toPromise()).resolves.toMatchInlineSnapshot(` + Object { + "id": undefined, + "isPartial": false, + "isRestored": false, + "isRunning": false, + "loaded": 12, + "rawResponse": Object { + "_shards": Object { + "failed": 0, + "skipped": 11, + "successful": 12, + "total": 12, + }, + "hits": Object { + "hits": Array [], + "max_score": null, + "total": 61, + }, + "timed_out": false, + "took": 2, + }, + "requestParams": Object {}, + "total": 12, + "warning": undefined, + } + `); }); test('should resolve immediately if first call returns full result', async () => { const responses = [ { time: 10, - value: { - isPartial: false, - isRunning: false, - id: 1, - rawResponse: { - took: 1, - }, - }, + value: getMockSearchResponse(), }, ]; - mockFetchImplementation(responses); + mockCoreSetup.http.post.mockImplementation(getHttpMock(responses)); const response = searchInterceptor.search({}); response.subscribe({ next, error, complete }); @@ -215,7 +260,33 @@ describe('SearchInterceptor', () => { await timeTravel(10); expect(next).toHaveBeenCalled(); - expect(next.mock.calls[0][0]).toStrictEqual(responses[0].value); + expect(next.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "id": undefined, + "isPartial": false, + "isRestored": false, + "isRunning": false, + "loaded": 12, + "rawResponse": Object { + "_shards": Object { + "failed": 0, + "skipped": 11, + "successful": 12, + "total": 12, + }, + "hits": Object { + "hits": Array [], + "max_score": null, + "total": 61, + }, + "timed_out": false, + "took": 2, + }, + "requestParams": Object {}, + "total": 12, + "warning": undefined, + } + `); expect(complete).toHaveBeenCalled(); expect(error).not.toHaveBeenCalled(); }); @@ -224,29 +295,29 @@ describe('SearchInterceptor', () => { const responses = [ { time: 10, - value: { + value: getMockSearchResponse({ isPartial: true, isRunning: true, - id: 1, + id: '1', rawResponse: { took: 1, }, - }, + }), }, { time: 20, - value: { + value: getMockSearchResponse({ isPartial: false, isRunning: false, - id: 1, + id: '1', rawResponse: { took: 1, }, - }, + }), }, ]; - mockFetchImplementation(responses); + mockCoreSetup.http.post.mockImplementation(getHttpMock(responses)); const response = searchInterceptor.search({}, { pollInterval: 0 }); response.subscribe({ next, error, complete }); @@ -254,14 +325,66 @@ describe('SearchInterceptor', () => { await timeTravel(10); expect(next).toHaveBeenCalled(); - expect(next.mock.calls[0][0]).toStrictEqual(responses[0].value); + expect(next.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "id": "1", + "isPartial": true, + "isRestored": false, + "isRunning": true, + "loaded": 12, + "rawResponse": Object { + "_shards": Object { + "failed": 0, + "skipped": 11, + "successful": 12, + "total": 12, + }, + "hits": Object { + "hits": Array [], + "max_score": null, + "total": 61, + }, + "timed_out": false, + "took": 1, + }, + "requestParams": Object {}, + "total": 12, + "warning": undefined, + } + `); expect(complete).not.toHaveBeenCalled(); expect(error).not.toHaveBeenCalled(); await timeTravel(20); expect(next).toHaveBeenCalledTimes(2); - expect(next.mock.calls[1][0]).toStrictEqual(responses[1].value); + expect(next.mock.calls[1][0]).toMatchInlineSnapshot(` + Object { + "id": "1", + "isPartial": false, + "isRestored": false, + "isRunning": false, + "loaded": 12, + "rawResponse": Object { + "_shards": Object { + "failed": 0, + "skipped": 11, + "successful": 12, + "total": 12, + }, + "hits": Object { + "hits": Array [], + "max_score": null, + "total": 61, + }, + "timed_out": false, + "took": 1, + }, + "requestParams": Object {}, + "total": 12, + "warning": undefined, + } + `); expect(complete).toHaveBeenCalled(); expect(error).not.toHaveBeenCalled(); }); @@ -270,15 +393,15 @@ describe('SearchInterceptor', () => { const responses = [ { time: 500, - value: { + value: getMockSearchResponse({ isPartial: false, isRunning: false, rawResponse: {}, - id: 1, - }, + id: '1', + }), }, ]; - mockFetchImplementation(responses); + mockCoreSetup.http.post.mockImplementation(getHttpMock(responses)); const abortController = new AbortController(); abortController.abort(); @@ -297,24 +420,24 @@ describe('SearchInterceptor', () => { const responses = [ { time: 10, - value: { + value: getMockSearchResponse({ isPartial: true, isRunning: true, rawResponse: {}, - id: 1, - }, + id: '1', + }), }, { time: 300, - value: { + value: getMockSearchResponse({ isPartial: false, isRunning: false, rawResponse: {}, - id: 1, - }, + id: '1', + }), }, ]; - mockFetchImplementation(responses); + mockCoreSetup.http.post.mockImplementation(getHttpMock(responses)); const abortController = new AbortController(); setTimeout(() => abortController.abort(), 250); @@ -335,7 +458,7 @@ describe('SearchInterceptor', () => { expect(error).toHaveBeenCalled(); expect(error.mock.calls[0][0]).toBeInstanceOf(AbortError); - expect(fetchMock).toHaveBeenCalledTimes(2); + expect(mockCoreSetup.http.post).toHaveBeenCalledTimes(2); expect(mockCoreSetup.http.delete).toHaveBeenCalledTimes(1); }); @@ -343,32 +466,34 @@ describe('SearchInterceptor', () => { const responses = [ { time: 2000, - value: { + value: getMockSearchResponse({ isPartial: false, isRunning: false, rawResponse: {}, - id: 1, - }, + id: '1', + }), }, ]; - mockFetchImplementation(responses); + mockCoreSetup.http.post.mockImplementation(getHttpMock(responses)); const response = searchInterceptor.search({}, { pollInterval: 0 }); response.subscribe({ next, error }); await timeTravel(1000); - expect(fetchMock).toHaveBeenCalled(); + expect(mockCoreSetup.http.post).toHaveBeenCalled(); expect(mockCoreSetup.http.delete).not.toHaveBeenCalled(); }); test('should DELETE a running async search on async timeout after first response', async () => { - fetchMock.mockResolvedValue({ - isPartial: true, - isRunning: true, - rawResponse: {}, - id: 1, - }); + mockCoreSetup.http.post.mockResolvedValue( + getMockSearchResponse({ + isPartial: true, + isRunning: true, + rawResponse: {}, + id: '1', + }) + ); const response = searchInterceptor.search({}, { pollInterval: 0 }); response.subscribe({ next, error }); @@ -377,7 +502,7 @@ describe('SearchInterceptor', () => { expect(next).toHaveBeenCalled(); expect(error).not.toHaveBeenCalled(); - expect(fetchMock).toHaveBeenCalled(); + expect(mockCoreSetup.http.post).toHaveBeenCalled(); expect(mockCoreSetup.http.delete).not.toHaveBeenCalled(); // Long enough to reach the timeout @@ -387,14 +512,16 @@ describe('SearchInterceptor', () => { }); test('should return the last response on async timeout', async () => { - fetchMock.mockResolvedValue({ - isPartial: true, - isRunning: true, - rawResponse: { - foo: 'bar', - }, - id: 1, - }); + mockCoreSetup.http.post.mockResolvedValue( + getMockSearchResponse({ + isPartial: true, + isRunning: true, + rawResponse: { + foo: 'bar', + }, + id: '1', + }) + ); const response = searchInterceptor.search({}, { pollInterval: 0 }); response.subscribe({ next, error }); @@ -403,7 +530,7 @@ describe('SearchInterceptor', () => { expect(next).toHaveBeenCalled(); expect(error).not.toHaveBeenCalled(); - expect(fetchMock).toHaveBeenCalled(); + expect(mockCoreSetup.http.post).toHaveBeenCalled(); expect(mockCoreSetup.http.delete).not.toHaveBeenCalled(); // Long enough to reach the timeout but not long enough to reach the next response @@ -413,12 +540,30 @@ describe('SearchInterceptor', () => { expect(next.mock.calls[1]).toMatchInlineSnapshot(` Array [ Object { - "id": 1, + "id": "1", "isPartial": true, + "isRestored": false, "isRunning": true, + "loaded": 12, "rawResponse": Object { + "_shards": Object { + "failed": 0, + "skipped": 11, + "successful": 12, + "total": 12, + }, "foo": "bar", + "hits": Object { + "hits": Array [], + "max_score": null, + "total": 61, + }, + "timed_out": false, + "took": 2, }, + "requestParams": Object {}, + "total": 12, + "warning": undefined, }, ] `); @@ -428,24 +573,24 @@ describe('SearchInterceptor', () => { const responses = [ { time: 10, - value: { + value: getMockSearchResponse({ isPartial: true, isRunning: true, rawResponse: {}, - id: 1, - }, + id: '1', + }), }, { time: 10, value: { statusCode: 500, message: 'oh no', - id: 1, + id: '1', }, isError: true, }, ]; - mockFetchImplementation(responses); + mockCoreSetup.http.post.mockImplementation(getHttpMock(responses)); const response = searchInterceptor.search({}, { pollInterval: 0 }); response.subscribe({ next, error }); @@ -454,7 +599,7 @@ describe('SearchInterceptor', () => { expect(next).toHaveBeenCalled(); expect(error).not.toHaveBeenCalled(); - expect(fetchMock).toHaveBeenCalled(); + expect(mockCoreSetup.http.post).toHaveBeenCalled(); expect(mockCoreSetup.http.delete).not.toHaveBeenCalled(); // Long enough to reach the timeout but not long enough to reach the next response @@ -463,7 +608,7 @@ describe('SearchInterceptor', () => { expect(error).toHaveBeenCalled(); expect(error.mock.calls[0][0]).toBeInstanceOf(Error); expect((error.mock.calls[0][0] as Error).message).toBe('oh no'); - expect(fetchMock).toHaveBeenCalledTimes(2); + expect(mockCoreSetup.http.post).toHaveBeenCalledTimes(2); expect(mockCoreSetup.http.delete).toHaveBeenCalledTimes(1); }); @@ -472,24 +617,24 @@ describe('SearchInterceptor', () => { const responses = [ { time: 10, - value: { + value: getMockSearchResponse({ isPartial: true, isRunning: true, rawResponse: {}, - id: 1, - }, + id: '1', + }), }, { time: 10, value: { statusCode: 500, message: 'oh no', - id: 1, + id: '1', }, isError: true, }, ]; - mockFetchImplementation(responses); + mockCoreSetup.http.post.mockImplementation(getHttpMock(responses)); const response = searchInterceptor.search({}, { pollInterval: 0 }); response.subscribe({ next, error }); @@ -498,7 +643,7 @@ describe('SearchInterceptor', () => { expect(next).toHaveBeenCalled(); expect(error).not.toHaveBeenCalled(); - expect(fetchMock).toHaveBeenCalled(); + expect(mockCoreSetup.http.post).toHaveBeenCalled(); expect(mockCoreSetup.http.delete).not.toHaveBeenCalled(); // Long enough to reach the timeout but not long enough to reach the next response @@ -507,7 +652,7 @@ describe('SearchInterceptor', () => { expect(error).toHaveBeenCalled(); expect(error.mock.calls[0][0]).toBeInstanceOf(Error); expect((error.mock.calls[0][0] as Error).message).toBe('oh no'); - expect(fetchMock).toHaveBeenCalledTimes(2); + expect(mockCoreSetup.http.post).toHaveBeenCalledTimes(2); expect(mockCoreSetup.http.delete).toHaveBeenCalledTimes(1); }); @@ -517,24 +662,24 @@ describe('SearchInterceptor', () => { const responses = [ { time: 10, - value: { + value: getMockSearchResponse({ isPartial: true, isRunning: true, rawResponse: {}, - id: 1, - }, + id: '1', + }), }, { time: 300, - value: { + value: getMockSearchResponse({ isPartial: false, isRunning: false, rawResponse: {}, - id: 1, - }, + id: '1', + }), }, ]; - mockFetchImplementation(responses); + mockCoreSetup.http.post.mockImplementation(getHttpMock(responses)); const abortController = new AbortController(); setTimeout(() => abortController.abort(), 250); @@ -557,7 +702,7 @@ describe('SearchInterceptor', () => { expect(error).toHaveBeenCalled(); expect(error.mock.calls[0][0]).toBeInstanceOf(AbortError); - expect(fetchMock).toHaveBeenCalledTimes(2); + expect(mockCoreSetup.http.post).toHaveBeenCalledTimes(2); expect(mockCoreSetup.http.delete).not.toHaveBeenCalled(); }); @@ -581,7 +726,7 @@ describe('SearchInterceptor', () => { ); sessionServiceMock.isRestore.mockReturnValue(!!opts?.isRestore); sessionServiceMock.getSessionId.mockImplementation(() => opts?.sessionId); - fetchMock.mockResolvedValue({ result: 200 }); + mockCoreSetup.http.post.mockResolvedValue({ result: 200 }); }; const mockRequest: IEsSearchRequest = { @@ -591,7 +736,7 @@ describe('SearchInterceptor', () => { afterEach(() => { const sessionServiceMock = sessionService as jest.Mocked; sessionServiceMock.getSearchOptions.mockReset(); - fetchMock.mockReset(); + mockCoreSetup.http.post.mockReset(); }); test('gets session search options from session service', async () => { @@ -606,15 +751,18 @@ describe('SearchInterceptor', () => { .search(mockRequest, { sessionId }) .toPromise() .catch(() => {}); - expect(fetchMock.mock.calls[0][0]).toEqual( + const [path, options] = mockCoreSetup.http.post.mock.calls[0] as unknown as [ + path: string, + options: HttpFetchOptions + ]; + const body = JSON.parse(options?.body as string); + expect(path).toEqual('/internal/search/ese'); + expect(body).toEqual( expect.objectContaining({ - options: { - sessionId, - isStored: true, - isRestore: true, - isSearchStored: false, - strategy: 'ese', - }, + sessionId, + isStored: true, + isRestore: true, + isSearchStored: false, }) ); @@ -631,7 +779,7 @@ describe('SearchInterceptor', () => { .search(mockRequest, { sessionId }) .toPromise() .catch(() => {}); - expect(fetchMock.mock.calls[0][0]).toEqual( + expect(mockCoreSetup.http.post.mock.calls[0][0]).toEqual( expect.not.objectContaining({ options: { sessionId }, }) @@ -656,14 +804,14 @@ describe('SearchInterceptor', () => { isPartial: false, isRunning: false, isRestored: true, - id: 1, + id: '1', rawResponse: { took: 1, }, }, }, ]; - mockFetchImplementation(responses); + mockCoreSetup.http.post.mockImplementation(getHttpMock(responses)); const response = searchInterceptor.search( {}, @@ -691,14 +839,14 @@ describe('SearchInterceptor', () => { isPartial: false, isRunning: false, isRestored: false, - id: 1, + id: '1', rawResponse: { took: 1, }, }, }, ]; - mockFetchImplementation(responses); + mockCoreSetup.http.post.mockImplementation(getHttpMock(responses)); const response = searchInterceptor.search( {}, @@ -723,18 +871,18 @@ describe('SearchInterceptor', () => { const responses = [ { time: 10, - value: { + value: getMockSearchResponse({ isPartial: false, isRunning: false, isRestored: false, - id: 1, + id: '1', rawResponse: { took: 1, }, - }, + }), }, ]; - mockFetchImplementation(responses); + mockCoreSetup.http.post.mockImplementation(getHttpMock(responses)); searchInterceptor .search( @@ -769,25 +917,25 @@ describe('SearchInterceptor', () => { const responses = [ { time: 10, - value: { + value: getMockSearchResponse({ isPartial: true, isRunning: true, rawResponse: {}, - id: 1, - }, + id: '1', + }), }, { time: 300, - value: { + value: getMockSearchResponse({ isPartial: false, isRunning: false, rawResponse: {}, - id: 1, - }, + id: '1', + }), }, ]; - mockFetchImplementation(responses); + mockCoreSetup.http.post.mockImplementation(getHttpMock(responses)); }); test('should track searches', async () => { @@ -886,39 +1034,39 @@ describe('SearchInterceptor', () => { const basicCompleteResponse = [ { time: 10, - value: { + value: getMockSearchResponse({ isPartial: false, isRunning: false, - id: 1, + id: '1', rawResponse: { took: 1, }, - }, + }), }, ]; const partialCompleteResponse = [ { time: 10, - value: { + value: getMockSearchResponse({ isPartial: true, isRunning: true, - id: 1, + id: '1', rawResponse: { took: 1, }, - }, + }), }, { time: 20, - value: { + value: getMockSearchResponse({ isPartial: false, isRunning: false, - id: 1, + id: '1', rawResponse: { took: 1, }, - }, + }), }, ]; @@ -930,17 +1078,17 @@ describe('SearchInterceptor', () => { }); test('should be disabled if there is no session', async () => { - mockFetchImplementation(basicCompleteResponse); + mockCoreSetup.http.post.mockImplementation(getHttpMock(basicCompleteResponse)); searchInterceptor.search(basicReq, {}).subscribe({ next, error, complete }); - expect(fetchMock).toBeCalledTimes(1); + expect(mockCoreSetup.http.post).toBeCalledTimes(1); searchInterceptor.search(basicReq, {}).subscribe({ next, error, complete }); - expect(fetchMock).toBeCalledTimes(2); + expect(mockCoreSetup.http.post).toBeCalledTimes(2); }); test('should fetch different requests in a single session', async () => { - mockFetchImplementation(basicCompleteResponse); + mockCoreSetup.http.post.mockImplementation(getHttpMock(basicCompleteResponse)); const req2 = { params: { @@ -950,29 +1098,29 @@ describe('SearchInterceptor', () => { searchInterceptor.search(basicReq, { sessionId }).subscribe({ next, error, complete }); await timeTravel(10); - expect(fetchMock).toBeCalledTimes(1); + expect(mockCoreSetup.http.post).toBeCalledTimes(1); searchInterceptor.search(req2, { sessionId }).subscribe({ next, error, complete }); await timeTravel(10); - expect(fetchMock).toBeCalledTimes(2); + expect(mockCoreSetup.http.post).toBeCalledTimes(2); }); test('should fetch the same request for two different sessions', async () => { - mockFetchImplementation(basicCompleteResponse); + mockCoreSetup.http.post.mockImplementation(getHttpMock(basicCompleteResponse)); searchInterceptor.search(basicReq, { sessionId }).subscribe({ next, error, complete }); await timeTravel(10); - expect(fetchMock).toBeCalledTimes(1); + expect(mockCoreSetup.http.post).toBeCalledTimes(1); searchInterceptor .search(basicReq, { sessionId: 'anotherSession' }) .subscribe({ next, error, complete }); await timeTravel(10); - expect(fetchMock).toBeCalledTimes(2); + expect(mockCoreSetup.http.post).toBeCalledTimes(2); }); test('should not track searches that come from cache', async () => { - mockFetchImplementation(partialCompleteResponse); + mockCoreSetup.http.post.mockImplementation(getHttpMock(partialCompleteResponse)); sessionService.isCurrentSession.mockImplementation( (_sessionId) => _sessionId === sessionId ); @@ -1000,12 +1148,12 @@ describe('SearchInterceptor', () => { response2.subscribe({ next, error, complete }); await timeTravel(10); - expect(fetchMock).toBeCalledTimes(1); + expect(mockCoreSetup.http.post).toBeCalledTimes(1); expect(sessionService.trackSearch).toBeCalledTimes(1); expect(completeSearch).not.toBeCalled(); await timeTravel(300); // Should be called only 2 times (once per partial response) - expect(fetchMock).toBeCalledTimes(2); + expect(mockCoreSetup.http.post).toBeCalledTimes(2); expect(sessionService.trackSearch).toBeCalledTimes(1); expect(completeSearch).toBeCalledTimes(1); @@ -1018,51 +1166,53 @@ describe('SearchInterceptor', () => { const responses = [ { time: 10, - value: { + value: getMockSearchResponse({ isPartial: true, isRunning: true, rawResponse: {}, - id: 1, - }, + id: '1', + }), }, ]; - mockFetchImplementation(responses); + mockCoreSetup.http.post.mockImplementation(getHttpMock(responses)); searchInterceptor.search(basicReq, { sessionId }).subscribe({ next, error, complete }); await timeTravel(10); - expect(fetchMock).toBeCalledTimes(1); + expect(mockCoreSetup.http.post).toBeCalledTimes(1); searchInterceptor.search(basicReq, { sessionId }).subscribe({ next, error, complete }); await timeTravel(10); - expect(fetchMock).toBeCalledTimes(1); + expect(mockCoreSetup.http.post).toBeCalledTimes(1); }); test('should not cache error responses', async () => { const responses = [ { time: 10, - value: { + value: getMockSearchResponse({ isPartial: true, isRunning: false, - id: 1, - }, + id: '1', + rawResponse: {}, + }), + isError: true, }, ]; - mockFetchImplementation(responses); + mockCoreSetup.http.post.mockImplementation(getHttpMock(responses)); searchInterceptor.search(basicReq, { sessionId }).subscribe({ next, error, complete }); await timeTravel(10); - expect(fetchMock).toBeCalledTimes(1); + expect(mockCoreSetup.http.post).toBeCalledTimes(1); searchInterceptor.search(basicReq, { sessionId }).subscribe({ next, error, complete }); await timeTravel(10); - expect(fetchMock).toBeCalledTimes(2); + expect(mockCoreSetup.http.post).toBeCalledTimes(2); }); test('should ignore anything outside params when hashing', async () => { - mockFetchImplementation(basicCompleteResponse); + mockCoreSetup.http.post.mockImplementation(getHttpMock(basicCompleteResponse)); const req = { something: 123, @@ -1080,34 +1230,39 @@ describe('SearchInterceptor', () => { searchInterceptor.search(req, { sessionId }).subscribe({ next, error, complete }); await timeTravel(10); - expect(fetchMock).toBeCalledTimes(1); + expect(mockCoreSetup.http.post).toBeCalledTimes(1); searchInterceptor.search(req2, { sessionId }).subscribe({ next, error, complete }); await timeTravel(10); - expect(fetchMock).toBeCalledTimes(1); + expect(mockCoreSetup.http.post).toBeCalledTimes(1); }); test('should deliver error to all replays', async () => { const responses = [ { time: 10, - value: {}, + value: { + statusCode: 500, + message: 'Aborted', + id: '1', + }, + isError: true, }, ]; - mockFetchImplementation(responses); + mockCoreSetup.http.post.mockImplementation(getHttpMock(responses)); searchInterceptor.search(basicReq, { sessionId }).subscribe({ next, error, complete }); searchInterceptor.search(basicReq, { sessionId }).subscribe({ next, error, complete }); await timeTravel(10); - expect(fetchMock).toBeCalledTimes(1); + expect(mockCoreSetup.http.post).toBeCalledTimes(1); expect(error).toBeCalledTimes(2); expect(error.mock.calls[0][0].message).toEqual('Aborted'); expect(error.mock.calls[1][0].message).toEqual('Aborted'); }); test('should ignore preference when hashing', async () => { - mockFetchImplementation(basicCompleteResponse); + mockCoreSetup.http.post.mockImplementation(getHttpMock(basicCompleteResponse)); const req = { params: { @@ -1125,27 +1280,27 @@ describe('SearchInterceptor', () => { searchInterceptor.search(req, { sessionId }).subscribe({ next, error, complete }); await timeTravel(10); - expect(fetchMock).toBeCalledTimes(1); + expect(mockCoreSetup.http.post).toBeCalledTimes(1); searchInterceptor.search(req2, { sessionId }).subscribe({ next, error, complete }); await timeTravel(10); - expect(fetchMock).toBeCalledTimes(1); + expect(mockCoreSetup.http.post).toBeCalledTimes(1); }); test('should return from cache for identical requests in the same session', async () => { - mockFetchImplementation(basicCompleteResponse); + mockCoreSetup.http.post.mockImplementation(getHttpMock(basicCompleteResponse)); searchInterceptor.search(basicReq, { sessionId }).subscribe({ next, error, complete }); await timeTravel(10); - expect(fetchMock).toBeCalledTimes(1); + expect(mockCoreSetup.http.post).toBeCalledTimes(1); searchInterceptor.search(basicReq, { sessionId }).subscribe({ next, error, complete }); await timeTravel(10); - expect(fetchMock).toBeCalledTimes(1); + expect(mockCoreSetup.http.post).toBeCalledTimes(1); }); test('aborting a search that didnt get any response should retrigger search', async () => { - mockFetchImplementation(basicCompleteResponse); + mockCoreSetup.http.post.mockImplementation(getHttpMock(basicCompleteResponse)); const abortController = new AbortController(); @@ -1159,7 +1314,7 @@ describe('SearchInterceptor', () => { // Time travel to make sure nothing appens await timeTravel(10); - expect(fetchMock).toBeCalledTimes(0); + expect(mockCoreSetup.http.post).toBeCalledTimes(0); expect(next).toBeCalledTimes(0); expect(error).toBeCalledTimes(1); expect(complete).toBeCalledTimes(0); @@ -1175,14 +1330,14 @@ describe('SearchInterceptor', () => { // Should search again await timeTravel(10); - expect(fetchMock).toBeCalledTimes(1); + expect(mockCoreSetup.http.post).toBeCalledTimes(1); expect(next2).toBeCalledTimes(1); expect(error2).toBeCalledTimes(0); expect(complete2).toBeCalledTimes(1); }); test('aborting a running first search shouldnt clear cache', async () => { - mockFetchImplementation(partialCompleteResponse); + mockCoreSetup.http.post.mockImplementation(getHttpMock(partialCompleteResponse)); sessionService.isCurrentSession.mockImplementation( (_sessionId) => _sessionId === sessionId ); @@ -1214,7 +1369,7 @@ describe('SearchInterceptor', () => { response.subscribe({ next, error, complete }); await timeTravel(10); - expect(fetchMock).toBeCalledTimes(1); + expect(mockCoreSetup.http.post).toBeCalledTimes(1); expect(next).toBeCalledTimes(1); expect(error).toBeCalledTimes(0); expect(complete).toBeCalledTimes(0); @@ -1245,11 +1400,11 @@ describe('SearchInterceptor', () => { expect(complete2).toBeCalledTimes(1); // Should be called only 2 times (once per partial response) - expect(fetchMock).toBeCalledTimes(2); + expect(mockCoreSetup.http.post).toBeCalledTimes(2); }); test('aborting a running second search shouldnt clear cache', async () => { - mockFetchImplementation(partialCompleteResponse); + mockCoreSetup.http.post.mockImplementation(getHttpMock(partialCompleteResponse)); sessionService.isCurrentSession.mockImplementation( (_sessionId) => _sessionId === sessionId ); @@ -1277,7 +1432,7 @@ describe('SearchInterceptor', () => { response.subscribe({ next, error, complete }); await timeTravel(10); - expect(fetchMock).toBeCalledTimes(1); + expect(mockCoreSetup.http.post).toBeCalledTimes(1); expect(next).toBeCalledTimes(1); expect(error).toBeCalledTimes(0); expect(complete).toBeCalledTimes(0); @@ -1310,11 +1465,11 @@ describe('SearchInterceptor', () => { expect(complete2).toBeCalledTimes(0); // Should be called only 2 times (once per partial response) - expect(fetchMock).toBeCalledTimes(2); + expect(mockCoreSetup.http.post).toBeCalledTimes(2); }); test('aborting both requests should cancel underlaying search only once', async () => { - mockFetchImplementation(partialCompleteResponse); + mockCoreSetup.http.post.mockImplementation(getHttpMock(partialCompleteResponse)); sessionService.isCurrentSession.mockImplementation( (_sessionId) => _sessionId === sessionId ); @@ -1351,7 +1506,7 @@ describe('SearchInterceptor', () => { }); test('aborting both searches should stop searching and clear cache', async () => { - mockFetchImplementation(partialCompleteResponse); + mockCoreSetup.http.post.mockImplementation(getHttpMock(partialCompleteResponse)); sessionService.isCurrentSession.mockImplementation( (_sessionId) => _sessionId === sessionId ); @@ -1382,7 +1537,7 @@ describe('SearchInterceptor', () => { }); response.subscribe({ next, error, complete }); await timeTravel(10); - expect(fetchMock).toBeCalledTimes(1); + expect(mockCoreSetup.http.post).toBeCalledTimes(1); const response2 = searchInterceptor.search(req, { pollInterval: 1, @@ -1391,7 +1546,7 @@ describe('SearchInterceptor', () => { }); response2.subscribe({ next, error, complete }); await timeTravel(0); - expect(fetchMock).toBeCalledTimes(1); + expect(mockCoreSetup.http.post).toBeCalledTimes(1); abortController.abort(); @@ -1404,11 +1559,11 @@ describe('SearchInterceptor', () => { expect(error.mock.calls[1][0]).toBeInstanceOf(AbortError); // Should be called only 1 times (one partial response) - expect(fetchMock).toBeCalledTimes(1); + expect(mockCoreSetup.http.post).toBeCalledTimes(1); // Clear mock and research - fetchMock.mockReset(); - mockFetchImplementation(partialCompleteResponse); + mockCoreSetup.http.post.mockReset(); + mockCoreSetup.http.post.mockImplementation(getHttpMock(partialCompleteResponse)); // Run the search again to see that we don't hit the cache const response3 = searchInterceptor.search(req, { pollInterval: 1, sessionId }); response3.subscribe({ next, error, complete }); @@ -1418,12 +1573,12 @@ describe('SearchInterceptor', () => { await timeTravel(300); // Should be called 2 times (two partial response) - expect(fetchMock).toBeCalledTimes(2); + expect(mockCoreSetup.http.post).toBeCalledTimes(2); expect(complete).toBeCalledTimes(1); }); test("aborting a completed search shouldn't effect cache", async () => { - mockFetchImplementation(basicCompleteResponse); + mockCoreSetup.http.post.mockImplementation(getHttpMock(basicCompleteResponse)); const abortController = new AbortController(); @@ -1434,7 +1589,7 @@ describe('SearchInterceptor', () => { // Get a final response await timeTravel(10); - expect(fetchMock).toBeCalledTimes(1); + expect(mockCoreSetup.http.post).toBeCalledTimes(1); // Abort the search request abortController.abort(); @@ -1443,14 +1598,14 @@ describe('SearchInterceptor', () => { searchInterceptor.search(basicReq, { sessionId }).subscribe({ next, error, complete }); // Get the response from cache - expect(fetchMock).toBeCalledTimes(1); + expect(mockCoreSetup.http.post).toBeCalledTimes(1); }); }); describe('Should throw typed errors', () => { test('Observable should fail if fetch has an internal error', async () => { const mockResponse: any = new Error('Internal Error'); - fetchMock.mockRejectedValue(mockResponse); + mockCoreSetup.http.post.mockRejectedValue(mockResponse); const mockRequest: IEsSearchRequest = { params: {}, }; @@ -1464,7 +1619,7 @@ describe('SearchInterceptor', () => { statusCode: 500, message: 'Request timed out', }; - fetchMock.mockRejectedValueOnce(mockResponse); + mockCoreSetup.http.post.mockRejectedValueOnce(mockResponse); const mockRequest: IEsSearchRequest = { params: {}, }; @@ -1478,7 +1633,7 @@ describe('SearchInterceptor', () => { statusCode: 500, message: 'Request timed out', }; - fetchMock.mockRejectedValue(mockResponse); + mockCoreSetup.http.post.mockRejectedValue(mockResponse); const mockRequest: IEsSearchRequest = { params: {}, }; @@ -1497,7 +1652,7 @@ describe('SearchInterceptor', () => { statusCode: 500, message: 'Request timed out', }; - fetchMock.mockRejectedValue(mockResponse); + mockCoreSetup.http.post.mockRejectedValue(mockResponse); const mockRequest: IEsSearchRequest = { params: {}, }; @@ -1516,7 +1671,7 @@ describe('SearchInterceptor', () => { statusCode: 500, message: 'Request timed out', }; - fetchMock.mockRejectedValue(mockResponse); + mockCoreSetup.http.post.mockRejectedValue(mockResponse); const mockRequest: IEsSearchRequest = { params: {}, }; @@ -1538,7 +1693,7 @@ describe('SearchInterceptor', () => { error: resourceNotFoundException.error, }, }; - fetchMock.mockRejectedValueOnce(mockResponse); + mockCoreSetup.http.post.mockRejectedValueOnce(mockResponse); const mockRequest: IEsSearchRequest = { params: {}, }; @@ -1548,7 +1703,7 @@ describe('SearchInterceptor', () => { test('Observable should fail if user aborts (test merged signal)', async () => { const abortController = new AbortController(); - fetchMock.mockImplementationOnce((options: any) => { + mockCoreSetup.http.post.mockImplementationOnce((options: any) => { return new Promise((resolve, reject) => { options.signal.addEventListener('abort', () => { reject(new AbortError()); @@ -1586,7 +1741,7 @@ describe('SearchInterceptor', () => { error.mockImplementation((e) => { expect(e).toBeInstanceOf(AbortError); - expect(fetchMock).not.toBeCalled(); + expect(mockCoreSetup.http.post).not.toBeCalled(); }); response.subscribe({ error }); diff --git a/src/plugins/data/public/search/search_interceptor/search_interceptor.ts b/src/plugins/data/public/search/search_interceptor/search_interceptor.ts index 458171e64a1d3..8b312cd2fab87 100644 --- a/src/plugins/data/public/search/search_interceptor/search_interceptor.ts +++ b/src/plugins/data/public/search/search_interceptor/search_interceptor.ts @@ -49,10 +49,8 @@ import type { ToastsSetup, } from '@kbn/core/public'; -import { BatchedFunc, BfetchPublicSetup, DISABLE_BFETCH } from '@kbn/bfetch-plugin/public'; import { toMountPoint } from '@kbn/react-kibana-mount'; import { AbortError, KibanaServerError } from '@kbn/kibana-utils-plugin/public'; -import { BfetchRequestError } from '@kbn/bfetch-error'; import type { SanitizedConnectionRequestParams, IKibanaSearchRequest, @@ -87,7 +85,6 @@ import type { SearchServiceStartDependencies } from '../search_service'; import { createRequestHash } from './create_request_hash'; export interface SearchInterceptorDeps { - bfetch: BfetchPublicSetup; http: HttpSetup; executionContext: ExecutionContextSetup; uiSettings: IUiSettingsClient; @@ -104,7 +101,6 @@ const MAX_CACHE_SIZE_MB = 10; export class SearchInterceptor { private uiSettingsSubs: Subscription[] = []; private searchTimeout: number; - private bFetchDisabled: boolean; private readonly responseCache: SearchResponseCache = new SearchResponseCache( MAX_CACHE_ITEMS, MAX_CACHE_SIZE_MB @@ -121,10 +117,6 @@ export class SearchInterceptor { */ private application!: ApplicationStart; private docLinks!: DocLinksStart; - private batchedFetch!: BatchedFunc< - { request: IKibanaSearchRequest; options: ISearchOptionsSerializable }, - IKibanaSearchResponse - >; private inspector!: InspectorStart; /* @@ -151,19 +143,11 @@ export class SearchInterceptor { this.inspector = (depsStart as SearchServiceStartDependencies).inspector; }); - this.batchedFetch = deps.bfetch.batchedFunction({ - url: '/internal/bsearch', - }); - this.searchTimeout = deps.uiSettings.get(UI_SETTINGS.SEARCH_TIMEOUT); - this.bFetchDisabled = deps.uiSettings.get(DISABLE_BFETCH); this.uiSettingsSubs.push( deps.uiSettings.get$(UI_SETTINGS.SEARCH_TIMEOUT).subscribe((timeout: number) => { this.searchTimeout = timeout; - }), - deps.uiSettings.get$(DISABLE_BFETCH).subscribe((bFetchDisabled: boolean) => { - this.bFetchDisabled = bFetchDisabled; }) ); } @@ -223,8 +207,8 @@ export class SearchInterceptor { return err; } - if (e instanceof AbortError || e instanceof BfetchRequestError) { - // In the case an application initiated abort, throw the existing AbortError, same with BfetchRequestErrors + if (e instanceof AbortError) { + // In the case an application initiated abort, throw the existing AbortError return e; } @@ -450,99 +434,85 @@ export class SearchInterceptor { ): Promise { const { abortSignal } = options || {}; - if (this.bFetchDisabled) { - const { executionContext, strategy, ...searchOptions } = this.getSerializableOptions(options); - return this.deps.http - .post( - `/internal/search/${strategy}${request.id ? `/${request.id}` : ''}`, - { - version: '1', - signal: abortSignal, - context: executionContext, - body: JSON.stringify({ - ...request, - ...searchOptions, - stream: - strategy === ESQL_ASYNC_SEARCH_STRATEGY || - strategy === ENHANCED_ES_SEARCH_STRATEGY || - strategy === undefined, // undefined strategy is treated as enhanced ES - }), - asResponse: true, - } - ) - .then((rawResponse) => { - const warning = rawResponse.response?.headers.get('warning'); - const requestParams = - rawResponse.body && 'requestParams' in rawResponse.body - ? rawResponse.body.requestParams - : JSON.parse(rawResponse.response?.headers.get('kbn-search-request-params') || '{}'); - const isRestored = - rawResponse.body && 'isRestored' in rawResponse.body - ? rawResponse.body.isRestored - : rawResponse.response?.headers.get('kbn-search-is-restored') === '?1'; - - if (rawResponse.body && 'error' in rawResponse.body) { - // eslint-disable-next-line no-throw-literal - throw { - attributes: { - error: rawResponse.body.error, - rawResponse: rawResponse.body, - requestParams, - isRestored, - }, - }; - } - - switch (strategy) { - case ENHANCED_ES_SEARCH_STRATEGY: - if (rawResponse.body?.rawResponse) return rawResponse.body; - const typedResponse = rawResponse.body as unknown as AsyncSearchGetResponse; - const shimmedResponse = shimHitsTotal(typedResponse.response, { - legacyHitsTotal: searchOptions.legacyHitsTotal, - }); - return { - id: typedResponse.id, - isPartial: typedResponse.is_partial, - isRunning: typedResponse.is_running, - rawResponse: shimmedResponse, - warning, - requestParams, - isRestored, - ...getTotalLoaded(shimmedResponse), - }; - case ESQL_ASYNC_SEARCH_STRATEGY: - const esqlResponse = rawResponse.body as unknown as SqlGetAsyncResponse; - return { - id: esqlResponse.id, - rawResponse: esqlResponse, - isPartial: esqlResponse.is_partial, - isRunning: esqlResponse.is_running, - warning, - }; - default: - return rawResponse.body; - } - }) - .catch((e: IHttpFetchError) => { - if (e?.body) { - throw e.body; - } else { - throw e; - } - }) as Promise; - } else { - const { executionContext, ...rest } = options || {}; - return this.batchedFetch( + const { executionContext, strategy, ...searchOptions } = this.getSerializableOptions(options); + return this.deps.http + .post( + `/internal/search/${strategy}${request.id ? `/${request.id}` : ''}`, { - request, - options: this.getSerializableOptions({ - ...rest, - executionContext: this.deps.executionContext.withGlobalContext(executionContext), + version: '1', + signal: abortSignal, + context: executionContext, + body: JSON.stringify({ + ...request, + ...searchOptions, + stream: + strategy === ESQL_ASYNC_SEARCH_STRATEGY || + strategy === ENHANCED_ES_SEARCH_STRATEGY || + strategy === undefined, // undefined strategy is treated as enhanced ES }), - }, - abortSignal - ); - } + asResponse: true, + } + ) + .then((rawResponse) => { + const warning = rawResponse.response?.headers.get('warning'); + const requestParams = + rawResponse.body && 'requestParams' in rawResponse.body + ? rawResponse.body.requestParams + : JSON.parse(rawResponse.response?.headers.get('kbn-search-request-params') || '{}'); + const isRestored = + rawResponse.body && 'isRestored' in rawResponse.body + ? rawResponse.body.isRestored + : rawResponse.response?.headers.get('kbn-search-is-restored') === '?1'; + + if (rawResponse.body && 'error' in rawResponse.body) { + // eslint-disable-next-line no-throw-literal + throw { + attributes: { + error: rawResponse.body.error, + rawResponse: rawResponse.body, + requestParams, + isRestored, + }, + }; + } + + switch (strategy) { + case ENHANCED_ES_SEARCH_STRATEGY: + if (rawResponse.body?.rawResponse) return rawResponse.body; + const typedResponse = rawResponse.body as unknown as AsyncSearchGetResponse; + const shimmedResponse = shimHitsTotal(typedResponse.response, { + legacyHitsTotal: searchOptions.legacyHitsTotal, + }); + return { + id: typedResponse.id, + isPartial: typedResponse.is_partial, + isRunning: typedResponse.is_running, + rawResponse: shimmedResponse, + warning, + requestParams, + isRestored, + ...getTotalLoaded(shimmedResponse), + }; + case ESQL_ASYNC_SEARCH_STRATEGY: + const esqlResponse = rawResponse.body as unknown as SqlGetAsyncResponse; + return { + id: esqlResponse.id, + rawResponse: esqlResponse, + isPartial: esqlResponse.is_partial, + isRunning: esqlResponse.is_running, + warning, + }; + default: + return rawResponse.body; + } + }) + .catch((e: IHttpFetchError) => { + if (e?.body) { + throw e.body; + } else { + throw e; + } + }) as Promise; } /** diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index 279e9cddc8986..d1e5d02e5d840 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -9,7 +9,6 @@ import { i18n } from '@kbn/i18n'; import { estypes } from '@elastic/elasticsearch'; -import { BfetchPublicSetup } from '@kbn/bfetch-plugin/public'; import { handleWarnings } from '@kbn/search-response-warnings'; import { CoreSetup, @@ -78,7 +77,6 @@ import { ISearchSetup, ISearchStart } from './types'; /** @internal */ export interface SearchServiceSetupDependencies { - bfetch: BfetchPublicSetup; expressions: ExpressionsSetup; usageCollection?: UsageCollectionSetup; management: ManagementSetup; @@ -106,13 +104,7 @@ export class SearchService implements Plugin { public setup( core: CoreSetup, - { - bfetch, - expressions, - usageCollection, - nowProvider, - management, - }: SearchServiceSetupDependencies + { expressions, usageCollection, nowProvider, management }: SearchServiceSetupDependencies ): ISearchSetup { const { http, getStartServices, notifications, uiSettings, executionContext } = core; this.usageCollector = createUsageCollector(getStartServices, usageCollection); @@ -130,7 +122,6 @@ export class SearchService implements Plugin { * all pending search requests, as well as getting the number of pending search requests. */ this.searchInterceptor = new SearchInterceptor({ - bfetch, toasts: notifications.toasts, executionContext, http, diff --git a/src/plugins/data/server/search/routes/bsearch.ts b/src/plugins/data/server/search/routes/bsearch.ts deleted file mode 100644 index 8e471bf0c4c6f..0000000000000 --- a/src/plugins/data/server/search/routes/bsearch.ts +++ /dev/null @@ -1,67 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { firstValueFrom } from 'rxjs'; -import { catchError } from 'rxjs'; -import { BfetchServerSetup } from '@kbn/bfetch-plugin/server'; -import type { ExecutionContextSetup } from '@kbn/core/server'; -import apm from 'elastic-apm-node'; -import type { - IKibanaSearchResponse, - IKibanaSearchRequest, - ISearchOptionsSerializable, -} from '@kbn/search-types'; -import { getRequestAbortedSignal } from '../..'; -import type { ISearchStart } from '../types'; - -export function registerBsearchRoute( - bfetch: BfetchServerSetup, - getScoped: ISearchStart['asScoped'], - executionContextService: ExecutionContextSetup -): void { - bfetch.addBatchProcessingRoute< - { request: IKibanaSearchRequest; options?: ISearchOptionsSerializable }, - IKibanaSearchResponse - >('/internal/bsearch', (request) => { - const search = getScoped(request); - const abortSignal = getRequestAbortedSignal(request.events.aborted$); - return { - /** - * @param requestOptions - * @throws `KibanaServerError` - */ - onBatchItem: async ({ request: requestData, options }) => { - const { executionContext, ...restOptions } = options || {}; - return executionContextService.withContext(executionContext, () => { - apm.addLabels(executionContextService.getAsLabels()); - - return firstValueFrom( - search.search(requestData, { ...restOptions, abortSignal }).pipe( - catchError((err) => { - // Re-throw as object, to get attributes passed to the client - // eslint-disable-next-line no-throw-literal - throw { - message: err.message, - statusCode: err.statusCode, - attributes: err.errBody - ? { - error: err.errBody.error, - rawResponse: err.errBody.response, - ...(err.requestParams ? { requestParams: err.requestParams } : {}), - } - : undefined, - }; - }) - ) - ); - }); - }, - }; - }); -} diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 2ed3a7b170611..156336e47492d 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -93,7 +93,6 @@ import { import { aggShardDelay } from '../../common/search/aggs/buckets/shard_delay_fn'; import { ConfigSchema } from '../config'; import { SearchSessionService } from './session'; -import { registerBsearchRoute } from './routes/bsearch'; import { enhancedEsSearchStrategyProvider } from './strategies/ese_search'; import { eqlSearchStrategyProvider } from './strategies/eql_search'; import { NoSearchIdInSessionError } from './errors/no_search_id_in_session'; @@ -209,12 +208,6 @@ export class SearchService implements Plugin { sqlSearchStrategyProvider(this.initializerContext.config.get().search, this.logger) ); - registerBsearchRoute( - bfetch, - (request: KibanaRequest) => this.asScoped(request), - core.executionContext - ); - core.savedObjects.registerType(searchTelemetry); if (usageCollection) { const getIndexForType = (type: string) => diff --git a/src/plugins/data/tsconfig.json b/src/plugins/data/tsconfig.json index 6a7ffce45e96b..b1f06b761c0fb 100644 --- a/src/plugins/data/tsconfig.json +++ b/src/plugins/data/tsconfig.json @@ -47,7 +47,6 @@ "@kbn/search-errors", "@kbn/search-response-warnings", "@kbn/shared-ux-link-redirect-app", - "@kbn/bfetch-error", "@kbn/es-types", "@kbn/code-editor", "@kbn/core-test-helpers-model-versions", diff --git a/test/api_integration/apis/search/bsearch.ts b/test/api_integration/apis/search/bsearch.ts deleted file mode 100644 index 2c4bcead1d475..0000000000000 --- a/test/api_integration/apis/search/bsearch.ts +++ /dev/null @@ -1,577 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import expect from '@kbn/expect'; -import request from 'superagent'; -import { inflateResponse } from '@kbn/bfetch-plugin/public/streaming'; -import { - ELASTIC_HTTP_VERSION_HEADER, - X_ELASTIC_INTERNAL_ORIGIN_REQUEST, -} from '@kbn/core-http-common'; -import { BFETCH_ROUTE_VERSION_LATEST } from '@kbn/bfetch-plugin/common'; -import { FtrProviderContext } from '../../ftr_provider_context'; -import { painlessErrReq } from './painless_err_req'; -import { verifyErrorResponse } from './verify_error'; - -function parseBfetchResponse(resp: request.Response, compressed: boolean = false) { - return resp.text - .trim() - .split('\n') - .map((item) => { - return JSON.parse(compressed ? inflateResponse(item) : item); - }); -} - -export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - describe('bsearch', () => { - describe('post', () => { - it('should return 200 a single response', async () => { - const resp = await supertest - .post(`/internal/bsearch`) - .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) - .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') - .send({ - batch: [ - { - request: { - params: { - index: '.kibana', - body: { - query: { - match_all: {}, - }, - }, - }, - }, - options: { - strategy: 'es', - }, - }, - ], - }); - - const jsonBody = parseBfetchResponse(resp); - - expect(resp.status).to.be(200); - expect(jsonBody[0].id).to.be(0); - expect(jsonBody[0].result.isPartial).to.be(false); - expect(jsonBody[0].result.isRunning).to.be(false); - expect(jsonBody[0].result).to.have.property('rawResponse'); - }); - - it('should return 200 a single response from compressed', async () => { - const resp = await supertest - .post(`/internal/bsearch?compress=true`) - .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) - .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') - .send({ - batch: [ - { - request: { - params: { - index: '.kibana', - body: { - query: { - match_all: {}, - }, - }, - }, - }, - options: { - strategy: 'es', - }, - }, - ], - }); - - const jsonBody = parseBfetchResponse(resp, true); - - expect(resp.status).to.be(200); - expect(jsonBody[0].id).to.be(0); - expect(jsonBody[0].result.isPartial).to.be(false); - expect(jsonBody[0].result.isRunning).to.be(false); - expect(jsonBody[0].result).to.have.property('rawResponse'); - }); - - it('should return a batch of successful responses', async () => { - const resp = await supertest - .post(`/internal/bsearch`) - .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) - .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') - .send({ - batch: [ - { - request: { - params: { - index: '.kibana', - body: { - query: { - match_all: {}, - }, - }, - }, - }, - }, - { - request: { - params: { - index: '.kibana', - body: { - query: { - match_all: {}, - }, - }, - }, - }, - }, - ], - }); - - expect(resp.status).to.be(200); - const parsedResponse = parseBfetchResponse(resp); - expect(parsedResponse).to.have.length(2); - parsedResponse.forEach((responseJson) => { - expect(responseJson.result).to.have.property('isPartial'); - expect(responseJson.result).to.have.property('isRunning'); - expect(responseJson.result).to.have.property('rawResponse'); - }); - }); - - it('should return error for not found strategy', async () => { - const resp = await supertest - .post(`/internal/bsearch`) - .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) - .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') - .send({ - batch: [ - { - request: { - params: { - index: '.kibana', - body: { - query: { - match_all: {}, - }, - }, - }, - }, - options: { - strategy: 'wtf', - }, - }, - ], - }); - - expect(resp.status).to.be(200); - parseBfetchResponse(resp).forEach((responseJson, i) => { - expect(responseJson.id).to.be(i); - verifyErrorResponse(responseJson.error, 404, 'Search strategy wtf not found'); - }); - }); - - it('should return 400 when index type is provided in "es" strategy', async () => { - const resp = await supertest - .post(`/internal/bsearch`) - .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) - .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') - .send({ - batch: [ - { - request: { - index: '.kibana', - indexType: 'baad', - params: { - body: { - query: { - match_all: {}, - }, - }, - }, - }, - options: { - strategy: 'es', - }, - }, - ], - }); - - expect(resp.status).to.be(200); - parseBfetchResponse(resp).forEach((responseJson, i) => { - expect(responseJson.id).to.be(i); - verifyErrorResponse(responseJson.error, 400, 'Unsupported index pattern type baad'); - }); - }); - - describe('painless', () => { - before(async () => { - await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); - }); - - after(async () => { - await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional'); - }); - it('should return 400 "search_phase_execution_exception" for Painless error in "es" strategy', async () => { - const resp = await supertest - .post(`/internal/bsearch`) - .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) - .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') - .send({ - batch: [ - { - request: painlessErrReq, - options: { - strategy: 'es', - }, - }, - ], - }); - - expect(resp.status).to.be(200); - parseBfetchResponse(resp).forEach((responseJson, i) => { - expect(responseJson.id).to.be(i); - verifyErrorResponse(responseJson.error, 400, 'search_phase_execution_exception', true); - }); - }); - }); - - describe('request meta', () => { - describe('es', () => { - it(`should return request meta`, async () => { - const resp = await supertest - .post(`/internal/bsearch`) - .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) - .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') - .send({ - batch: [ - { - request: { - params: { - index: '.kibana', - body: { - query: { - match_all: {}, - }, - }, - }, - }, - options: { - strategy: 'es', - }, - }, - ], - }); - - const jsonBody = parseBfetchResponse(resp); - - expect(resp.status).to.be(200); - expect(jsonBody[0].result.requestParams).to.eql({ - method: 'POST', - path: '/.kibana/_search', - querystring: 'ignore_unavailable=true', - }); - }); - - it(`should return request meta when request fails`, async () => { - const resp = await supertest - .post(`/internal/bsearch`) - .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) - .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') - .send({ - batch: [ - { - request: { - params: { - index: '.kibana', - body: { - query: { - bool: { - filter: [ - { - error_query: { - indices: [ - { - error_type: 'exception', - message: 'simulated failure', - name: '.kibana', - }, - ], - }, - }, - ], - }, - }, - }, - }, - }, - options: { - strategy: 'es', - }, - }, - ], - }); - - const jsonBody = parseBfetchResponse(resp); - - expect(resp.status).to.be(200); - expect(jsonBody[0].error.attributes.requestParams).to.eql({ - method: 'POST', - path: '/.kibana/_search', - querystring: 'ignore_unavailable=true', - }); - }); - }); - - describe('ese', () => { - it(`should return request meta`, async () => { - const resp = await supertest - .post(`/internal/bsearch`) - .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) - .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') - .send({ - batch: [ - { - request: { - params: { - index: '.kibana', - body: { - query: { - match_all: {}, - }, - }, - }, - }, - options: { - strategy: 'ese', - }, - }, - ], - }); - - const jsonBody = parseBfetchResponse(resp); - - expect(resp.status).to.be(200); - expect(jsonBody[0].result.requestParams).to.eql({ - method: 'POST', - path: '/.kibana/_async_search', - querystring: - 'batched_reduce_size=64&ccs_minimize_roundtrips=true&wait_for_completion_timeout=200ms&keep_on_completion=false&keep_alive=60000ms&ignore_unavailable=true', - }); - }); - - it(`should return request meta when request fails`, async () => { - const resp = await supertest - .post(`/internal/bsearch`) - .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) - .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') - .send({ - batch: [ - { - request: { - params: { - index: '.kibana', - body: { - bool: { - filter: [ - { - error_query: { - indices: [ - { - error_type: 'exception', - message: 'simulated failure', - name: '.kibana', - }, - ], - }, - }, - ], - }, - }, - }, - }, - options: { - strategy: 'ese', - }, - }, - ], - }); - - const jsonBody = parseBfetchResponse(resp); - - expect(resp.status).to.be(200); - expect(jsonBody[0].error.attributes.requestParams).to.eql({ - method: 'POST', - path: '/.kibana/_async_search', - querystring: - 'batched_reduce_size=64&ccs_minimize_roundtrips=true&wait_for_completion_timeout=200ms&keep_on_completion=false&keep_alive=60000ms&ignore_unavailable=true', - }); - }); - }); - - describe('esql', () => { - it(`should return request meta`, async () => { - const resp = await supertest - .post(`/internal/bsearch`) - .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) - .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') - .send({ - batch: [ - { - request: { - params: { - query: 'from .kibana | limit 1', - }, - }, - options: { - strategy: 'esql', - }, - }, - ], - }); - - const jsonBody = parseBfetchResponse(resp); - - expect(resp.status).to.be(200); - expect(jsonBody[0].result.requestParams).to.eql({ - method: 'POST', - path: '/_query', - }); - }); - - it(`should return request meta when request fails`, async () => { - const resp = await supertest - .post(`/internal/bsearch`) - .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) - .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') - .send({ - batch: [ - { - request: { - params: { - query: 'fro .kibana | limit 1', - }, - }, - options: { - strategy: 'esql', - }, - }, - ], - }); - - const jsonBody = parseBfetchResponse(resp); - - expect(resp.status).to.be(200); - expect(jsonBody[0].error.attributes.requestParams).to.eql({ - method: 'POST', - path: '/_query', - }); - }); - }); - - describe('sql', () => { - it(`should return request meta`, async () => { - const resp = await supertest - .post(`/internal/bsearch`) - .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) - .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') - .send({ - batch: [ - { - request: { - params: { - query: 'SELECT * FROM ".kibana" LIMIT 1', - }, - }, - options: { - strategy: 'sql', - }, - }, - ], - }); - - const jsonBody = parseBfetchResponse(resp); - - expect(resp.status).to.be(200); - expect(jsonBody[0].result.requestParams).to.eql({ - method: 'POST', - path: '/_sql', - querystring: 'format=json', - }); - }); - - it(`should return request meta when request fails`, async () => { - const resp = await supertest - .post(`/internal/bsearch`) - .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) - .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') - .send({ - batch: [ - { - request: { - params: { - query: 'SELEC * FROM ".kibana" LIMIT 1', - }, - }, - options: { - strategy: 'sql', - }, - }, - ], - }); - - const jsonBody = parseBfetchResponse(resp); - - expect(resp.status).to.be(200); - expect(jsonBody[0].error.attributes.requestParams).to.eql({ - method: 'POST', - path: '/_sql', - querystring: 'format=json', - }); - }); - }); - - describe('eql', () => { - it(`should return request meta`, async () => { - const resp = await supertest - .post(`/internal/bsearch`) - .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) - .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') - .send({ - batch: [ - { - request: { - params: { - index: '.kibana', - query: 'any where true', - timestamp_field: 'created_at', - }, - }, - options: { - strategy: 'eql', - }, - }, - ], - }); - - const jsonBody = parseBfetchResponse(resp); - - expect(resp.status).to.be(200); - expect(jsonBody[0].result.requestParams).to.eql({ - method: 'POST', - path: '/.kibana/_eql/search', - querystring: 'ignore_unavailable=true', - }); - }); - }); - }); - }); - }); -} diff --git a/test/api_integration/apis/search/index.ts b/test/api_integration/apis/search/index.ts index 1ef0efee92d42..3d4b77634adf0 100644 --- a/test/api_integration/apis/search/index.ts +++ b/test/api_integration/apis/search/index.ts @@ -13,6 +13,5 @@ export default function ({ loadTestFile }: FtrProviderContext) { describe('search', () => { loadTestFile(require.resolve('./search')); loadTestFile(require.resolve('./sql_search')); - loadTestFile(require.resolve('./bsearch')); }); } diff --git a/test/tsconfig.json b/test/tsconfig.json index d7e0de39d5e5e..1ba594b8ecbdb 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -23,7 +23,6 @@ "kbn_references": [ "@kbn/core", { "path": "../src/setup_node_env/tsconfig.json" }, - "@kbn/bfetch-plugin", "@kbn/dashboard-plugin", "@kbn/expressions-plugin", "@kbn/saved-objects-management-plugin", diff --git a/x-pack/test/common/services/search_secure.ts b/x-pack/test/common/services/search_secure.ts index 9f25140df5d23..46eb8c765cd94 100644 --- a/x-pack/test/common/services/search_secure.ts +++ b/x-pack/test/common/services/search_secure.ts @@ -11,7 +11,6 @@ import expect from '@kbn/expect'; import type { IEsSearchResponse } from '@kbn/search-types'; import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; -import { BFETCH_ROUTE_VERSION_LATEST } from '@kbn/bfetch-plugin/common'; import { SupertestWithoutAuthProviderType } from '@kbn/ftr-common-functional-services'; import { FtrService } from '../ftr_provider_context'; @@ -109,7 +108,7 @@ export class SearchSecureService extends FtrService { .auth(auth.username, auth.password) .set('kbn-xsrf', 'true') .set('x-elastic-internal-origin', 'Kibana') - .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') .send(options) .expect(200); expect(resp.body.isRunning).equal(false); diff --git a/x-pack/test_serverless/api_integration/test_suites/common/search_oss/bsearch.ts b/x-pack/test_serverless/api_integration/test_suites/common/search_oss/bsearch.ts deleted file mode 100644 index 93da25aed6000..0000000000000 --- a/x-pack/test_serverless/api_integration/test_suites/common/search_oss/bsearch.ts +++ /dev/null @@ -1,247 +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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import request from 'superagent'; -import { inflateResponse } from '@kbn/bfetch-plugin/public/streaming'; -import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; -import { BFETCH_ROUTE_VERSION_LATEST } from '@kbn/bfetch-plugin/common'; -import { SupertestWithRoleScopeType } from '@kbn/test-suites-xpack/api_integration/deployment_agnostic/services'; -import type { FtrProviderContext } from '../../../ftr_provider_context'; -import { painlessErrReq } from './painless_err_req'; -import { verifyErrorResponse } from './verify_error'; - -function parseBfetchResponse(resp: request.Response, compressed: boolean = false) { - return resp.text - .trim() - .split('\n') - .map((item) => { - return JSON.parse(compressed ? inflateResponse(item) : item); - }); -} - -export default function ({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const roleScopedSupertest = getService('roleScopedSupertest'); - let supertestAdminWithCookieCredentials: SupertestWithRoleScopeType; - - describe('bsearch', () => { - before(async () => { - supertestAdminWithCookieCredentials = await roleScopedSupertest.getSupertestWithRoleScope( - 'admin', - { - useCookieHeader: true, - withInternalHeaders: true, - withCustomHeaders: { - [ELASTIC_HTTP_VERSION_HEADER]: BFETCH_ROUTE_VERSION_LATEST, - }, - } - ); - }); - - describe('post', () => { - it('should return 200 a single response', async () => { - const resp = await supertestAdminWithCookieCredentials.post(`/internal/bsearch`).send({ - batch: [ - { - request: { - params: { - index: '.kibana', - body: { - query: { - match_all: {}, - }, - }, - }, - }, - options: { - strategy: 'es', - }, - }, - ], - }); - - const jsonBody = parseBfetchResponse(resp); - - expect(resp.status).to.be(200); - expect(jsonBody[0].id).to.be(0); - expect(jsonBody[0].result.isPartial).to.be(false); - expect(jsonBody[0].result.isRunning).to.be(false); - expect(jsonBody[0].result).to.have.property('rawResponse'); - }); - - it('should return 200 a single response from compressed', async () => { - const resp = await supertestAdminWithCookieCredentials - .post(`/internal/bsearch?compress=true`) - .send({ - batch: [ - { - request: { - params: { - index: '.kibana', - body: { - query: { - match_all: {}, - }, - }, - }, - }, - options: { - strategy: 'es', - }, - }, - ], - }); - - const jsonBody = parseBfetchResponse(resp, true); - - expect(resp.status).to.be(200); - expect(jsonBody[0].id).to.be(0); - expect(jsonBody[0].result.isPartial).to.be(false); - expect(jsonBody[0].result.isRunning).to.be(false); - expect(jsonBody[0].result).to.have.property('rawResponse'); - }); - - it('should return a batch of successful responses', async () => { - const resp = await supertestAdminWithCookieCredentials - .post(`/internal/bsearch`) - .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) - .send({ - batch: [ - { - request: { - params: { - index: '.kibana', - body: { - query: { - match_all: {}, - }, - }, - }, - }, - }, - { - request: { - params: { - index: '.kibana', - body: { - query: { - match_all: {}, - }, - }, - }, - }, - }, - ], - }); - - expect(resp.status).to.be(200); - const parsedResponse = parseBfetchResponse(resp); - expect(parsedResponse).to.have.length(2); - parsedResponse.forEach((responseJson) => { - expect(responseJson.result).to.have.property('isPartial'); - expect(responseJson.result).to.have.property('isRunning'); - expect(responseJson.result).to.have.property('rawResponse'); - }); - }); - - it('should return error for not found strategy', async () => { - const resp = await supertestAdminWithCookieCredentials - .post(`/internal/bsearch`) - .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) - .send({ - batch: [ - { - request: { - params: { - index: '.kibana', - body: { - query: { - match_all: {}, - }, - }, - }, - }, - options: { - strategy: 'wtf', - }, - }, - ], - }); - - expect(resp.status).to.be(200); - parseBfetchResponse(resp).forEach((responseJson, i) => { - expect(responseJson.id).to.be(i); - verifyErrorResponse(responseJson.error, 404, 'Search strategy wtf not found'); - }); - }); - - it('should return 400 when index type is provided in "es" strategy', async () => { - const resp = await supertestAdminWithCookieCredentials - .post(`/internal/bsearch`) - .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) - .send({ - batch: [ - { - request: { - index: '.kibana', - indexType: 'baad', - params: { - body: { - query: { - match_all: {}, - }, - }, - }, - }, - options: { - strategy: 'es', - }, - }, - ], - }); - - expect(resp.status).to.be(200); - parseBfetchResponse(resp).forEach((responseJson, i) => { - expect(responseJson.id).to.be(i); - verifyErrorResponse(responseJson.error, 400, 'Unsupported index pattern type baad'); - }); - }); - - describe('painless', () => { - before(async () => { - await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); - }); - - after(async () => { - await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional'); - }); - it('should return 400 "search_phase_execution_exception" for Painless error in "es" strategy', async () => { - const resp = await supertestAdminWithCookieCredentials - .post(`/internal/bsearch`) - .set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST) - .send({ - batch: [ - { - request: painlessErrReq, - options: { - strategy: 'es', - }, - }, - ], - }); - - expect(resp.status).to.be(200); - parseBfetchResponse(resp).forEach((responseJson, i) => { - expect(responseJson.id).to.be(i); - verifyErrorResponse(responseJson.error, 400, 'search_phase_execution_exception', true); - }); - }); - }); - }); - }); -} diff --git a/x-pack/test_serverless/api_integration/test_suites/common/search_oss/index.ts b/x-pack/test_serverless/api_integration/test_suites/common/search_oss/index.ts index 79ff29fdf9f22..50299ed81a444 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/search_oss/index.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/search_oss/index.ts @@ -16,6 +16,5 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./search')); // TODO: Removed `sql_search` since // SQL is not supported in Serverless - loadTestFile(require.resolve('./bsearch')); }); } diff --git a/x-pack/test_serverless/tsconfig.json b/x-pack/test_serverless/tsconfig.json index f54ffc1bb4c28..452b6b0260fbf 100644 --- a/x-pack/test_serverless/tsconfig.json +++ b/x-pack/test_serverless/tsconfig.json @@ -50,7 +50,6 @@ "@kbn/data-view-field-editor-plugin", "@kbn/data-plugin", "@kbn/dev-utils", - "@kbn/bfetch-plugin", "@kbn/es-archiver", "@kbn/rule-data-utils", "@kbn/rison",