[Console] Add Kibana APIs Support (#128562)

* Add support for Kibana API requests

* Fix failing tests

* Support leading / and minor refactor

* Resolve conflicts

* Update send_request.test file

* Refactor

* Add functional test

* Address comments

* Fix typo

* Resolve conflicts and refactor error handling

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* Address comments

* Resolve merge conflicts

* Rename KIBANA_API_KEYWORD

Co-authored-by: Muhammad Ibragimov <muhammad.ibragimov@elastic.co>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Muhammad Ibragimov 2022-04-21 19:49:04 +05:00 committed by GitHub
parent 1a1adb9e35
commit 502a00b025
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 173 additions and 130 deletions

View file

@ -7,3 +7,4 @@
*/
export const API_BASE_PATH = '/api/console';
export const KIBANA_API_PREFIX = 'kbn:';

View file

@ -7,4 +7,4 @@
*/
export { MAJOR_VERSION } from './plugin';
export { API_BASE_PATH } from './api';
export { API_BASE_PATH, KIBANA_API_PREFIX } from './api';

View file

@ -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(),

View file

@ -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', () => {

View file

@ -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<HTMLDivElement | null>(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) {
>
<EuiLink
color="success"
onClick={sendCurrentRequestToES}
onClick={sendCurrentRequest}
data-test-subj="sendRequestButton"
aria-label={i18n.translate('console.sendRequestButtonTooltip', {
defaultMessage: 'Click to send request',

View file

@ -11,7 +11,7 @@ import { SenseEditor } from '../../../../models/sense_editor';
interface Actions {
senseEditor: SenseEditor;
sendCurrentRequestToES: () => 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();
},
});

View file

@ -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';

View file

@ -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';

View file

@ -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);
});
});
});

View file

@ -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<V = unknown> {
export interface ResponseObject<V = unknown> {
statusCode: number;
statusText: string;
timeMs: number;
@ -28,17 +28,17 @@ export interface ESResponseObject<V = unknown> {
value: V;
}
export interface ESRequestResult<V = unknown> {
export interface RequestResult<V = unknown> {
request: { data: string; method: string; path: string };
response: ESResponseObject<V>;
response: ResponseObject<V>;
}
let CURRENT_REQ_ID = 0;
export function sendRequestToES(args: EsRequestArgs): Promise<ESRequestResult[]> {
export function sendRequest(args: RequestArgs): Promise<RequestResult[]> {
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<ESRequestResult[]>
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<ESRequestResult[]>
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<ESRequestResult[]>
value,
},
request: {
data: esData,
method: esMethod,
path: esPath,
data,
method,
path,
},
});
@ -127,25 +127,19 @@ export function sendRequestToES(args: EsRequestArgs): Promise<ESRequestResult[]>
}
} 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<ESRequestResult[]>
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,
},
});
}

View file

@ -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);

View file

@ -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();

View file

@ -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

View file

@ -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 += "'";

View file

@ -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<string> | undefined };
| { type: 'requestSuccess'; payload: { data: RequestResult[] } }
| { type: 'requestFail'; payload: RequestResult<string> | undefined };
export interface Store {
requestInFlight: boolean;
lastResult: {
data: ESRequestResult[] | null;
error?: ESRequestResult<string>;
data: RequestResult[] | null;
error?: RequestResult<string>;
};
}

View file

@ -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]<HttpResponse>(pathname, {
body,
query,
asResponse,
asSystemRequest,
});
}
return await http.post<HttpResponse>(`${API_BASE_PATH}/proxy`, {
query: { path, method, ...(withProductOrigin && { withProductOrigin }) },
body: data,
asResponse,
asSystemRequest,
};
return await http.post<HttpResponse>(`${API_BASE_PATH}/proxy`, options);
});
}
export function constructESUrl(baseUri: string, path: string) {
function getKibanaRequestUrl(path: string) {
const isKibanaApiRequest = path.startsWith(KIBANA_API_PREFIX);
const kibanaBasePath = window.location.origin;
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 constructUrl(baseUri: string, path: string) {
const kibanaRequestUrl = getKibanaRequestUrl(path);
if (kibanaRequestUrl) {
return kibanaRequestUrl;
}
baseUri = baseUri.replace(/\/+$/, '');
path = path.replace(/^\/+/, '');
return baseUri + '/' + path;

View file

@ -6,4 +6,4 @@
* Side Public License, v 1.
*/
export { send, constructESUrl, getContentType, getVersion } from './es';
export { send, constructUrl, getContentType, getVersion } from './es';

View file

@ -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": {}`);

View file

@ -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);
});
});
});
});
}

View file

@ -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) {