mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[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:
parent
1a1adb9e35
commit
502a00b025
21 changed files with 173 additions and 130 deletions
|
@ -7,3 +7,4 @@
|
|||
*/
|
||||
|
||||
export const API_BASE_PATH = '/api/console';
|
||||
export const KIBANA_API_PREFIX = 'kbn:';
|
||||
|
|
|
@ -7,4 +7,4 @@
|
|||
*/
|
||||
|
||||
export { MAJOR_VERSION } from './plugin';
|
||||
export { API_BASE_PATH } from './api';
|
||||
export { API_BASE_PATH, KIBANA_API_PREFIX } from './api';
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
}
|
|
@ -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);
|
|
@ -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();
|
|
@ -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
|
||||
|
|
|
@ -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 += "'";
|
||||
|
|
|
@ -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>;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -6,4 +6,4 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { send, constructESUrl, getContentType, getVersion } from './es';
|
||||
export { send, constructUrl, getContentType, getVersion } from './es';
|
||||
|
|
|
@ -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": {}`);
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue