diff --git a/src/plugins/console/common/constants/api.ts b/src/plugins/console/common/constants/api.ts index aa0fad1fe442..3c5aa1151935 100644 --- a/src/plugins/console/common/constants/api.ts +++ b/src/plugins/console/common/constants/api.ts @@ -7,3 +7,4 @@ */ export const API_BASE_PATH = '/api/console'; +export const KIBANA_API_PREFIX = 'kbn:'; diff --git a/src/plugins/console/common/constants/index.ts b/src/plugins/console/common/constants/index.ts index d8768af8fc8d..756a79883cbd 100644 --- a/src/plugins/console/common/constants/index.ts +++ b/src/plugins/console/common/constants/index.ts @@ -7,4 +7,4 @@ */ export { MAJOR_VERSION } from './plugin'; -export { API_BASE_PATH } from './api'; +export { API_BASE_PATH, KIBANA_API_PREFIX } from './api'; diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.test.mock.tsx b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.test.mock.tsx index dfed86a64362..b410e240151d 100644 --- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.test.mock.tsx +++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.test.mock.tsx @@ -39,8 +39,8 @@ jest.mock('../../../../models/sense_editor', () => { }; }); -jest.mock('../../../../hooks/use_send_current_request_to_es/send_request_to_es', () => ({ - sendRequestToES: jest.fn(), +jest.mock('../../../../hooks/use_send_current_request/send_request', () => ({ + sendRequest: jest.fn(), })); jest.mock('../../../../../lib/autocomplete/get_endpoint_from_position', () => ({ getEndpointFromPosition: jest.fn(), diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.test.tsx b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.test.tsx index b942a6d83021..ba5f1e78d5f0 100644 --- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.test.tsx +++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.test.tsx @@ -25,7 +25,7 @@ import { } from '../../../../contexts'; // Mocked functions -import { sendRequestToES } from '../../../../hooks/use_send_current_request_to_es/send_request_to_es'; +import { sendRequest } from '../../../../hooks/use_send_current_request/send_request'; import { getEndpointFromPosition } from '../../../../../lib/autocomplete/get_endpoint_from_position'; import type { DevToolsSettings } from '../../../../../services'; import * as consoleMenuActions from '../console_menu_actions'; @@ -58,15 +58,15 @@ describe('Legacy (Ace) Console Editor Component Smoke Test', () => { sandbox.restore(); }); - it('calls send current request to ES', async () => { + it('calls send current request', async () => { (getEndpointFromPosition as jest.Mock).mockReturnValue({ patterns: [] }); - (sendRequestToES as jest.Mock).mockRejectedValue({}); + (sendRequest as jest.Mock).mockRejectedValue({}); const editor = doMount(); act(() => { editor.find('button[data-test-subj~="sendRequestButton"]').simulate('click'); }); await nextTick(); - expect(sendRequestToES).toBeCalledTimes(1); + expect(sendRequest).toBeCalledTimes(1); }); it('opens docs', () => { diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx index bafe9ee6ca15..d01a40bdd44b 100644 --- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx +++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx @@ -26,7 +26,7 @@ import { ConsoleMenu } from '../../../../components'; import { useEditorReadContext, useServicesContext } from '../../../../contexts'; import { useSaveCurrentTextObject, - useSendCurrentRequestToES, + useSendCurrentRequest, useSetInputEditor, } from '../../../../hooks'; import * as senseEditor from '../../../../models/sense_editor'; @@ -72,7 +72,7 @@ function EditorUI({ initialTextValue, setEditorInstance }: EditorProps) { const { settings } = useEditorReadContext(); const setInputEditor = useSetInputEditor(); - const sendCurrentRequestToES = useSendCurrentRequestToES(); + const sendCurrentRequest = useSendCurrentRequest(); const saveCurrentTextObject = useSaveCurrentTextObject(); const editorRef = useRef(null); @@ -231,11 +231,11 @@ function EditorUI({ initialTextValue, setEditorInstance }: EditorProps) { if (!isKeyboardShortcutsDisabled) { registerCommands({ senseEditor: editorInstanceRef.current!, - sendCurrentRequestToES, + sendCurrentRequest, openDocumentation, }); } - }, [sendCurrentRequestToES, openDocumentation, settings]); + }, [openDocumentation, settings, sendCurrentRequest]); useEffect(() => { const { current: editor } = editorInstanceRef; @@ -262,7 +262,7 @@ function EditorUI({ initialTextValue, setEditorInstance }: EditorProps) { > void; + sendCurrentRequest: () => void; openDocumentation: () => void; } @@ -24,11 +24,7 @@ const COMMANDS = { GO_TO_LINE: 'gotoline', }; -export function registerCommands({ - senseEditor, - sendCurrentRequestToES, - openDocumentation, -}: Actions) { +export function registerCommands({ senseEditor, sendCurrentRequest, openDocumentation }: Actions) { const throttledAutoIndent = throttle(() => senseEditor.autoIndent(), 500, { leading: true, trailing: true, @@ -39,7 +35,7 @@ export function registerCommands({ keys: { win: 'Ctrl-Enter', mac: 'Command-Enter' }, name: COMMANDS.SEND_TO_ELASTICSEARCH, fn: () => { - sendCurrentRequestToES(); + sendCurrentRequest(); }, }); diff --git a/src/plugins/console/public/application/hooks/index.ts b/src/plugins/console/public/application/hooks/index.ts index 1a9b4e5c472b..1996330bef66 100644 --- a/src/plugins/console/public/application/hooks/index.ts +++ b/src/plugins/console/public/application/hooks/index.ts @@ -8,6 +8,6 @@ export { useSetInputEditor } from './use_set_input_editor'; export { useRestoreRequestFromHistory } from './use_restore_request_from_history'; -export { useSendCurrentRequestToES } from './use_send_current_request_to_es'; +export { useSendCurrentRequest } from './use_send_current_request'; export { useSaveCurrentTextObject } from './use_save_current_text_object'; export { useDataInit } from './use_data_init'; diff --git a/src/plugins/console/public/application/hooks/use_send_current_request_to_es/index.ts b/src/plugins/console/public/application/hooks/use_send_current_request/index.ts similarity index 81% rename from src/plugins/console/public/application/hooks/use_send_current_request_to_es/index.ts rename to src/plugins/console/public/application/hooks/use_send_current_request/index.ts index df2431f1f6f4..33bdbef87f2e 100644 --- a/src/plugins/console/public/application/hooks/use_send_current_request_to_es/index.ts +++ b/src/plugins/console/public/application/hooks/use_send_current_request/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { useSendCurrentRequestToES } from './use_send_current_request_to_es'; +export { useSendCurrentRequest } from './use_send_current_request'; diff --git a/src/plugins/console/public/application/hooks/use_send_current_request_to_es/send_request_to_es.test.ts b/src/plugins/console/public/application/hooks/use_send_current_request/send_request.test.ts similarity index 66% rename from src/plugins/console/public/application/hooks/use_send_current_request_to_es/send_request_to_es.test.ts rename to src/plugins/console/public/application/hooks/use_send_current_request/send_request.test.ts index 8578e271f37b..60ced085c689 100644 --- a/src/plugins/console/public/application/hooks/use_send_current_request_to_es/send_request_to_es.test.ts +++ b/src/plugins/console/public/application/hooks/use_send_current_request/send_request.test.ts @@ -8,14 +8,14 @@ import type { ContextValue } from '../../contexts'; -jest.mock('./send_request_to_es', () => ({ sendRequestToES: jest.fn(() => Promise.resolve()) })); +jest.mock('./send_request', () => ({ sendRequest: jest.fn(() => Promise.resolve()) })); -import { sendRequestToES } from './send_request_to_es'; +import { sendRequest } from './send_request'; import { serviceContextMock } from '../../contexts/services_context.mock'; -const mockedSendRequestToES = sendRequestToES as jest.Mock; +const mockedSendRequest = sendRequest as jest.Mock; -describe('sendRequestToES', () => { +describe('sendRequest', () => { let mockContextValue: ContextValue; beforeEach(() => { @@ -26,8 +26,8 @@ describe('sendRequestToES', () => { jest.resetAllMocks(); }); - it('should send request to ES', async () => { - mockedSendRequestToES.mockResolvedValue([ + it('should send request', async () => { + mockedSendRequest.mockResolvedValue([ { response: { statusCode: 200, @@ -40,17 +40,17 @@ describe('sendRequestToES', () => { http: mockContextValue.services.http, requests: [{ method: 'PUT', url: 'test', data: [] }], }; - const results = await sendRequestToES(args); + const results = await sendRequest(args); const [request] = results; expect(request.response.statusCode).toEqual(200); expect(request.response.value).toContain('"acknowledged": true'); - expect(mockedSendRequestToES).toHaveBeenCalledWith(args); - expect(mockedSendRequestToES).toHaveBeenCalledTimes(1); + expect(mockedSendRequest).toHaveBeenCalledWith(args); + expect(mockedSendRequest).toHaveBeenCalledTimes(1); }); - it('should send multiple requests to ES', async () => { - mockedSendRequestToES.mockResolvedValue([ + it('should send multiple requests', async () => { + mockedSendRequest.mockResolvedValue([ { response: { statusCode: 200, @@ -70,17 +70,17 @@ describe('sendRequestToES', () => { { method: 'GET', url: 'test-2', data: [] }, ], }; - const results = await sendRequestToES(args); + const results = await sendRequest(args); const [firstRequest, secondRequest] = results; expect(firstRequest.response.statusCode).toEqual(200); expect(secondRequest.response.statusCode).toEqual(200); - expect(mockedSendRequestToES).toHaveBeenCalledWith(args); - expect(mockedSendRequestToES).toHaveBeenCalledTimes(1); + expect(mockedSendRequest).toHaveBeenCalledWith(args); + expect(mockedSendRequest).toHaveBeenCalledTimes(1); }); it('should handle errors', async () => { - mockedSendRequestToES.mockRejectedValue({ + mockedSendRequest.mockRejectedValue({ response: { statusCode: 500, statusText: 'error', @@ -88,45 +88,46 @@ describe('sendRequestToES', () => { }); try { - await sendRequestToES({ + await sendRequest({ http: mockContextValue.services.http, requests: [{ method: 'GET', url: 'test', data: [] }], }); } catch (error) { expect(error.response.statusCode).toEqual(500); expect(error.response.statusText).toEqual('error'); - expect(mockedSendRequestToES).toHaveBeenCalledTimes(1); + expect(mockedSendRequest).toHaveBeenCalledTimes(1); } }); + describe('successful response value', () => { describe('with text', () => { it('should return value with lines separated', async () => { - mockedSendRequestToES.mockResolvedValue('\ntest_index-1 [] \ntest_index-2 []\n'); - const response = await sendRequestToES({ + mockedSendRequest.mockResolvedValue('\ntest_index-1 []\ntest_index-2 []\n'); + const response = await sendRequest({ http: mockContextValue.services.http, requests: [{ method: 'GET', url: 'test-1', data: [] }], }); expect(response).toMatchInlineSnapshot(` " - test_index-1 [] + test_index-1 [] test_index-2 [] " `); - expect(mockedSendRequestToES).toHaveBeenCalledTimes(1); + expect(mockedSendRequest).toHaveBeenCalledTimes(1); }); }); describe('with parsed json', () => { it('should stringify value', async () => { - mockedSendRequestToES.mockResolvedValue(JSON.stringify({ test: 'some value' })); - const response = await sendRequestToES({ + mockedSendRequest.mockResolvedValue(JSON.stringify({ test: 'some value' })); + const response = await sendRequest({ http: mockContextValue.services.http, requests: [{ method: 'GET', url: 'test-2', data: [] }], }); expect(typeof response).toBe('string'); - expect(mockedSendRequestToES).toHaveBeenCalledTimes(1); + expect(mockedSendRequest).toHaveBeenCalledTimes(1); }); }); }); diff --git a/src/plugins/console/public/application/hooks/use_send_current_request_to_es/send_request_to_es.ts b/src/plugins/console/public/application/hooks/use_send_current_request/send_request.ts similarity index 69% rename from src/plugins/console/public/application/hooks/use_send_current_request_to_es/send_request_to_es.ts rename to src/plugins/console/public/application/hooks/use_send_current_request/send_request.ts index 451198aaf2d2..1247f3f78aa6 100644 --- a/src/plugins/console/public/application/hooks/use_send_current_request_to_es/send_request_to_es.ts +++ b/src/plugins/console/public/application/hooks/use_send_current_request/send_request.ts @@ -15,12 +15,12 @@ import { BaseResponseType } from '../../../types'; const { collapseLiteralStrings } = XJson; -export interface EsRequestArgs { +export interface RequestArgs { http: HttpSetup; requests: Array<{ url: string; method: string; data: string[] }>; } -export interface ESResponseObject { +export interface ResponseObject { statusCode: number; statusText: string; timeMs: number; @@ -28,17 +28,17 @@ export interface ESResponseObject { value: V; } -export interface ESRequestResult { +export interface RequestResult { request: { data: string; method: string; path: string }; - response: ESResponseObject; + response: ResponseObject; } let CURRENT_REQ_ID = 0; -export function sendRequestToES(args: EsRequestArgs): Promise { +export function sendRequest(args: RequestArgs): Promise { const requests = args.requests.slice(); return new Promise((resolve, reject) => { const reqId = ++CURRENT_REQ_ID; - const results: ESRequestResult[] = []; + const results: RequestResult[] = []; if (reqId !== CURRENT_REQ_ID) { return; } @@ -59,11 +59,11 @@ export function sendRequestToES(args: EsRequestArgs): Promise return; } const req = requests.shift()!; - const esPath = req.url; - const esMethod = req.method; - let esData = collapseLiteralStrings(req.data.join('\n')); - if (esData) { - esData += '\n'; + const path = req.url; + const method = req.method; + let data = collapseLiteralStrings(req.data.join('\n')); + if (data) { + data += '\n'; } // append a new line for bulk requests. const startTime = Date.now(); @@ -71,9 +71,9 @@ export function sendRequestToES(args: EsRequestArgs): Promise try { const { response, body } = await es.send({ http: args.http, - method: esMethod, - path: esPath, - data: esData, + method, + path, + data, asResponse: true, }); @@ -115,9 +115,9 @@ export function sendRequestToES(args: EsRequestArgs): Promise value, }, request: { - data: esData, - method: esMethod, - path: esPath, + data, + method, + path, }, }); @@ -127,25 +127,19 @@ export function sendRequestToES(args: EsRequestArgs): Promise } } catch (error) { let value; - let contentType: string | null = ''; + const { response, body } = error as IHttpFetchError; + const contentType = response?.headers.get('Content-Type') ?? ''; + const statusCode = response?.status ?? 500; + const statusText = error?.response?.statusText ?? 'error'; - const { response, body = {} } = error as IHttpFetchError; - if (response) { - const { status, headers } = response; - if (body) { - value = JSON.stringify(body, null, 2); // ES error should be shown - contentType = headers.get('Content-Type'); - } else { - value = 'Request failed to get to the server (status code: ' + status + ')'; - contentType = headers.get('Content-Type'); - } - - if (isMultiRequest) { - value = '# ' + req.method + ' ' + req.url + '\n' + value; - } + if (body) { + value = JSON.stringify(body, null, 2); } else { - value = - "\n\nFailed to connect to Console's backend.\nPlease check the Kibana server is up and running"; + value = 'Request failed to get to the server (status code: ' + statusCode + ')'; + } + + if (isMultiRequest) { + value = '# ' + req.method + ' ' + req.url + '\n' + value; } reject({ @@ -153,13 +147,13 @@ export function sendRequestToES(args: EsRequestArgs): Promise value, contentType, timeMs: Date.now() - startTime, - statusCode: error?.response?.status ?? 500, - statusText: error?.response?.statusText ?? 'error', + statusCode, + statusText, }, request: { - data: esData, - method: esMethod, - path: esPath, + data, + method, + path, }, }); } diff --git a/src/plugins/console/public/application/hooks/use_send_current_request_to_es/track.ts b/src/plugins/console/public/application/hooks/use_send_current_request/track.ts similarity index 100% rename from src/plugins/console/public/application/hooks/use_send_current_request_to_es/track.ts rename to src/plugins/console/public/application/hooks/use_send_current_request/track.ts diff --git a/src/plugins/console/public/application/hooks/use_send_current_request_to_es/use_send_current_request_to_es.test.tsx b/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.test.tsx similarity index 80% rename from src/plugins/console/public/application/hooks/use_send_current_request_to_es/use_send_current_request_to_es.test.tsx rename to src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.test.tsx index e0131dc116a3..d16dc3f832d3 100644 --- a/src/plugins/console/public/application/hooks/use_send_current_request_to_es/use_send_current_request_to_es.test.tsx +++ b/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.test.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -jest.mock('./send_request_to_es', () => ({ sendRequestToES: jest.fn() })); +jest.mock('./send_request', () => ({ sendRequest: jest.fn() })); jest.mock('../../contexts/editor_context/editor_registry', () => ({ instance: { getInputEditor: jest.fn() }, })); @@ -21,10 +21,10 @@ import { serviceContextMock } from '../../contexts/services_context.mock'; import { useRequestActionContext } from '../../contexts/request_context'; import { instance as editorRegistry } from '../../contexts/editor_context/editor_registry'; -import { sendRequestToES } from './send_request_to_es'; -import { useSendCurrentRequestToES } from './use_send_current_request_to_es'; +import { sendRequest } from './send_request'; +import { useSendCurrentRequest } from './use_send_current_request'; -describe('useSendCurrentRequestToES', () => { +describe('useSendCurrentRequest', () => { let mockContextValue: ContextValue; let dispatch: (...args: unknown[]) => void; const contexts = ({ children }: { children: JSX.Element }) => ( @@ -41,18 +41,18 @@ describe('useSendCurrentRequestToES', () => { jest.resetAllMocks(); }); - it('calls send request to ES', async () => { + it('calls send request', async () => { // Set up mocks (mockContextValue.services.settings.toJSON as jest.Mock).mockReturnValue({}); // This request should succeed - (sendRequestToES as jest.Mock).mockResolvedValue([]); + (sendRequest as jest.Mock).mockResolvedValue([]); (editorRegistry.getInputEditor as jest.Mock).mockImplementation(() => ({ getRequestsInRange: () => ['test'], })); - const { result } = renderHook(() => useSendCurrentRequestToES(), { wrapper: contexts }); + const { result } = renderHook(() => useSendCurrentRequest(), { wrapper: contexts }); await act(() => result.current()); - expect(sendRequestToES).toHaveBeenCalledWith({ + expect(sendRequest).toHaveBeenCalledWith({ http: mockContextValue.services.http, requests: ['test'], }); @@ -64,12 +64,12 @@ describe('useSendCurrentRequestToES', () => { it('handles known errors', async () => { // Set up mocks - (sendRequestToES as jest.Mock).mockRejectedValue({ response: 'nada' }); + (sendRequest as jest.Mock).mockRejectedValue({ response: 'nada' }); (editorRegistry.getInputEditor as jest.Mock).mockImplementation(() => ({ getRequestsInRange: () => ['test'], })); - const { result } = renderHook(() => useSendCurrentRequestToES(), { wrapper: contexts }); + const { result } = renderHook(() => useSendCurrentRequest(), { wrapper: contexts }); await act(() => result.current()); // Second call should be the request failure const [, [requestFailedCall]] = (dispatch as jest.Mock).mock.calls; @@ -80,12 +80,12 @@ describe('useSendCurrentRequestToES', () => { it('handles unknown errors', async () => { // Set up mocks - (sendRequestToES as jest.Mock).mockRejectedValue(NaN /* unexpected error value */); + (sendRequest as jest.Mock).mockRejectedValue(NaN /* unexpected error value */); (editorRegistry.getInputEditor as jest.Mock).mockImplementation(() => ({ getRequestsInRange: () => ['test'], })); - const { result } = renderHook(() => useSendCurrentRequestToES(), { wrapper: contexts }); + const { result } = renderHook(() => useSendCurrentRequest(), { wrapper: contexts }); await act(() => result.current()); // Second call should be the request failure const [, [requestFailedCall]] = (dispatch as jest.Mock).mock.calls; @@ -100,7 +100,7 @@ describe('useSendCurrentRequestToES', () => { it('notifies the user about save to history errors once only', async () => { // Set up mocks - (sendRequestToES as jest.Mock).mockReturnValue( + (sendRequest as jest.Mock).mockReturnValue( [{ request: {} }, { request: {} }] /* two responses to save history */ ); (mockContextValue.services.settings.toJSON as jest.Mock).mockReturnValue({}); @@ -112,7 +112,7 @@ describe('useSendCurrentRequestToES', () => { getRequestsInRange: () => ['test', 'test'], })); - const { result } = renderHook(() => useSendCurrentRequestToES(), { wrapper: contexts }); + const { result } = renderHook(() => useSendCurrentRequest(), { wrapper: contexts }); await act(() => result.current()); expect(dispatch).toHaveBeenCalledTimes(2); diff --git a/src/plugins/console/public/application/hooks/use_send_current_request_to_es/use_send_current_request_to_es.ts b/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.ts similarity index 96% rename from src/plugins/console/public/application/hooks/use_send_current_request_to_es/use_send_current_request_to_es.ts rename to src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.ts index e7c436c9806b..ed08304d8d66 100644 --- a/src/plugins/console/public/application/hooks/use_send_current_request_to_es/use_send_current_request_to_es.ts +++ b/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.ts @@ -16,10 +16,10 @@ import { retrieveAutoCompleteInfo } from '../../../lib/mappings/mappings'; import { instance as registry } from '../../contexts/editor_context/editor_registry'; import { useRequestActionContext, useServicesContext } from '../../contexts'; import { StorageQuotaError } from '../../components/storage_quota_error'; -import { sendRequestToES } from './send_request_to_es'; +import { sendRequest } from './send_request'; import { track } from './track'; -export const useSendCurrentRequestToES = () => { +export const useSendCurrentRequest = () => { const { services: { history, settings, notifications, trackUiMetric, http }, theme$, @@ -46,7 +46,7 @@ export const useSendCurrentRequestToES = () => { // Fire and forget setTimeout(() => track(requests, editor, trackUiMetric), 0); - const results = await sendRequestToES({ http, requests }); + const results = await sendRequest({ http, requests }); let saveToHistoryError: undefined | Error; const { isHistoryDisabled } = settings.toJSON(); diff --git a/src/plugins/console/public/application/models/sense_editor/sense_editor.test.js b/src/plugins/console/public/application/models/sense_editor/sense_editor.test.js index 0889b98c6938..ff9d245f6127 100644 --- a/src/plugins/console/public/application/models/sense_editor/sense_editor.test.js +++ b/src/plugins/console/public/application/models/sense_editor/sense_editor.test.js @@ -455,11 +455,11 @@ describe('Editor', () => { editorInput1, { start: { lineNumber: 7 }, end: { lineNumber: 14 } }, ` -curl -XGET "http://localhost:9200/_stats?level=shards" +curl -XGET "http://localhost:9200/_stats?level=shards" -H "kbn-xsrf: reporting" #in between comment -curl -XPUT "http://localhost:9200/index_1/type1/1" -H 'Content-Type: application/json' -d' +curl -XPUT "http://localhost:9200/index_1/type1/1" -H "kbn-xsrf: reporting" -H "Content-Type: application/json" -d' { "f": 1 }'`.trim() @@ -470,7 +470,7 @@ curl -XPUT "http://localhost:9200/index_1/type1/1" -H 'Content-Type: application editorInput1, { start: { lineNumber: 29 }, end: { lineNumber: 33 } }, ` -curl -XPOST "http://localhost:9200/_sql?format=txt" -H 'Content-Type: application/json' -d' +curl -XPOST "http://localhost:9200/_sql?format=txt" -H "kbn-xsrf: reporting" -H "Content-Type: application/json" -d' { "query": "SELECT prenom FROM claude_index WHERE prenom = '\\''claude'\\'' ", "fetch_size": 1 diff --git a/src/plugins/console/public/application/models/sense_editor/sense_editor.ts b/src/plugins/console/public/application/models/sense_editor/sense_editor.ts index 5e8ca35f287b..ac65afce2c18 100644 --- a/src/plugins/console/public/application/models/sense_editor/sense_editor.ts +++ b/src/plugins/console/public/application/models/sense_editor/sense_editor.ts @@ -14,7 +14,7 @@ import RowParser from '../../../lib/row_parser'; import * as utils from '../../../lib/utils'; // @ts-ignore -import * as es from '../../../lib/es/es'; +import { constructUrl } from '../../../lib/es/es'; import { CoreEditor, Position, Range } from '../../../types'; import { createTokenIterator } from '../../factories'; @@ -467,21 +467,22 @@ export class SenseEditor { return req; } - const esPath = req.url; - const esMethod = req.method; - const esData = req.data; + const path = req.url; + const method = req.method; + const data = req.data; // this is the first url defined in elasticsearch.hosts - const url = es.constructESUrl(elasticsearchBaseUrl, esPath); + const url = constructUrl(elasticsearchBaseUrl, path); - let ret = 'curl -X' + esMethod + ' "' + url + '"'; - if (esData && esData.length) { - ret += " -H 'Content-Type: application/json' -d'\n"; - const dataAsString = collapseLiteralStrings(esData.join('\n')); + // Append 'kbn-xsrf' header to bypass (XSRF/CSRF) protections + let ret = `curl -X${method.toUpperCase()} "${url}" -H "kbn-xsrf: reporting"`; + if (data && data.length) { + ret += ` -H "Content-Type: application/json" -d'\n`; + const dataAsString = collapseLiteralStrings(data.join('\n')); // We escape single quoted strings that that are wrapped in single quoted strings ret += dataAsString.replace(/'/g, "'\\''"); - if (esData.length > 1) { + if (data.length > 1) { ret += '\n'; } // end with a new line ret += "'"; diff --git a/src/plugins/console/public/application/stores/request.ts b/src/plugins/console/public/application/stores/request.ts index 099ab24326d3..8056ab5a7987 100644 --- a/src/plugins/console/public/application/stores/request.ts +++ b/src/plugins/console/public/application/stores/request.ts @@ -10,18 +10,18 @@ import { Reducer } from 'react'; import { produce } from 'immer'; import { identity } from 'fp-ts/lib/function'; import { BaseResponseType } from '../../types/common'; -import { ESRequestResult } from '../hooks/use_send_current_request_to_es/send_request_to_es'; +import { RequestResult } from '../hooks/use_send_current_request/send_request'; export type Actions = | { type: 'sendRequest'; payload: undefined } - | { type: 'requestSuccess'; payload: { data: ESRequestResult[] } } - | { type: 'requestFail'; payload: ESRequestResult | undefined }; + | { type: 'requestSuccess'; payload: { data: RequestResult[] } } + | { type: 'requestFail'; payload: RequestResult | undefined }; export interface Store { requestInFlight: boolean; lastResult: { - data: ESRequestResult[] | null; - error?: ESRequestResult; + data: RequestResult[] | null; + error?: RequestResult; }; } diff --git a/src/plugins/console/public/lib/es/es.ts b/src/plugins/console/public/lib/es/es.ts index 2a4059d664e6..10d0ad95b049 100644 --- a/src/plugins/console/public/lib/es/es.ts +++ b/src/plugins/console/public/lib/es/es.ts @@ -6,8 +6,9 @@ * Side Public License, v 1. */ -import type { HttpFetchOptions, HttpResponse, HttpSetup } from '@kbn/core/public'; -import { API_BASE_PATH } from '../../../common/constants'; +import type { HttpResponse, HttpSetup } from '@kbn/core/public'; +import { trimStart } from 'lodash'; +import { API_BASE_PATH, KIBANA_API_PREFIX } from '../../../common/constants'; const esVersion: string[] = []; @@ -20,7 +21,7 @@ export function getContentType(body: unknown) { return 'application/json'; } -interface SendProps { +interface SendConfig { http: HttpSetup; method: string; path: string; @@ -30,6 +31,8 @@ interface SendProps { asResponse?: boolean; } +type Method = 'get' | 'post' | 'delete' | 'put' | 'patch' | 'head'; + export async function send({ http, method, @@ -38,18 +41,48 @@ export async function send({ asSystemRequest = false, withProductOrigin = false, asResponse = false, -}: SendProps) { - const options: HttpFetchOptions = { +}: SendConfig) { + const kibanaRequestUrl = getKibanaRequestUrl(path); + + if (kibanaRequestUrl) { + const httpMethod = method.toLowerCase() as Method; + const url = new URL(kibanaRequestUrl); + const { pathname, searchParams } = url; + const query = Object.fromEntries(searchParams.entries()); + const body = ['post', 'put', 'patch'].includes(httpMethod) ? data : null; + + return await http[httpMethod](pathname, { + body, + query, + asResponse, + asSystemRequest, + }); + } + + return await http.post(`${API_BASE_PATH}/proxy`, { query: { path, method, ...(withProductOrigin && { withProductOrigin }) }, body: data, asResponse, asSystemRequest, - }; + }); +} + +function getKibanaRequestUrl(path: string) { + const isKibanaApiRequest = path.startsWith(KIBANA_API_PREFIX); + const kibanaBasePath = window.location.origin; - return await http.post(`${API_BASE_PATH}/proxy`, options); + if (isKibanaApiRequest) { + // window.location.origin is used as a Kibana public base path for sending requests in cURL commands. E.g. "Copy as cURL". + return `${kibanaBasePath}/${trimStart(path.replace(KIBANA_API_PREFIX, ''), '/')}`; + } } -export function constructESUrl(baseUri: string, path: string) { +export function constructUrl(baseUri: string, path: string) { + const kibanaRequestUrl = getKibanaRequestUrl(path); + + if (kibanaRequestUrl) { + return kibanaRequestUrl; + } baseUri = baseUri.replace(/\/+$/, ''); path = path.replace(/^\/+/, ''); return baseUri + '/' + path; diff --git a/src/plugins/console/public/lib/es/index.ts b/src/plugins/console/public/lib/es/index.ts index 61d34ba96ec0..f83893e93713 100644 --- a/src/plugins/console/public/lib/es/index.ts +++ b/src/plugins/console/public/lib/es/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { send, constructESUrl, getContentType, getVersion } from './es'; +export { send, constructUrl, getContentType, getVersion } from './es'; diff --git a/test/functional/apps/console/_autocomplete.ts b/test/functional/apps/console/_autocomplete.ts index 57c59793f69f..7bf872373c6c 100644 --- a/test/functional/apps/console/_autocomplete.ts +++ b/test/functional/apps/console/_autocomplete.ts @@ -27,6 +27,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should provide basic auto-complete functionality', async () => { await PageObjects.console.enterRequest(); + await PageObjects.console.pressEnter(); await PageObjects.console.enterText(`{\n\t"query": {`); await PageObjects.console.pressEnter(); await PageObjects.console.promptAutocomplete(); @@ -39,6 +40,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { beforeEach(async () => { await PageObjects.console.clearTextArea(); await PageObjects.console.enterRequest(); + await PageObjects.console.pressEnter(); }); it('should add a comma after previous non empty line', async () => { await PageObjects.console.enterText(`{\n\t"query": {\n\t\t"match": {}`); @@ -85,6 +87,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { beforeEach(async () => { await PageObjects.console.clearTextArea(); await PageObjects.console.enterRequest('\n POST _snapshot/test_repo'); + await PageObjects.console.pressEnter(); }); await asyncForEach(CONDITIONAL_TEMPLATES, async ({ type, template }) => { diff --git a/test/functional/apps/console/_console.ts b/test/functional/apps/console/_console.ts index 367f1ccb5625..52218b88be60 100644 --- a/test/functional/apps/console/_console.ts +++ b/test/functional/apps/console/_console.ts @@ -106,5 +106,21 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); }); + + describe('with kbn: prefix in request', () => { + before(async () => { + await PageObjects.console.clearTextArea(); + }); + it('it should send successful request to Kibana API', async () => { + const expectedResponseContains = 'default space'; + await PageObjects.console.enterRequest('\n GET kbn:/api/spaces/space'); + await PageObjects.console.clickPlay(); + await retry.try(async () => { + const actualResponse = await PageObjects.console.getResponse(); + log.debug(actualResponse); + expect(actualResponse).to.contain(expectedResponseContains); + }); + }); + }); }); } diff --git a/test/functional/page_objects/console_page.ts b/test/functional/page_objects/console_page.ts index 281c49a789ac..7aaf842f28d1 100644 --- a/test/functional/page_objects/console_page.ts +++ b/test/functional/page_objects/console_page.ts @@ -102,7 +102,6 @@ export class ConsolePageObject extends FtrService { public async enterRequest(request: string = '\nGET _search') { const textArea = await this.getEditorTextArea(); await textArea.pressKeys(request); - await textArea.pressKeys(Key.ENTER); } public async enterText(text: string) {